From c278a99587b71cead6f3f86c23e8e51a1044aa5e Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sat, 27 Jun 2026 20:18:27 +0000 Subject: [PATCH 01/71] Add plan for Rocq standard library benchmark suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds benchmarks/stdlib/PLAN.md: a plan for a per-file benchmark comparing MEngine vs Rocq on a curated, auto-translatable subset of the Rocq stdlib. No implementation changes — planning artifact only. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/PLAN.md | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 benchmarks/stdlib/PLAN.md diff --git a/benchmarks/stdlib/PLAN.md b/benchmarks/stdlib/PLAN.md new file mode 100644 index 0000000..904e410 --- /dev/null +++ b/benchmarks/stdlib/PLAN.md @@ -0,0 +1,189 @@ +# Plan: Rocq Standard Library Benchmark Suite for MEngine + +Status: plan (not yet implemented). Scope decisions locked with the user: +- **Granularity:** per-file first; per-proof is a later optional phase. +- **Corpus scope:** Tier A only — files the translator handles with **zero manual edits**. + Tier B (manual/aligned) and Tier C (out of scope) are documented but not built initially. +- **Constraint:** do **not** edit the MEngine implementation (`src/`, `Makefile`). All work is + new files under `benchmarks/stdlib/`. + +--- + +## 1. Feasibility verdict (read this first) + +A pure text-level translator of *arbitrary* stdlib files is **not** feasible. Two gaps dominate: + +- **Notation.** MEngine has no notation system. Rocq stdlib is saturated with infix: `A -> B`, + `x = y`, `n + m`, `/\`, `\/`, `<=`, decimal literals (`0`, `42`), list `::`/`[]`. MEngine wants + `forall (_:A), B`, `eq nat x y`, prefix application, and `S (S O)` constructors. Every one must + be desugared. +- **Tactics.** MEngine has ~14 primitives (`intro/intros/apply/eapply/exact/rewrite/cbv/ + reflexivity/split/left/right/exists/assumption`) plus a thin prelude. It has **no** `induction`, + `destruct`, `constructor`, `simpl`, `lia`, `ring`, real `auto`, `symmetry`, `trivial`, + `inversion`. Most real stdlib proofs use these on line one. + +Structural gaps too: no `Require`/`Import`/`Module`/`Section`/`Notation`/`Record`/`Class`/`Scope`, +no implicit args `{A}`, single-binder `forall (x:T),`, mandatory `{struct n}` on `Fixpoint`, no +mutual inductives. + +Grounding facts verified in-tree: +- `src/runtime/core.c` provides only `eq`, `and`, `or`, `ex`, `Reflexive`. **No** `nat`, `bool`, + `list`, `True`/`False` — these must come from a compat prelude or each unit. +- `src/main.c` exposes `-q/--quiet` and `--time` (whole-run *subsystem* totals on exit, **not** + per-proof). So there is no per-proof timer we can use; per-proof requires file-splitting. + +**Consequence:** viable benchmark over a *curated Tier-A subset* — the computational/equational +corners of the stdlib (Bool, basic Arith provable by `rewrite`/`reflexivity`/`cbv`, structural +list lemmas) — reached through a layered pipeline (mechanical translator + hand-written compat +prelude). `bench`/runner only builds Rocq files that successfully translate. + +## 2. Granularity + +- **Per-file (primary, build now):** time `coqc rocq.v` vs `mengine mengine.me` end to end. + Robust, simple, symmetric. +- **Per-proof (optional, later):** no cross-engine per-proof timer exists (MEngine has none; + Rocq's `coqc -time` is per-sentence but has no MEngine analog). Portable route = **file + splitting**: one unit per theorem = `shared preamble + single Theorem`, time each in both + engines, subtract a measured **preamble-only baseline** to isolate proof cost. `coqc -time` + output captured as a Rocq-side cross-check. + +## 3. Why a sibling driver (not the existing framework) + +`benchmarks/framework/` is built for parametric scaling sweeps: `ParamSpec` integer ranges, +adaptive step sizing (`runner._adaptive_step`), auto-retirement on consecutive failures. A fixed +corpus is a different shape. We will **not** subclass `Benchmark`; we add a sibling driver that +**reuses the timing core** (`framework/runner.run_single`: process-group kill on timeout, N-trials +keep-min, soft/hard timeouts) but iterates the manifest instead of sweeping a range. Existing +`framework/` and `benchmarks/` packages are untouched. + +## 4. Directory layout (all new, under `benchmarks/`) + +``` +benchmarks/stdlib/ + PLAN.md # this document + README.md # how the suite works, how to add a file + translate.py # mechanical Rocq .v -> MEngine .me translator + stdlib_bench.py # corpus runner (list/test/run/report subcommands) + report.py # markdown table + scatter plot (Rocq vs MEngine) + compat/ + stdlib_compat.me # compat prelude: nat/bool/list defs + emulated tactics + corpus/ + manifest.json # in-scope units: source path+sha, tier, deps, notes + / + rocq.v # benchmarked Rocq version (Requires installed stdlib) + mengine.me # MEngine version (Tier A: auto-translated) + results/ # per-unit timings (json), framework-style schema + plots/ # generated comparison artifacts +``` + +No edits to `src/`, `Makefile`, or existing `benchmarks/framework|benchmarks`, except an optional +one-line pointer added to `benchmarks/README.md`. + +## 5. Translator (`translate.py`) + +Single readable module. `translate.py file.v > out.me`; plus `--report` mode listing handled vs +unhandled constructs (drives triage). It is a **lightweight token/sentence-aware rewriter** (split +on `.` respecting `(* *)` comments and strings), not a full Coq parser. Ordered rules: + +**Structural (drop / unwrap):** +- Strip `Require`/`Import`/`Export`/`Open Scope`/`Hint`/`Set`/`Unset`/`Arguments`/`#[...]`. +- Drop `Section ... End`, `Module ... End` wrappers (keep bodies; flag `Context`/`Variable`). +- Remove `Proof.`/`Qed.`/`Defined.`/`Admitted.` framing (MEngine ends a proof when goals close). +- `Parameter`/`Conjecture` -> `Axiom`. + +**Term desugaring (the bulk):** +- Non-dependent `A -> B` -> `forall (_ : A), B`. +- Multi-binder expansion: `forall x y z, P` / `forall (x y : T), P` -> nested single binders; + same for `fun`. +- `fun x : T => e` -> `fun (x : T) => e`; normalize `=>` / `,`. +- Notation -> prefix application via configurable table: `=`->`eq`, `/\`->`and`, `\/`->`or`, + `exists x, P`->`ex ...`, etc. (`eq`/`and`/`or`/`ex` heads exist in `core.c`.) Operators needing + a type argument (`eq` is `eq A x y`) handled where recoverable, else flagged. +- Numeric literals `0,1,2,...` -> `O, S O, ...` (bounded; large literals flagged). +- `Fixpoint`: inject `{struct }` if absent (flag if decreasing arg isn't first). +- `match ... end`: pass through; flag `as`/`in`/`return` clauses and nested patterns as manual. + +**Tactics (line-by-line, conservative):** +- Pass through the supported set (`intro(s)/apply/eapply/exact/rewrite/reflexivity/cbv/split/ + left/right/exists/assumption`). +- Map known aliases to compat-prelude tactics: `simpl`->`cbv`, `symmetry`/`trivial`/`now`/`easy` + -> compat equivalents (sec. 6). +- Any unsupported tactic (`induction/destruct/lia/ring/inversion/auto/...`) -> **hard stop**: + unit marked `manual`, excluded from Tier A; `--report` lists the offending tactic. + +Principle: **flag, never guess.** A wrong translation that happens to compile would corrupt the +benchmark, so the translator refuses rather than emit an unjustified rewrite. + +## 6. Compat prelude (`compat/stdlib_compat.me`) + +Hand-written MEngine, loaded ahead of every translated unit. Contents: +- **Base datatypes** stdlib assumes but `core.c` lacks: `nat` (`O`/`S`), `bool`, `list`, `option`, + `True`/`False`, plus basic `Fixpoint`s (`add`, `mul`, `app`, ...), matching Rocq's definitions + exactly so theorem statements line up. +- **Emulated tactics** in the tactic language (same pattern as the separation-logic benchmark's + `.me` tactic defs): + - `symmetry` := `apply eq_sym` (with a provided `eq_sym`). + - `trivial`/`easy`/`now` := `try reflexivity; try assumption` (+ `intros`). + - `simpl` := `cbv beta delta iota fix`. + - limited `destruct`/`induction` helpers: `match Goal` to find the inductive head, then `apply` + the auto-generated induction principle (MEngine builds these for `Inductive` decls). Clean + structural cases only; complex eliminations stay Tier B. +- Each emulated tactic carries a comment stating exactly how it differs from Rocq's, so divergence + is auditable and lives in one reviewable file. + +## 7. Faithfulness validation (integrity) + +`stdlib_bench.py test` verifies, per unit, before any timing: +1. `rocq.v` compiles against installed stdlib (`coqc -time`, with `-Q`/`-R` from config). +2. `mengine.me` runs clean under `mengine -q`. +3. **Statement correspondence:** translator emits a normalized statement digest; the Rocq and + MEngine statements must correspond. Guards against benchmarking two engines on *different* + theorems. Only units passing all three enter the timed corpus. + +## 8. Runner & Rocq instrumentation (`stdlib_bench.py`) + +Reuses `framework/runner.run_single` timing logic; subcommands mirror `bench.py`: `list`, `test`, +`run [unit]`, `report`. +- **MEngine:** `mengine -q /mengine.me`, run from `mengine_root` so `prelude/` resolves; + compat prelude prepended. +- **Rocq:** `coqc -time /rocq.v` with load paths from config. Rely on the **installed + stdlib** for `Require` (deps are *not* recompiled — we time only the translated unit's work, + the fair comparison). `-time` per-sentence output stored alongside wall-clock. +- **Config:** extend `benchmarks/config.json` with a `stdlib` block (rocq stdlib source path for + translation input, coqc load-path args, per-unit timeout). Reuse existing + `mengine_path`/`coq_path`/`trials`/timeout keys. +- **Results:** `results/stdlib.json`, keyed `{engine}_{unit}` (and `_{proof}` in per-proof mode), + same value schema (`time_taken`/`success`/`trials`) as the framework. + +## 9. Reporting (`report.py`) + +- **Markdown table:** per unit — Rocq time, MEngine time, ratio, tier, status — drop-in for + `README.md` like the existing benchmark table. +- **Scatter plot:** Rocq time (x) vs MEngine time (y), log-log, parity diagonal, colored by tier. +- **Summary:** geometric-mean speedup over Tier A; counts (translated / built-in-both / excluded). + +## 10. Phased roadmap (per-file, Tier A) + +1. **Skeleton + one unit end to end.** Layout, `config.json` `stdlib` block, compat prelude with + `nat`/`bool` + 2-3 tactics, `stdlib_bench.py` (`test`/`run`), one hand-made Tier-A unit (e.g. + `add_0_r`). Proves translate->validate->time->report before scaling. +2. **Translator core.** Structural stripping + term desugaring + supported-tactic passthrough + + `--report`. Sanity-check against `examples/*.me` and the first units. +3. **Triage.** Run `--report` over a Rocq stdlib checkout; populate `manifest.json`; lock the + Tier-A set (Tier B/C recorded but not built). +4. **Corpus build-out.** Auto-translate Tier A; expand compat prelude as recurring needs surface. +5. **Full run + report.** `stdlib_bench.py run`; generate table + scatter; optional row in + `benchmarks/README.md`. +6. **Per-proof mode (optional, later).** File-splitting + preamble-baseline subtraction; `-time` + cross-check. + +## 11. Key risks + +- **Tier A is small.** Likely, given the tactic gap. Mitigation: lean on the compat prelude; + accept "useful benchmark" = tens of representative equational/structural proofs, not breadth. + Tier B (manual, deferred) is the growth path if more coverage is wanted later. +- **Mistranslation that compiles** (silent unfaithfulness — the worst failure). Mitigation: + flag-not-guess translator + sec. 7 statement-correspondence check. +- **Fairness of the Rocq baseline.** One-theorem `.v` against installed stdlib includes Rocq + process/Require startup. Mitigation: symmetric treatment (MEngine pays prelude startup too) + + preamble-baseline subtraction in per-proof mode. From bc8d4806f1008edd20e7d298920c06366a4c8a39 Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sat, 27 Jun 2026 22:09:18 +0000 Subject: [PATCH 02/71] Adapt stdlib benchmark plan to generated induction principles Rebased onto induction-principle-with-ih, which makes MEngine generate proper _ind (with induction hypotheses) for parametric and non-parametric inductives. Update the plan accordingly: - Replace the "no induction" stance with an emulation strategy: translate `induction x` to `intro x; apply (T_ind (fun x => )); `, with the IH named as Rocq does. New section 5a documents the mechanics verified against the engine. - Record the two Tier-2 caveats that still gate computational induction (apply-with-fixpoint-motive segfault; ground-only iota), with pointers to the bugs/ reproducers, and note that closing them is the biggest corpus-expansion lever and is now in scope. - Relax the "do not edit the engine" constraint to an engine baseline, and update the translator rules, compat prelude, roadmap, and risks. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/PLAN.md | 98 +++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/benchmarks/stdlib/PLAN.md b/benchmarks/stdlib/PLAN.md index 904e410..f0123fb 100644 --- a/benchmarks/stdlib/PLAN.md +++ b/benchmarks/stdlib/PLAN.md @@ -4,8 +4,13 @@ Status: plan (not yet implemented). Scope decisions locked with the user: - **Granularity:** per-file first; per-proof is a later optional phase. - **Corpus scope:** Tier A only — files the translator handles with **zero manual edits**. Tier B (manual/aligned) and Tier C (out of scope) are documented but not built initially. -- **Constraint:** do **not** edit the MEngine implementation (`src/`, `Makefile`). All work is - new files under `benchmarks/stdlib/`. +- **Engine baseline:** this branch is rebased on `induction-principle-with-ih`, so the benchmark + targets a MEngine that **generates proper induction principles** — `_ind` with induction + hypotheses, for both parametric and non-parametric inductives. The original "do not touch the + engine" rule has been relaxed: the user is willing to make targeted kernel fixes that unblock the + benchmark. Remaining engine gaps are tracked as **Tier 2** (sec. 5a); taking them on + substantially widens the corpus. The benchmark *machinery itself* still lives entirely under + `benchmarks/stdlib/`. --- @@ -18,9 +23,11 @@ A pure text-level translator of *arbitrary* stdlib files is **not** feasible. Tw `forall (_:A), B`, `eq nat x y`, prefix application, and `S (S O)` constructors. Every one must be desugared. - **Tactics.** MEngine has ~14 primitives (`intro/intros/apply/eapply/exact/rewrite/cbv/ - reflexivity/split/left/right/exists/assumption`) plus a thin prelude. It has **no** `induction`, - `destruct`, `constructor`, `simpl`, `lia`, `ring`, real `auto`, `symmetry`, `trivial`, - `inversion`. Most real stdlib proofs use these on line one. + reflexivity/split/left/right/exists/assumption`) plus a thin prelude. There is still no native + `induction`/`destruct` *tactic*, but the engine now **generates the induction principle** + `_ind` (with IH) for every inductive, so `induction`/`destruct` can be *emulated* by applying + it (sec. 5a). Still missing: `constructor`, `simpl`, `lia`, `ring`, real `auto`, `symmetry`, + `trivial`, `inversion`. Most real stdlib proofs use several of these on line one. Structural gaps too: no `Require`/`Import`/`Module`/`Section`/`Notation`/`Record`/`Class`/`Scope`, no implicit args `{A}`, single-binder `forall (x:T),`, mandatory `{struct n}` on `Fixpoint`, no @@ -34,8 +41,11 @@ Grounding facts verified in-tree: **Consequence:** viable benchmark over a *curated Tier-A subset* — the computational/equational corners of the stdlib (Bool, basic Arith provable by `rewrite`/`reflexivity`/`cbv`, structural -list lemmas) — reached through a layered pipeline (mechanical translator + hand-written compat -prelude). `bench`/runner only builds Rocq files that successfully translate. +list lemmas) plus **structural induction proofs** now reachable through the generated `_ind` +(sec. 5a) — reached through a layered pipeline (mechanical translator + hand-written compat +prelude). Two engine gaps (sec. 5a) still block *computational* induction (the bulk of stdlib +arithmetic); closing them is the main lever for growing the corpus. `bench`/runner only builds Rocq +files that successfully translate. ## 2. Granularity @@ -108,12 +118,65 @@ on `.` respecting `(* *)` comments and strings), not a full Coq parser. Ordered left/right/exists/assumption`). - Map known aliases to compat-prelude tactics: `simpl`->`cbv`, `symmetry`/`trivial`/`now`/`easy` -> compat equivalents (sec. 6). -- Any unsupported tactic (`induction/destruct/lia/ring/inversion/auto/...`) -> **hard stop**: +- `induction x` / `destruct x` -> emit the generated-principle application (sec. 5a), but only when + it is safe (motive free of fixpoints; step case needs no symbolic reduction). Otherwise flag. +- Remaining unsupported tactics (`lia/ring/inversion/auto/constructor/...`) -> **hard stop**: unit marked `manual`, excluded from Tier A; `--report` lists the offending tactic. Principle: **flag, never guess.** A wrong translation that happens to compile would corrupt the benchmark, so the translator refuses rather than emit an unjustified rewrite. +## 5a. Induction (generated `_ind`) + +The engine now generates, for every inductive `T`, the principle `T_ind` carrying an induction +hypothesis for each recursive argument — e.g. + +``` +nat_ind : forall (P : nat -> Prop), P O -> (forall n, P n -> P (S n)) -> forall n, P n +list_ind : forall (A : Type) (P : list A -> Prop), + P (nil A) -> (forall a l, P l -> P (cons A a l)) -> forall l, P l +``` + +**Translating `induction x`.** Emit an explicit application of the principle: + +``` +intro x. +apply (T_ind (fun (x : T) => )). + ... (* one focused subgoal per constructor, in declaration order *) +``` + +Verified against the engine: +- The `intro x` **then** `apply (T_ind motive)` order matters. MEngine's first-order `apply` can't + match the principle's conclusion under the goal's outer `forall`, so `x` is introduced first and + the conclusion then unifies at the concrete `x`. (`apply` of the bare principle, or with a motive + but no prior `intro`, fails with `fill: hole fill failed`.) +- The motive `fun (x:T) => ` is computed textually. The clean case is `induction x` on a + **leading** universally-quantified variable (goal `forall (x:T), Body`, so the body is `Body`). + `induction` after intros, on a non-leading variable, or with hypotheses depending on `x` (which + Rocq auto-reverts) is Tier B / out of scope. +- Each constructor yields one focused subgoal in declaration order; the step case exposes the + recursive arguments and the IH. The translator introduces them and names the IH as Rocq does + (`IH`) so the remaining ported lines line up. +- `destruct x` is the same application, ignoring the IH in the step case(s). + +**Tier-2 caveats (these gate the computational majority).** Two engine issues — each with a minimal +reproducer in `bugs/` on the engine branch — block induction over goals stated with a `Fixpoint`: + +1. **`apply` with a fixpoint-mentioning motive segfaults.** When `` applies a defined + `Fixpoint` to `x` (e.g. `eq nat (add x O) x`), the `apply (T_ind motive)` step crashes. So any + equational arithmetic/list lemma whose *statement* uses a recursive function is unreachable by + induction right now. +2. **Ground-only iota.** `simpl`/`cbv` do not reduce a fixpoint applied to a constructor-headed + *symbolic* term (`add (S n) m`, `app (cons a l) m`), so step cases that must unfold a recursive + function on `S n` / `cons a l` cannot progress. + +Until (1) and (2) are fixed, the translator must **detect and exclude** induction whose motive +mentions a defined fixpoint or whose step case needs symbolic reduction, keeping such units out of +Tier A. What works **today**: structural induction with a fixpoint-free motive whose cases close via +the IH, constructors, `apply`/`rewrite` with lemmas, and `assumption` — i.e. relational/predicate +lemmas rather than computational equalities. Fixing (1) and (2) is the highest-leverage corpus +expansion and is now in scope (the engine is already being modified on the parent branch). + ## 6. Compat prelude (`compat/stdlib_compat.me`) Hand-written MEngine, loaded ahead of every translated unit. Contents: @@ -125,9 +188,10 @@ Hand-written MEngine, loaded ahead of every translated unit. Contents: - `symmetry` := `apply eq_sym` (with a provided `eq_sym`). - `trivial`/`easy`/`now` := `try reflexivity; try assumption` (+ `intros`). - `simpl` := `cbv beta delta iota fix`. - - limited `destruct`/`induction` helpers: `match Goal` to find the inductive head, then `apply` - the auto-generated induction principle (MEngine builds these for `Inductive` decls). Clean - structural cases only; complex eliminations stay Tier B. + - `destruct`/`induction` support built on the generated `_ind` (now IH-bearing, sec. 5a). + Whether this lives as a compat tactic or as direct translator-emitted `apply (T_ind motive)` + is decided in implementation; either way, clean structural cases only — computational and + complex eliminations stay Tier 2 / Tier B. - Each emulated tactic carries a comment stating exactly how it differs from Rocq's, so divergence is auditable and lives in one reviewable file. @@ -168,7 +232,8 @@ Reuses `framework/runner.run_single` timing logic; subcommands mirror `bench.py` `nat`/`bool` + 2-3 tactics, `stdlib_bench.py` (`test`/`run`), one hand-made Tier-A unit (e.g. `add_0_r`). Proves translate->validate->time->report before scaling. 2. **Translator core.** Structural stripping + term desugaring + supported-tactic passthrough + - `--report`. Sanity-check against `examples/*.me` and the first units. + the `induction`/`destruct` rule (sec. 5a) with fixpoint-motive exclusion + `--report`. + Sanity-check against `examples/*.me` and the first units (include one structural induction unit). 3. **Triage.** Run `--report` over a Rocq stdlib checkout; populate `manifest.json`; lock the Tier-A set (Tier B/C recorded but not built). 4. **Corpus build-out.** Auto-translate Tier A; expand compat prelude as recurring needs surface. @@ -179,9 +244,12 @@ Reuses `framework/runner.run_single` timing logic; subcommands mirror `bench.py` ## 11. Key risks -- **Tier A is small.** Likely, given the tactic gap. Mitigation: lean on the compat prelude; - accept "useful benchmark" = tens of representative equational/structural proofs, not breadth. - Tier B (manual, deferred) is the growth path if more coverage is wanted later. +- **Tier A is small.** Likely, given the tactic gap. Induction principles widen it to structural + inductive proofs, but the Tier-2 gaps (sec. 5a) keep *computational* induction — most stdlib + arithmetic — out for now. Mitigations, in leverage order: (1) take on the Tier-2 engine fixes + (the single biggest corpus multiplier, now in scope); (2) lean on the compat prelude; (3) accept + "useful benchmark" = tens of representative proofs, not breadth. Tier B (manual, deferred) is a + further growth path. - **Mistranslation that compiles** (silent unfaithfulness — the worst failure). Mitigation: flag-not-guess translator + sec. 7 statement-correspondence check. - **Fairness of the Rocq baseline.** One-theorem `.v` against installed stdlib includes Rocq From 6b349eec65cfa9a61b1243a4534225fe51643713 Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sat, 27 Jun 2026 22:16:45 +0000 Subject: [PATCH 03/71] Support constructor via compat prelude in stdlib benchmark plan Reframe constructor from a translator hard-stop to a runtime tactic emulated in the compat prelude, the same way split/left/right already are. It needs no engine change: match Goal reads the goal head, first tries constructors in declaration order, and eapply leaves multi-arg constructors' arguments as subgoals. - Sec. 2: move constructor off the "still missing" list. - Sec. 4: pass constructor/constructor n through to the prelude tactic; flag only when the goal head isn't enumerated. Drop it from hard-stop. - New sec. 6a: document the tactic, the constructor n -> eapply C_n shortcut, soundness under "flag, never guess," the leverage (it unblocks the relational lemmas that already work), and limitations (no eexists witness-guessing; a fully generic engine-level version is deferred). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/PLAN.md | 48 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/benchmarks/stdlib/PLAN.md b/benchmarks/stdlib/PLAN.md index f0123fb..f343ce8 100644 --- a/benchmarks/stdlib/PLAN.md +++ b/benchmarks/stdlib/PLAN.md @@ -26,8 +26,9 @@ A pure text-level translator of *arbitrary* stdlib files is **not** feasible. Tw reflexivity/split/left/right/exists/assumption`) plus a thin prelude. There is still no native `induction`/`destruct` *tactic*, but the engine now **generates the induction principle** `_ind` (with IH) for every inductive, so `induction`/`destruct` can be *emulated* by applying - it (sec. 5a). Still missing: `constructor`, `simpl`, `lia`, `ring`, real `auto`, `symmetry`, - `trivial`, `inversion`. Most real stdlib proofs use several of these on line one. + it (sec. 5a). `constructor` is emulated in the compat prelude (sec. 6a). Still missing: `simpl`, + `lia`, `ring`, real `auto`, `symmetry`, `trivial`, `inversion`. Most real stdlib proofs use + several of these on line one. Structural gaps too: no `Require`/`Import`/`Module`/`Section`/`Notation`/`Record`/`Class`/`Scope`, no implicit args `{A}`, single-binder `forall (x:T),`, mandatory `{struct n}` on `Fixpoint`, no @@ -118,9 +119,11 @@ on `.` respecting `(* *)` comments and strings), not a full Coq parser. Ordered left/right/exists/assumption`). - Map known aliases to compat-prelude tactics: `simpl`->`cbv`, `symmetry`/`trivial`/`now`/`easy` -> compat equivalents (sec. 6). +- `constructor` / `constructor n` -> pass through to the compat-prelude `constructor` tactic + (sec. 6a), but only when the goal head is one the prelude enumerates; otherwise flag. - `induction x` / `destruct x` -> emit the generated-principle application (sec. 5a), but only when it is safe (motive free of fixpoints; step case needs no symbolic reduction). Otherwise flag. -- Remaining unsupported tactics (`lia/ring/inversion/auto/constructor/...`) -> **hard stop**: +- Remaining unsupported tactics (`lia/ring/inversion/auto/...`) -> **hard stop**: unit marked `manual`, excluded from Tier A; `--report` lists the offending tactic. Principle: **flag, never guess.** A wrong translation that happens to compile would corrupt the @@ -195,6 +198,45 @@ Hand-written MEngine, loaded ahead of every translated unit. Contents: - Each emulated tactic carries a comment stating exactly how it differs from Rocq's, so divergence is auditable and lives in one reviewable file. +## 6a. `constructor` (compat prelude, no engine change) + +`constructor` is **not** a static-translation problem and does not need a new engine primitive. It +is the generalization of `split`/`left`/`right`, which the existing prelude already implements as +runtime tactics via `match Goal with … => eapply `. The tactic language supplies the three +primitives needed: `match Goal with` (read the goal head), `first [ … | … ]` (try constructors in +declaration order), and `eapply` (which leaves a multi-arg constructor's arguments as subgoals/evars, +exactly as `split` leaves two subgoals). + +Because the compat prelude already *defines* every inductive the corpus uses, we enumerate their +constructors once in a single dispatch tactic, e.g. + +``` +Tactic constructor := match Goal with +| [ |- True ] => exact I +| [ |- ((and ?A) ?B) ] => eapply ((conj A) B) +| [ |- ((or ?A) ?B) ] => first [ eapply (or_introl A B) | eapply (or_intror A B) ] +| [ |- ((le ?n) ?m) ] => first [ eapply (le_n n) | eapply (le_S n ...) ] +| ... (* one arm per relational predicate in the corpus *) +end. +``` + +`first` over the constructors reproduces Rocq's "try each in declaration order" semantics, and each +arm builds a full proof term, so this stays sound under **flag, never guess** — the arms are +hand-verified, exactly like `split`. `constructor n` translates directly to `eapply C_n`, no +dispatch needed. + +This is high leverage: the lemmas already reachable today (sec. 5a: relational/predicate lemmas +such as `le`, ordering, custom inductive predicates) are precisely the ones whose proofs lead with +`constructor`, so adding it promotes a chunk of current hard-stops into Tier A at near-zero cost. It +is orthogonal to the two Tier-2 engine caveats (those gate computational induction, not `constructor`). + +Limitations, kept honest: witness-guessing (`constructor` on `ex`, i.e. `eexists`) stays out, the +same boundary as today; and a goal whose head the prelude does not enumerate is still flagged — but +the supported set is now "every inductive in the compat prelude," not "none." A fully generic +`constructor` over *any* inductive would want a small engine primitive that reads the goal's +inductive and tries each stored constructor (the engine already keeps that list — it generates +`_ind` from it); that is the "do it once properly" path, deferred and out of scope here. + ## 7. Faithfulness validation (integrity) `stdlib_bench.py test` verifies, per unit, before any timing: From f2aeb9870f75bf02b32e4133833234c5687b9ac1 Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sun, 28 Jun 2026 04:20:43 +0000 Subject: [PATCH 04/71] Add Rocq stdlib benchmark suite The per-module Rocq-stdlib benchmark machinery: translator, corpus runner, compat prelude, report generator, and the initial Bool/Logic/Nat/le corpus. The enabling kernel fixes it relies on live on the engine-fixes branch this is based on. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- .gitignore | 4 + benchmarks/README.md | 13 + benchmarks/config.json | 9 + benchmarks/stdlib/PLAN.md | 9 +- benchmarks/stdlib/README.md | 117 ++ benchmarks/stdlib/compat/stdlib_compat.me | 147 ++ .../stdlib/corpus/bool_andb_b_b/mengine.me | 7 + benchmarks/stdlib/corpus/bool_andb_b_b/rocq.v | 5 + .../corpus/bool_andb_false_l/mengine.me | 3 + .../stdlib/corpus/bool_andb_false_l/rocq.v | 2 + .../stdlib/corpus/bool_andb_true_l/mengine.me | 3 + .../stdlib/corpus/bool_andb_true_l/rocq.v | 2 + .../stdlib/corpus/bool_andb_true_r/mengine.me | 7 + .../stdlib/corpus/bool_andb_true_r/rocq.v | 5 + .../stdlib/corpus/bool_negb_false/mengine.me | 2 + .../stdlib/corpus/bool_negb_false/rocq.v | 2 + .../corpus/bool_negb_involutive/mengine.me | 7 + .../stdlib/corpus/bool_negb_involutive/rocq.v | 5 + .../stdlib/corpus/bool_negb_true/mengine.me | 2 + .../stdlib/corpus/bool_negb_true/rocq.v | 2 + .../stdlib/corpus/bool_orb_b_b/mengine.me | 7 + benchmarks/stdlib/corpus/bool_orb_b_b/rocq.v | 5 + .../stdlib/corpus/bool_orb_false_l/mengine.me | 3 + .../stdlib/corpus/bool_orb_false_l/rocq.v | 2 + .../stdlib/corpus/bool_orb_false_r/mengine.me | 7 + .../stdlib/corpus/bool_orb_false_r/rocq.v | 5 + .../stdlib/corpus/bool_orb_true_l/mengine.me | 3 + .../stdlib/corpus/bool_orb_true_l/rocq.v | 2 + benchmarks/stdlib/corpus/le_0_n/mengine.me | 10 + benchmarks/stdlib/corpus/le_0_n/rocq.v | 5 + benchmarks/stdlib/corpus/le_1_2/mengine.me | 3 + benchmarks/stdlib/corpus/le_1_2/rocq.v | 2 + benchmarks/stdlib/corpus/le_2_4/mengine.me | 4 + benchmarks/stdlib/corpus/le_2_4/rocq.v | 2 + benchmarks/stdlib/corpus/le_refl_n/mengine.me | 3 + benchmarks/stdlib/corpus/le_refl_n/rocq.v | 2 + .../stdlib/corpus/logic_and_intro/mengine.me | 3 + .../stdlib/corpus/logic_and_intro/rocq.v | 2 + .../stdlib/corpus/logic_and_intro3/mengine.me | 7 + .../stdlib/corpus/logic_and_intro3/rocq.v | 2 + .../stdlib/corpus/logic_imp_refl/mengine.me | 4 + .../stdlib/corpus/logic_imp_refl/rocq.v | 2 + .../stdlib/corpus/logic_imp_trans/mengine.me | 5 + .../stdlib/corpus/logic_imp_trans/rocq.v | 2 + .../stdlib/corpus/logic_or_introl/mengine.me | 3 + .../stdlib/corpus/logic_or_introl/rocq.v | 2 + .../stdlib/corpus/logic_or_intror/mengine.me | 3 + .../stdlib/corpus/logic_or_intror/rocq.v | 2 + benchmarks/stdlib/corpus/manifest.json | 356 +++++ .../stdlib/corpus/nat_add_0_l/mengine.me | 3 + benchmarks/stdlib/corpus/nat_add_0_l/rocq.v | 2 + .../stdlib/corpus/nat_add_2_2/mengine.me | 2 + benchmarks/stdlib/corpus/nat_add_2_2/rocq.v | 2 + .../stdlib/corpus/nat_add_3_4/mengine.me | 2 + benchmarks/stdlib/corpus/nat_add_3_4/rocq.v | 2 + .../stdlib/corpus/nat_mul_2_3/mengine.me | 2 + benchmarks/stdlib/corpus/nat_mul_2_3/rocq.v | 2 + .../stdlib/corpus/nat_sub_5_2/mengine.me | 2 + benchmarks/stdlib/corpus/nat_sub_5_2/rocq.v | 2 + benchmarks/stdlib/plots/stdlib_scatter.png | Bin 0 -> 74904 bytes benchmarks/stdlib/report.py | 133 ++ benchmarks/stdlib/results/REPORT.md | 37 + benchmarks/stdlib/results/stdlib.json | 1406 +++++++++++++++++ benchmarks/stdlib/stdlib_bench.py | 383 +++++ benchmarks/stdlib/translate.py | 880 +++++++++++ 65 files changed, 3670 insertions(+), 1 deletion(-) create mode 100644 benchmarks/stdlib/README.md create mode 100644 benchmarks/stdlib/compat/stdlib_compat.me create mode 100644 benchmarks/stdlib/corpus/bool_andb_b_b/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_andb_b_b/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_andb_false_l/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_andb_false_l/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_andb_true_l/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_andb_true_l/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_andb_true_r/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_andb_true_r/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_negb_false/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_negb_false/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_negb_involutive/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_negb_involutive/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_negb_true/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_negb_true/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_orb_b_b/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_orb_b_b/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_orb_false_l/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_orb_false_l/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_orb_false_r/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_orb_false_r/rocq.v create mode 100644 benchmarks/stdlib/corpus/bool_orb_true_l/mengine.me create mode 100644 benchmarks/stdlib/corpus/bool_orb_true_l/rocq.v create mode 100644 benchmarks/stdlib/corpus/le_0_n/mengine.me create mode 100644 benchmarks/stdlib/corpus/le_0_n/rocq.v create mode 100644 benchmarks/stdlib/corpus/le_1_2/mengine.me create mode 100644 benchmarks/stdlib/corpus/le_1_2/rocq.v create mode 100644 benchmarks/stdlib/corpus/le_2_4/mengine.me create mode 100644 benchmarks/stdlib/corpus/le_2_4/rocq.v create mode 100644 benchmarks/stdlib/corpus/le_refl_n/mengine.me create mode 100644 benchmarks/stdlib/corpus/le_refl_n/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_and_intro/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_and_intro/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_and_intro3/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_and_intro3/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_imp_refl/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_imp_refl/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_imp_trans/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_imp_trans/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_or_introl/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_or_introl/rocq.v create mode 100644 benchmarks/stdlib/corpus/logic_or_intror/mengine.me create mode 100644 benchmarks/stdlib/corpus/logic_or_intror/rocq.v create mode 100644 benchmarks/stdlib/corpus/manifest.json create mode 100644 benchmarks/stdlib/corpus/nat_add_0_l/mengine.me create mode 100644 benchmarks/stdlib/corpus/nat_add_0_l/rocq.v create mode 100644 benchmarks/stdlib/corpus/nat_add_2_2/mengine.me create mode 100644 benchmarks/stdlib/corpus/nat_add_2_2/rocq.v create mode 100644 benchmarks/stdlib/corpus/nat_add_3_4/mengine.me create mode 100644 benchmarks/stdlib/corpus/nat_add_3_4/rocq.v create mode 100644 benchmarks/stdlib/corpus/nat_mul_2_3/mengine.me create mode 100644 benchmarks/stdlib/corpus/nat_mul_2_3/rocq.v create mode 100644 benchmarks/stdlib/corpus/nat_sub_5_2/mengine.me create mode 100644 benchmarks/stdlib/corpus/nat_sub_5_2/rocq.v create mode 100644 benchmarks/stdlib/plots/stdlib_scatter.png create mode 100644 benchmarks/stdlib/report.py create mode 100644 benchmarks/stdlib/results/REPORT.md create mode 100644 benchmarks/stdlib/results/stdlib.json create mode 100644 benchmarks/stdlib/stdlib_bench.py create mode 100644 benchmarks/stdlib/translate.py diff --git a/.gitignore b/.gitignore index e85c843..794b742 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,10 @@ results/ !benchmarks/plots/** !benchmarks/results/ !benchmarks/results/** +!benchmarks/stdlib/plots/ +!benchmarks/stdlib/plots/** +!benchmarks/stdlib/results/ +!benchmarks/stdlib/results/** benchmarks/flame_trends/ # Local profiling and packaging artifacts. diff --git a/benchmarks/README.md b/benchmarks/README.md index c39a80d..134c73f 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -53,3 +53,16 @@ python3 bench.py plot rewrite_nm --fixed m=3 ## Adding a Benchmark Create `benchmarks/my_benchmark.py` with a class extending `Benchmark`. The registry, CLI, and plotter pick it up automatically. + +## Rocq standard-library benchmark + +A separate, fixed-corpus suite comparing MEngine vs Rocq on auto-translated +stdlib lemmas lives under [`stdlib/`](stdlib/README.md). It is driven by its own +runner (it iterates a manifest rather than sweeping a parameter range): + +```bash +python3 stdlib/stdlib_bench.py list # show the corpus +python3 stdlib/stdlib_bench.py test # faithfulness gate +python3 stdlib/stdlib_bench.py run # time both engines +python3 stdlib/stdlib_bench.py report # table + scatter plot +``` diff --git a/benchmarks/config.json b/benchmarks/config.json index 8221494..e35ac01 100644 --- a/benchmarks/config.json +++ b/benchmarks/config.json @@ -11,6 +11,15 @@ "max_consecutive_failures": 2, "trials": 2, "coq_timeout_multiplier": 1.5, + "stdlib": { + "corpus_dir": "stdlib/corpus", + "compat": "stdlib/compat/stdlib_compat.me", + "results": "stdlib/results/stdlib.json", + "plots_dir": "stdlib/plots", + "timeout": 20, + "trials": 5, + "rocq_stdlib_src": "~/.opam/default/lib/coq/theories" + }, "mengine_variants": { "baseline": { "path": "~/mengine/build/ablations/mengine-baseline", diff --git a/benchmarks/stdlib/PLAN.md b/benchmarks/stdlib/PLAN.md index f343ce8..2a16ebc 100644 --- a/benchmarks/stdlib/PLAN.md +++ b/benchmarks/stdlib/PLAN.md @@ -1,6 +1,13 @@ # Plan: Rocq Standard Library Benchmark Suite for MEngine -Status: plan (not yet implemented). Scope decisions locked with the user: +Status: **implemented** (Tier A). See `README.md` for the operator's guide and +the realized scope; the per-proof mode (phase 6) remains optional/future. The +sections below are the original design; where the implementation refined a +decision (e.g. corpus drawn from curated single-lemma units rather than whole +stdlib files, two targeted kernel fixes landed, the symbolic-fixpoint induction +crash deferred), `README.md` is authoritative. + +Scope decisions locked with the user: - **Granularity:** per-file first; per-proof is a later optional phase. - **Corpus scope:** Tier A only — files the translator handles with **zero manual edits**. Tier B (manual/aligned) and Tier C (out of scope) are documented but not built initially. diff --git a/benchmarks/stdlib/README.md b/benchmarks/stdlib/README.md new file mode 100644 index 0000000..601872b --- /dev/null +++ b/benchmarks/stdlib/README.md @@ -0,0 +1,117 @@ +# Rocq standard-library benchmark for MEngine + +A per-unit benchmark comparing **MEngine** against **Rocq** on a curated, +auto-translated subset of the Rocq standard library. See [`PLAN.md`](PLAN.md) +for the full design and rationale; this README is the operator's guide. + +## What it measures + +Each *unit* is one stdlib-style lemma in two forms: + +- `corpus//rocq.v` — the Rocq source (compiles against the installed + stdlib; `Coq.Init` is auto-loaded, so most units need no `Require`). +- `corpus//mengine.me` — the MEngine source, produced **mechanically** by + [`translate.py`](translate.py) with zero manual edits (Tier A). + +`stdlib_bench.py run` times each unit end-to-end in both engines (whole process, +best of N trials). Both engines pay their own fixed startup: MEngine loads +`prelude/tactics.me` plus the compat prelude; Rocq starts its process and loads +`Coq.Init`. At this problem size the comparison reflects per-invocation cost, +not asymptotics. + +## Layout + +``` +benchmarks/stdlib/ + PLAN.md design document + README.md this file + translate.py Rocq .v -> MEngine .me translator (+ --report triage) + stdlib_bench.py corpus runner: list / test / run / report / manifest / triage + report.py markdown table + log-log scatter plot + compat/stdlib_compat.me compat prelude: nat/bool/list/option + le + emulated tactics + corpus/ + manifest.json locked Tier-A set + per-statement digests + excluded boundary + /rocq.v benchmarked Rocq source + /mengine.me auto-translated MEngine source + results/stdlib.json per-unit timings + results/REPORT.md generated table + geometric-mean summary + plots/stdlib_scatter.png generated scatter (Rocq x vs MEngine y, log-log) +``` + +## Usage + +```bash +cd benchmarks +python3 stdlib/stdlib_bench.py list # show the corpus by category +python3 stdlib/stdlib_bench.py test # faithfulness gate (see below) +python3 stdlib/stdlib_bench.py run # time both engines, write results +python3 stdlib/stdlib_bench.py report # regenerate REPORT.md + scatter plot +python3 stdlib/stdlib_bench.py manifest # regenerate corpus/manifest.json +python3 stdlib/stdlib_bench.py triage # translate.py --report over the stdlib +``` + +Run a single unit: `… run le_0_n`, `… test bool_negb_involutive`. + +## Faithfulness (`test`) + +Before any timing, `test` verifies per unit (plan §7): + +1. `rocq.v` compiles under `coqc`. +2. `mengine.me` runs clean under `mengine -q` (compat prelude prepended). +3. `mengine.me` is exactly what `translate.py` re-emits from `rocq.v` (no drift), + and the **theorem names match** between the two sides. + +The guiding principle of the translator is **flag, never guess**: any construct +it cannot translate soundly is reported and the unit is excluded, rather than +mistranslated. A wrong translation that happened to compile would silently +benchmark two *different* theorems — the worst failure — so the translator +refuses instead. + +## Scope (Tier A) and the engine boundary + +Tier A is the computational/structural corner reachable today by MEngine + the +compat prelude: + +- **Bool** — identities by ground reduction and by case analysis (`destruct`). +- **Nat (ground)** — closed `add`/`mul`/`sub` computations and reductions like + `0 + n = n`. +- **`le` / order** — structural induction with a fixpoint-free motive, and + constructor chains. +- **Logic** — propositional introduction (`split`/`left`/`right`/`apply`). + +Two engine fixes on this branch enable the computational part (both are pure, +soundness-preserving kernel changes; all 431 kernel tests still pass): + +1. **`cbv` reduces applied fixpoints** (`src/kernel/normalize.c`). `_normalize_cbv` + now unfolds `(fix …) arg` like `normalize_whnf` already did, so `cbv`/`Eval` + actually compute (e.g. `add (S O) O` → `S O`). +2. **GC shutdown no longer double-frees shared match branches** + (`src/kernel/expression.c`). `MatchBranch` arrays are shared by pointer + across arena nodes (normalize/conversion rebuild a match with a new scrutinee + but reuse its branches); shutdown now frees each exactly once. This removes a + crash that fired whenever a computational eliminator was type-checked + (e.g. `destruct b` on `negb (negb b) = b`). + +What stays **out of Tier A** (documented in `corpus/manifest.json` → `excluded`): + +- **Computational induction over a `Fixpoint`** (`add n 0 = n`, `n + 0 = n`): + needs symbolic-argument fixpoint conversion, which hits a pre-existing kernel + stack-overflow (`bugs/segfault_apply_fixpoint_motive.me`, + `bugs/segfault_exact_eliminator_fixpoint.me`). This is the highest-leverage + remaining expansion (plan §5a, §11) and is genuinely hard kernel work. +- **`destruct`/`case` with a constant RHS** (`andb b false = false`): MEngine's + first-order `apply` cannot solve the eliminator's scrutinee evar when the + scrutinee variable has no *bare* occurrence in the goal, because the unifier + whnf-folds the function application into a stuck `match`. +- **Polymorphic list reasoning** (`app`/`length` lemmas): needs element-type + inference in the translator (Tier B). + +## Why whole stdlib files don't translate + +`stdlib_bench.py triage` runs the translator over the installed stdlib and +reports, per file, the first blocking construct. Real files are saturated with +`Notation`/`Ltac`/`Variant`/`Register`/multi-scrutinee `match`/qualified names, +so essentially none translate as a whole file (≈1/16 even in `Coq.Init`). This +is exactly the feasibility verdict in `PLAN.md §1`, and the reason the corpus is +built from curated, single-lemma units drawn from stdlib content rather than from +whole files. diff --git a/benchmarks/stdlib/compat/stdlib_compat.me b/benchmarks/stdlib/compat/stdlib_compat.me new file mode 100644 index 0000000..d056709 --- /dev/null +++ b/benchmarks/stdlib/compat/stdlib_compat.me @@ -0,0 +1,147 @@ +(* stdlib_compat.me — compatibility prelude for the Rocq-stdlib benchmark. + + Loaded ahead of every translated unit (the translator prepends it). It + supplies the base datatypes and functions that the Rocq standard library + assumes but that MEngine's C core (src/runtime/core.c) does not provide + (core.c only defines eq/and/or/ex/Reflexive), plus a handful of emulated + tactics written in the tactic language. + + Each datatype/function mirrors the *semantics* of its Rocq counterpart so + that a translated theorem statement denotes the same proposition and ground + computation reduces identically. Function bodies use single-scrutinee + matches (MEngine has no multi-scrutinee match); this is extensionally equal + to Rocq's definitions, which is all the benchmark requires. + + Every emulated tactic carries a comment stating exactly how it differs from + Rocq's, so divergence is auditable in one place. *) + +(* ── Base datatypes ─────────────────────────────────────────────────── *) + +Inductive True : Prop := | I : True. + +Inductive bool : Type := | true : bool | false : bool. + +Inductive nat : Type := +| O : nat +| S : forall (n : nat), nat. + +Inductive list : forall (A : Type), Type := +| nil : forall (A : Type), list A +| cons : forall (A : Type), forall (x : A), forall (l : list A), list A. + +Inductive option : forall (A : Type), Type := +| None : forall (A : Type), option A +| Some : forall (A : Type), forall (x : A), option A. + +(* ── bool functions (Coq.Init.Datatypes) ───────────────────────────── *) + +Definition negb : forall (_ : bool), bool := + fun (b : bool) => match b with | true => false | false => true end. + +Definition andb : forall (_ : bool), forall (_ : bool), bool := + fun (b1 : bool) => fun (b2 : bool) => match b1 with | true => b2 | false => false end. + +Definition orb : forall (_ : bool), forall (_ : bool), bool := + fun (b1 : bool) => fun (b2 : bool) => match b1 with | true => true | false => b2 end. + +Definition implb : forall (_ : bool), forall (_ : bool), bool := + fun (b1 : bool) => fun (b2 : bool) => match b1 with | true => b2 | false => true end. + +Definition xorb : forall (_ : bool), forall (_ : bool), bool := + fun (b1 : bool) => fun (b2 : bool) => + match b1 with + | true => match b2 with | true => false | false => true end + | false => b2 + end. + +(* ── nat functions (Coq.Init.Nat) ──────────────────────────────────── *) + +Fixpoint add (n : nat) (m : nat) {struct n} : nat := + match n with | O => m | S p => S (add p m) end. + +Fixpoint mul (n : nat) (m : nat) {struct n} : nat := + match n with | O => O | S p => add m (mul p m) end. + +(* sub: Rocq matches on both n and m; here nested single-scrutinee matches. *) +Fixpoint sub (n : nat) (m : nat) {struct n} : nat := + match n with + | O => O + | S k => match m with | O => n | S l => sub k l end + end. + +Fixpoint eqb (n : nat) (m : nat) {struct n} : bool := + match n with + | O => match m with | O => true | S q => false end + | S p => match m with | O => false | S q => eqb p q end + end. + +Fixpoint leb (n : nat) (m : nat) {struct n} : bool := + match n with + | O => true + | S p => match m with | O => false | S q => leb p q end + end. + +Definition ltb : forall (_ : nat), forall (_ : nat), bool := + fun (n : nat) => fun (m : nat) => leb (S n) m. + +(* ── list functions (Coq.Lists.List / Coq.Init.Datatypes) ──────────── *) + +Fixpoint app (A : Type) (l : list A) (k : list A) {struct l} : list A := + match l with + | nil _ => k + | cons _ x xs => cons A x (app A xs k) + end. + +Fixpoint length (A : Type) (l : list A) {struct l} : nat := + match l with + | nil _ => O + | cons _ x xs => S (length A xs) + end. + +(* ── Relational predicates (Coq.Init.Peano / Coq.Init.Datatypes) ───── *) + +(* le, as in Coq.Init.Peano: le n n and le n m -> le n (S m). *) +Inductive le : forall (_ : nat), forall (_ : nat), Prop := +| le_n : forall (n : nat), le n n +| le_S : forall (n : nat), forall (m : nat), forall (_ : le n m), le n (S m). + +(* ── Emulated tactics ──────────────────────────────────────────────── *) + +(* reflexivity (override): build `eq_refl A x` for a goal `eq A x y` and let + the kernel's conversion checker verify `x` converts to `y`. Differs from + the prelude's reflexivity, which matches `eq ?A ?x ?x` purely syntactically + (pointer equality) and so cannot close a goal whose two sides are equal only + up to computation, e.g. `eq nat (add (S O) O) (S O)`. Like Rocq's + `reflexivity`, it fails (cleanly) when the sides are not convertible. *) +Tactic reflexivity := match Goal with +| [ |- (((eq ?A) ?x) ?y) ] => exact ((eq_refl A) x) +end. + +(* symmetry := apply eq_sym. eq_sym : forall A x y, eq A x y -> eq A y x, so + applying it to `eq A y x` leaves the subgoal `eq A x y`. Identical to Rocq. *) +Tactic symmetry := apply eq_sym. + +(* simpl := cbv (full beta/delta/iota/fix normalization). Rocq's simpl is a + heuristic partial reduction that avoids unfolding some applications; cbv is + strictly more aggressive but agrees with simpl whenever simpl makes progress + on the closed/structural goals in this corpus. *) +Tactic simpl := cbv. + +(* trivial / easy / now : close trivial goals. Rocq's are stronger (trivial + runs a hint database; easy/now also discharge by `congruence`/`lia`-free + automation). Here: try reflexivity, then try assumption. *) +Tactic trivial := try reflexivity; try assumption. +Tactic easy := intros; try reflexivity; try assumption. +Tactic now := intros; try reflexivity; try assumption. + +(* constructor (sec. 6a): generalization of split/left/right. One arm per + inductive whose constructors this corpus uses. `first` tries them in + declaration order, exactly like Rocq's `constructor`. Each arm builds a + full proof term, so this stays sound under flag-never-guess. Witness-guessing + (constructor on `ex`, i.e. eexists) is intentionally excluded. *) +Tactic constructor := match Goal with +| [ |- True ] => exact I +| [ |- ((and ?A) ?B) ] => eapply ((conj A) B) +| [ |- ((or ?A) ?B) ] => first [ eapply ((or_introl A) B) | eapply ((or_intror A) B) ] +| [ |- ((le ?n) ?m) ] => first [ eapply (le_n n) | eapply (le_S n) ] +end. diff --git a/benchmarks/stdlib/corpus/bool_andb_b_b/mengine.me b/benchmarks/stdlib/corpus/bool_andb_b_b/mengine.me new file mode 100644 index 0000000..2f26245 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_b_b/mengine.me @@ -0,0 +1,7 @@ +Theorem andb_b_b : forall (b : bool), (((eq bool) ((andb b) b)) b). +intro b. +apply (bool_ind (fun (b : bool) => (((eq bool) ((andb b) b)) b))). +simpl. +reflexivity. +simpl. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_andb_b_b/rocq.v b/benchmarks/stdlib/corpus/bool_andb_b_b/rocq.v new file mode 100644 index 0000000..ab84b4b --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_b_b/rocq.v @@ -0,0 +1,5 @@ +Lemma andb_b_b : forall b : bool, andb b b = b. +Proof. destruct b. + - reflexivity. + - reflexivity. +Qed. diff --git a/benchmarks/stdlib/corpus/bool_andb_false_l/mengine.me b/benchmarks/stdlib/corpus/bool_andb_false_l/mengine.me new file mode 100644 index 0000000..c8ca6bb --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_false_l/mengine.me @@ -0,0 +1,3 @@ +Theorem andb_false_l : forall (b : bool), (((eq bool) ((andb false) b)) false). +intro b. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_andb_false_l/rocq.v b/benchmarks/stdlib/corpus/bool_andb_false_l/rocq.v new file mode 100644 index 0000000..5dc40af --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_false_l/rocq.v @@ -0,0 +1,2 @@ +Lemma andb_false_l : forall b : bool, andb false b = false. +Proof. intro b. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/bool_andb_true_l/mengine.me b/benchmarks/stdlib/corpus/bool_andb_true_l/mengine.me new file mode 100644 index 0000000..a5bf85d --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_true_l/mengine.me @@ -0,0 +1,3 @@ +Theorem andb_true_l : forall (b : bool), (((eq bool) ((andb true) b)) b). +intro b. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_andb_true_l/rocq.v b/benchmarks/stdlib/corpus/bool_andb_true_l/rocq.v new file mode 100644 index 0000000..e8d89f0 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_true_l/rocq.v @@ -0,0 +1,2 @@ +Lemma andb_true_l : forall b : bool, andb true b = b. +Proof. intro b. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/bool_andb_true_r/mengine.me b/benchmarks/stdlib/corpus/bool_andb_true_r/mengine.me new file mode 100644 index 0000000..bcc9805 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_true_r/mengine.me @@ -0,0 +1,7 @@ +Theorem andb_true_r : forall (b : bool), (((eq bool) ((andb b) true)) b). +intro b. +apply (bool_ind (fun (b : bool) => (((eq bool) ((andb b) true)) b))). +simpl. +reflexivity. +simpl. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_andb_true_r/rocq.v b/benchmarks/stdlib/corpus/bool_andb_true_r/rocq.v new file mode 100644 index 0000000..23c73d0 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_andb_true_r/rocq.v @@ -0,0 +1,5 @@ +Lemma andb_true_r : forall b : bool, andb b true = b. +Proof. destruct b. + - reflexivity. + - reflexivity. +Qed. diff --git a/benchmarks/stdlib/corpus/bool_negb_false/mengine.me b/benchmarks/stdlib/corpus/bool_negb_false/mengine.me new file mode 100644 index 0000000..d107deb --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_false/mengine.me @@ -0,0 +1,2 @@ +Theorem negb_false : (((eq bool) (negb false)) true). +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_negb_false/rocq.v b/benchmarks/stdlib/corpus/bool_negb_false/rocq.v new file mode 100644 index 0000000..784bd29 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_false/rocq.v @@ -0,0 +1,2 @@ +Example negb_false : negb false = true. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/bool_negb_involutive/mengine.me b/benchmarks/stdlib/corpus/bool_negb_involutive/mengine.me new file mode 100644 index 0000000..060e275 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_involutive/mengine.me @@ -0,0 +1,7 @@ +Theorem negb_involutive : forall (b : bool), (((eq bool) (negb (negb b))) b). +intro b. +apply (bool_ind (fun (b : bool) => (((eq bool) (negb (negb b))) b))). +simpl. +reflexivity. +simpl. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_negb_involutive/rocq.v b/benchmarks/stdlib/corpus/bool_negb_involutive/rocq.v new file mode 100644 index 0000000..c596a04 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_involutive/rocq.v @@ -0,0 +1,5 @@ +Lemma negb_involutive : forall b : bool, negb (negb b) = b. +Proof. destruct b. + - reflexivity. + - reflexivity. +Qed. diff --git a/benchmarks/stdlib/corpus/bool_negb_true/mengine.me b/benchmarks/stdlib/corpus/bool_negb_true/mengine.me new file mode 100644 index 0000000..b5cfee7 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_true/mengine.me @@ -0,0 +1,2 @@ +Theorem negb_true : (((eq bool) (negb true)) false). +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_negb_true/rocq.v b/benchmarks/stdlib/corpus/bool_negb_true/rocq.v new file mode 100644 index 0000000..76480bb --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_negb_true/rocq.v @@ -0,0 +1,2 @@ +Example negb_true : negb true = false. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/bool_orb_b_b/mengine.me b/benchmarks/stdlib/corpus/bool_orb_b_b/mengine.me new file mode 100644 index 0000000..d65a985 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_b_b/mengine.me @@ -0,0 +1,7 @@ +Theorem orb_b_b : forall (b : bool), (((eq bool) ((orb b) b)) b). +intro b. +apply (bool_ind (fun (b : bool) => (((eq bool) ((orb b) b)) b))). +simpl. +reflexivity. +simpl. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_orb_b_b/rocq.v b/benchmarks/stdlib/corpus/bool_orb_b_b/rocq.v new file mode 100644 index 0000000..cf8e796 --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_b_b/rocq.v @@ -0,0 +1,5 @@ +Lemma orb_b_b : forall b : bool, orb b b = b. +Proof. destruct b. + - reflexivity. + - reflexivity. +Qed. diff --git a/benchmarks/stdlib/corpus/bool_orb_false_l/mengine.me b/benchmarks/stdlib/corpus/bool_orb_false_l/mengine.me new file mode 100644 index 0000000..d234cbd --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_false_l/mengine.me @@ -0,0 +1,3 @@ +Theorem orb_false_l : forall (b : bool), (((eq bool) ((orb false) b)) b). +intro b. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_orb_false_l/rocq.v b/benchmarks/stdlib/corpus/bool_orb_false_l/rocq.v new file mode 100644 index 0000000..2a7c20b --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_false_l/rocq.v @@ -0,0 +1,2 @@ +Lemma orb_false_l : forall b : bool, orb false b = b. +Proof. intro b. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/bool_orb_false_r/mengine.me b/benchmarks/stdlib/corpus/bool_orb_false_r/mengine.me new file mode 100644 index 0000000..6ff8efe --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_false_r/mengine.me @@ -0,0 +1,7 @@ +Theorem orb_false_r : forall (b : bool), (((eq bool) ((orb b) false)) b). +intro b. +apply (bool_ind (fun (b : bool) => (((eq bool) ((orb b) false)) b))). +simpl. +reflexivity. +simpl. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_orb_false_r/rocq.v b/benchmarks/stdlib/corpus/bool_orb_false_r/rocq.v new file mode 100644 index 0000000..a8819fa --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_false_r/rocq.v @@ -0,0 +1,5 @@ +Lemma orb_false_r : forall b : bool, orb b false = b. +Proof. destruct b. + - reflexivity. + - reflexivity. +Qed. diff --git a/benchmarks/stdlib/corpus/bool_orb_true_l/mengine.me b/benchmarks/stdlib/corpus/bool_orb_true_l/mengine.me new file mode 100644 index 0000000..b2dd98b --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_true_l/mengine.me @@ -0,0 +1,3 @@ +Theorem orb_true_l : forall (b : bool), (((eq bool) ((orb true) b)) true). +intro b. +reflexivity. diff --git a/benchmarks/stdlib/corpus/bool_orb_true_l/rocq.v b/benchmarks/stdlib/corpus/bool_orb_true_l/rocq.v new file mode 100644 index 0000000..96ae76a --- /dev/null +++ b/benchmarks/stdlib/corpus/bool_orb_true_l/rocq.v @@ -0,0 +1,2 @@ +Lemma orb_true_l : forall b : bool, orb true b = true. +Proof. intro b. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/le_0_n/mengine.me b/benchmarks/stdlib/corpus/le_0_n/mengine.me new file mode 100644 index 0000000..f929bc9 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_0_n/mengine.me @@ -0,0 +1,10 @@ +Theorem le_0_n : forall (n : nat), ((le O) n). +intro n. +apply (nat_ind (fun (n : nat) => ((le O) n))). +simpl. +constructor. +simpl. +intro n. +intro IHn. +constructor. +exact (IHn). diff --git a/benchmarks/stdlib/corpus/le_0_n/rocq.v b/benchmarks/stdlib/corpus/le_0_n/rocq.v new file mode 100644 index 0000000..45353e6 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_0_n/rocq.v @@ -0,0 +1,5 @@ +Lemma le_0_n : forall n : nat, 0 <= n. +Proof. induction n. + - constructor. + - constructor. exact IHn. +Qed. diff --git a/benchmarks/stdlib/corpus/le_1_2/mengine.me b/benchmarks/stdlib/corpus/le_1_2/mengine.me new file mode 100644 index 0000000..faa5dd2 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_1_2/mengine.me @@ -0,0 +1,3 @@ +Theorem le_1_2 : ((le (S O)) (S (S O))). +constructor. +constructor. diff --git a/benchmarks/stdlib/corpus/le_1_2/rocq.v b/benchmarks/stdlib/corpus/le_1_2/rocq.v new file mode 100644 index 0000000..cef43f3 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_1_2/rocq.v @@ -0,0 +1,2 @@ +Lemma le_1_2 : 1 <= 2. +Proof. constructor. constructor. Qed. diff --git a/benchmarks/stdlib/corpus/le_2_4/mengine.me b/benchmarks/stdlib/corpus/le_2_4/mengine.me new file mode 100644 index 0000000..df2603e --- /dev/null +++ b/benchmarks/stdlib/corpus/le_2_4/mengine.me @@ -0,0 +1,4 @@ +Theorem le_2_4 : ((le (S (S O))) (S (S (S (S O))))). +constructor. +constructor. +constructor. diff --git a/benchmarks/stdlib/corpus/le_2_4/rocq.v b/benchmarks/stdlib/corpus/le_2_4/rocq.v new file mode 100644 index 0000000..11f5642 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_2_4/rocq.v @@ -0,0 +1,2 @@ +Lemma le_2_4 : 2 <= 4. +Proof. constructor. constructor. constructor. Qed. diff --git a/benchmarks/stdlib/corpus/le_refl_n/mengine.me b/benchmarks/stdlib/corpus/le_refl_n/mengine.me new file mode 100644 index 0000000..d60ae19 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_refl_n/mengine.me @@ -0,0 +1,3 @@ +Theorem le_refl_n : forall (n : nat), ((le n) n). +intro n. +constructor. diff --git a/benchmarks/stdlib/corpus/le_refl_n/rocq.v b/benchmarks/stdlib/corpus/le_refl_n/rocq.v new file mode 100644 index 0000000..0b18d66 --- /dev/null +++ b/benchmarks/stdlib/corpus/le_refl_n/rocq.v @@ -0,0 +1,2 @@ +Lemma le_refl_n : forall n : nat, n <= n. +Proof. intro n. constructor. Qed. diff --git a/benchmarks/stdlib/corpus/logic_and_intro/mengine.me b/benchmarks/stdlib/corpus/logic_and_intro/mengine.me new file mode 100644 index 0000000..2dcb88c --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_and_intro/mengine.me @@ -0,0 +1,3 @@ +Theorem and_intro : forall (A : Prop), forall (B : Prop), forall (_ : A), forall (_ : B), ((and A) B). +intro A. intro B. intro HA. intro HB. +split; assumption. diff --git a/benchmarks/stdlib/corpus/logic_and_intro/rocq.v b/benchmarks/stdlib/corpus/logic_and_intro/rocq.v new file mode 100644 index 0000000..d2d1e23 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_and_intro/rocq.v @@ -0,0 +1,2 @@ +Lemma and_intro : forall A B : Prop, A -> B -> A /\ B. +Proof. intros A B HA HB. split; assumption. Qed. diff --git a/benchmarks/stdlib/corpus/logic_and_intro3/mengine.me b/benchmarks/stdlib/corpus/logic_and_intro3/mengine.me new file mode 100644 index 0000000..42977f7 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_and_intro3/mengine.me @@ -0,0 +1,7 @@ +Theorem and_intro3 : forall (A : Prop), forall (B : Prop), forall (C : Prop), forall (_ : A), forall (_ : B), forall (_ : C), ((and A) ((and B) C)). +intro A. intro B. intro C. intro HA. intro HB. intro HC. +split. +exact (HA). +split. +exact (HB). +exact (HC). diff --git a/benchmarks/stdlib/corpus/logic_and_intro3/rocq.v b/benchmarks/stdlib/corpus/logic_and_intro3/rocq.v new file mode 100644 index 0000000..eeec6d2 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_and_intro3/rocq.v @@ -0,0 +1,2 @@ +Lemma and_intro3 : forall A B C : Prop, A -> B -> C -> A /\ (B /\ C). +Proof. intros A B C HA HB HC. split. exact HA. split. exact HB. exact HC. Qed. diff --git a/benchmarks/stdlib/corpus/logic_imp_refl/mengine.me b/benchmarks/stdlib/corpus/logic_imp_refl/mengine.me new file mode 100644 index 0000000..7f632bc --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_imp_refl/mengine.me @@ -0,0 +1,4 @@ +Theorem imp_refl : forall (A : Prop), forall (_ : A), A. +intro A. +intro H. +exact (H). diff --git a/benchmarks/stdlib/corpus/logic_imp_refl/rocq.v b/benchmarks/stdlib/corpus/logic_imp_refl/rocq.v new file mode 100644 index 0000000..1c1d53a --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_imp_refl/rocq.v @@ -0,0 +1,2 @@ +Lemma imp_refl : forall A : Prop, A -> A. +Proof. intro A. intro H. exact H. Qed. diff --git a/benchmarks/stdlib/corpus/logic_imp_trans/mengine.me b/benchmarks/stdlib/corpus/logic_imp_trans/mengine.me new file mode 100644 index 0000000..c4addc1 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_imp_trans/mengine.me @@ -0,0 +1,5 @@ +Theorem imp_trans : forall (A : Prop), forall (B : Prop), forall (C : Prop), forall (_ : forall (_ : A), B), forall (_ : forall (_ : B), C), forall (_ : A), C. +intro A. intro B. intro C. intro HAB. intro HBC. intro HA. +apply (HBC). +apply (HAB). +exact (HA). diff --git a/benchmarks/stdlib/corpus/logic_imp_trans/rocq.v b/benchmarks/stdlib/corpus/logic_imp_trans/rocq.v new file mode 100644 index 0000000..d7bdeab --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_imp_trans/rocq.v @@ -0,0 +1,2 @@ +Lemma imp_trans : forall A B C : Prop, (A -> B) -> (B -> C) -> A -> C. +Proof. intros A B C HAB HBC HA. apply HBC. apply HAB. exact HA. Qed. diff --git a/benchmarks/stdlib/corpus/logic_or_introl/mengine.me b/benchmarks/stdlib/corpus/logic_or_introl/mengine.me new file mode 100644 index 0000000..4e8477d --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_or_introl/mengine.me @@ -0,0 +1,3 @@ +Theorem or_introl_ex : forall (A : Prop), forall (B : Prop), forall (_ : A), ((or A) B). +intro A. intro B. intro HA. +left; assumption. diff --git a/benchmarks/stdlib/corpus/logic_or_introl/rocq.v b/benchmarks/stdlib/corpus/logic_or_introl/rocq.v new file mode 100644 index 0000000..1e9fc72 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_or_introl/rocq.v @@ -0,0 +1,2 @@ +Lemma or_introl_ex : forall A B : Prop, A -> A \/ B. +Proof. intros A B HA. left; assumption. Qed. diff --git a/benchmarks/stdlib/corpus/logic_or_intror/mengine.me b/benchmarks/stdlib/corpus/logic_or_intror/mengine.me new file mode 100644 index 0000000..ff138d6 --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_or_intror/mengine.me @@ -0,0 +1,3 @@ +Theorem or_intror_ex : forall (A : Prop), forall (B : Prop), forall (_ : B), ((or A) B). +intro A. intro B. intro HB. +right; assumption. diff --git a/benchmarks/stdlib/corpus/logic_or_intror/rocq.v b/benchmarks/stdlib/corpus/logic_or_intror/rocq.v new file mode 100644 index 0000000..ccbad8a --- /dev/null +++ b/benchmarks/stdlib/corpus/logic_or_intror/rocq.v @@ -0,0 +1,2 @@ +Lemma or_intror_ex : forall A B : Prop, B -> A \/ B. +Proof. intros A B HB. right; assumption. Qed. diff --git a/benchmarks/stdlib/corpus/manifest.json b/benchmarks/stdlib/corpus/manifest.json new file mode 100644 index 0000000..c076290 --- /dev/null +++ b/benchmarks/stdlib/corpus/manifest.json @@ -0,0 +1,356 @@ +{ + "description": "Tier-A: Rocq stdlib units auto-translated with zero manual edits and proved by MEngine + the compat prelude.", + "engine_note": "Requires the cbv applied-fix and GC-dedup kernel fixes (see benchmarks/stdlib/README.md).", + "units": [ + { + "name": "bool_andb_b_b", + "tier": "A", + "category": "bool", + "rocq_sha256": "bb6fd4757e68f40f", + "statements": [ + { + "name": "andb_b_b", + "digest": "forall (b : bool), (((eq bool) ((andb b) b)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_andb_false_l", + "tier": "A", + "category": "bool", + "rocq_sha256": "b576ea7c73624fa1", + "statements": [ + { + "name": "andb_false_l", + "digest": "forall (b : bool), (((eq bool) ((andb false) b)) false)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_andb_true_l", + "tier": "A", + "category": "bool", + "rocq_sha256": "e6759cbfe1607bfb", + "statements": [ + { + "name": "andb_true_l", + "digest": "forall (b : bool), (((eq bool) ((andb true) b)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_andb_true_r", + "tier": "A", + "category": "bool", + "rocq_sha256": "d7645d3fc67c777d", + "statements": [ + { + "name": "andb_true_r", + "digest": "forall (b : bool), (((eq bool) ((andb b) true)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_negb_false", + "tier": "A", + "category": "bool", + "rocq_sha256": "09f84fafa77d9395", + "statements": [ + { + "name": "negb_false", + "digest": "(((eq bool) (negb false)) true)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_negb_involutive", + "tier": "A", + "category": "bool", + "rocq_sha256": "8e276bfb5c9c03c9", + "statements": [ + { + "name": "negb_involutive", + "digest": "forall (b : bool), (((eq bool) (negb (negb b))) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_negb_true", + "tier": "A", + "category": "bool", + "rocq_sha256": "51354009e04ddc4d", + "statements": [ + { + "name": "negb_true", + "digest": "(((eq bool) (negb true)) false)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_orb_b_b", + "tier": "A", + "category": "bool", + "rocq_sha256": "2a38ffca93cbbfe9", + "statements": [ + { + "name": "orb_b_b", + "digest": "forall (b : bool), (((eq bool) ((orb b) b)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_orb_false_l", + "tier": "A", + "category": "bool", + "rocq_sha256": "ce76fae07e4ad729", + "statements": [ + { + "name": "orb_false_l", + "digest": "forall (b : bool), (((eq bool) ((orb false) b)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_orb_false_r", + "tier": "A", + "category": "bool", + "rocq_sha256": "4278dd784a67cf5c", + "statements": [ + { + "name": "orb_false_r", + "digest": "forall (b : bool), (((eq bool) ((orb b) false)) b)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "bool_orb_true_l", + "tier": "A", + "category": "bool", + "rocq_sha256": "edfc7caf147b57c7", + "statements": [ + { + "name": "orb_true_l", + "digest": "forall (b : bool), (((eq bool) ((orb true) b)) true)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "le_0_n", + "tier": "A", + "category": "le", + "rocq_sha256": "29e8d73394141b57", + "statements": [ + { + "name": "le_0_n", + "digest": "forall (n : nat), ((le O) n)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "le_1_2", + "tier": "A", + "category": "le", + "rocq_sha256": "48dc552042d6d716", + "statements": [ + { + "name": "le_1_2", + "digest": "((le (S O)) (S (S O)))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "le_2_4", + "tier": "A", + "category": "le", + "rocq_sha256": "5fbd2e68c028de33", + "statements": [ + { + "name": "le_2_4", + "digest": "((le (S (S O))) (S (S (S (S O)))))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "le_refl_n", + "tier": "A", + "category": "le", + "rocq_sha256": "d575fd73054c51aa", + "statements": [ + { + "name": "le_refl_n", + "digest": "forall (n : nat), ((le n) n)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_and_intro", + "tier": "A", + "category": "logic", + "rocq_sha256": "eae706b5389b59e9", + "statements": [ + { + "name": "and_intro", + "digest": "forall (A : Prop), forall (B : Prop), forall (_ : A), forall (_ : B), ((and A) B)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_and_intro3", + "tier": "A", + "category": "logic", + "rocq_sha256": "2a9cea4035ecf0c2", + "statements": [ + { + "name": "and_intro3", + "digest": "forall (A : Prop), forall (B : Prop), forall (C : Prop), forall (_ : A), forall (_ : B), forall (_ : C), ((and A) ((and B) C))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_imp_refl", + "tier": "A", + "category": "logic", + "rocq_sha256": "c6106300d85b26c5", + "statements": [ + { + "name": "imp_refl", + "digest": "forall (A : Prop), forall (_ : A), A" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_imp_trans", + "tier": "A", + "category": "logic", + "rocq_sha256": "a929b5af77369b8d", + "statements": [ + { + "name": "imp_trans", + "digest": "forall (A : Prop), forall (B : Prop), forall (C : Prop), forall (_ : forall (_ : A), B), forall (_ : forall (_ : B), C), forall (_ : A), C" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_or_introl", + "tier": "A", + "category": "logic", + "rocq_sha256": "2929c04519ab5518", + "statements": [ + { + "name": "or_introl_ex", + "digest": "forall (A : Prop), forall (B : Prop), forall (_ : A), ((or A) B)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "logic_or_intror", + "tier": "A", + "category": "logic", + "rocq_sha256": "0d0f413a63402857", + "statements": [ + { + "name": "or_intror_ex", + "digest": "forall (A : Prop), forall (B : Prop), forall (_ : B), ((or A) B)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "nat_add_0_l", + "tier": "A", + "category": "nat_ground", + "rocq_sha256": "e2ce1d82d2849368", + "statements": [ + { + "name": "add_0_l", + "digest": "forall (n : nat), (((eq nat) ((add O) n)) n)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "nat_add_2_2", + "tier": "A", + "category": "nat_ground", + "rocq_sha256": "4cee85a5832ae338", + "statements": [ + { + "name": "add_2_2", + "digest": "(((eq nat) ((add (S (S O))) (S (S O)))) (S (S (S (S O)))))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "nat_add_3_4", + "tier": "A", + "category": "nat_ground", + "rocq_sha256": "690d9c4cb2067639", + "statements": [ + { + "name": "add_3_4", + "digest": "(((eq nat) ((add (S (S (S O)))) (S (S (S (S O)))))) (S (S (S (S (S (S (S O))))))))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "nat_mul_2_3", + "tier": "A", + "category": "nat_ground", + "rocq_sha256": "14247e3010ce343c", + "statements": [ + { + "name": "mul_2_3", + "digest": "(((eq nat) ((mul (S (S O))) (S (S (S O))))) (S (S (S (S (S (S O)))))))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "nat_sub_5_2", + "tier": "A", + "category": "nat_ground", + "rocq_sha256": "8797257b18b9858e", + "statements": [ + { + "name": "sub_5_2", + "digest": "(((eq nat) ((sub (S (S (S (S (S O)))))) (S (S O)))) (S (S (S O))))" + } + ], + "deps": "Coq.Init (auto-loaded)" + } + ], + "excluded": [ + { + "pattern": "destruct/case-analysis with a constant RHS (e.g. andb b false = false)", + "boundary": "tier2-unify", + "reason": "apply cannot solve the eliminator's scrutinee evar when the scrutinee variable has no bare occurrence in the goal; the unifier whnf-folds the function application into a stuck match." + }, + { + "pattern": "computational induction over a Fixpoint (e.g. add n 0 = n, n + 0 = n)", + "boundary": "tier2-symbolic", + "reason": "needs symbolic-argument fixpoint conversion; hits the documented stack-overflow (bugs/segfault_apply_fixpoint_motive.me, bugs/segfault_exact_eliminator_fixpoint.me)." + } + ] +} \ No newline at end of file diff --git a/benchmarks/stdlib/corpus/nat_add_0_l/mengine.me b/benchmarks/stdlib/corpus/nat_add_0_l/mengine.me new file mode 100644 index 0000000..97dca28 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_0_l/mengine.me @@ -0,0 +1,3 @@ +Theorem add_0_l : forall (n : nat), (((eq nat) ((add O) n)) n). +intro n. +reflexivity. diff --git a/benchmarks/stdlib/corpus/nat_add_0_l/rocq.v b/benchmarks/stdlib/corpus/nat_add_0_l/rocq.v new file mode 100644 index 0000000..510dfd2 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_0_l/rocq.v @@ -0,0 +1,2 @@ +Lemma add_0_l : forall n : nat, 0 + n = n. +Proof. intro n. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/nat_add_2_2/mengine.me b/benchmarks/stdlib/corpus/nat_add_2_2/mengine.me new file mode 100644 index 0000000..f782f81 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_2_2/mengine.me @@ -0,0 +1,2 @@ +Theorem add_2_2 : (((eq nat) ((add (S (S O))) (S (S O)))) (S (S (S (S O))))). +reflexivity. diff --git a/benchmarks/stdlib/corpus/nat_add_2_2/rocq.v b/benchmarks/stdlib/corpus/nat_add_2_2/rocq.v new file mode 100644 index 0000000..8c8032c --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_2_2/rocq.v @@ -0,0 +1,2 @@ +Example add_2_2 : 2 + 2 = 4. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/nat_add_3_4/mengine.me b/benchmarks/stdlib/corpus/nat_add_3_4/mengine.me new file mode 100644 index 0000000..d37faa9 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_3_4/mengine.me @@ -0,0 +1,2 @@ +Theorem add_3_4 : (((eq nat) ((add (S (S (S O)))) (S (S (S (S O)))))) (S (S (S (S (S (S (S O)))))))). +reflexivity. diff --git a/benchmarks/stdlib/corpus/nat_add_3_4/rocq.v b/benchmarks/stdlib/corpus/nat_add_3_4/rocq.v new file mode 100644 index 0000000..9065217 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_add_3_4/rocq.v @@ -0,0 +1,2 @@ +Example add_3_4 : 3 + 4 = 7. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/nat_mul_2_3/mengine.me b/benchmarks/stdlib/corpus/nat_mul_2_3/mengine.me new file mode 100644 index 0000000..8e92818 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_mul_2_3/mengine.me @@ -0,0 +1,2 @@ +Theorem mul_2_3 : (((eq nat) ((mul (S (S O))) (S (S (S O))))) (S (S (S (S (S (S O))))))). +reflexivity. diff --git a/benchmarks/stdlib/corpus/nat_mul_2_3/rocq.v b/benchmarks/stdlib/corpus/nat_mul_2_3/rocq.v new file mode 100644 index 0000000..4b1548d --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_mul_2_3/rocq.v @@ -0,0 +1,2 @@ +Example mul_2_3 : 2 * 3 = 6. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/nat_sub_5_2/mengine.me b/benchmarks/stdlib/corpus/nat_sub_5_2/mengine.me new file mode 100644 index 0000000..fd17eba --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_sub_5_2/mengine.me @@ -0,0 +1,2 @@ +Theorem sub_5_2 : (((eq nat) ((sub (S (S (S (S (S O)))))) (S (S O)))) (S (S (S O)))). +reflexivity. diff --git a/benchmarks/stdlib/corpus/nat_sub_5_2/rocq.v b/benchmarks/stdlib/corpus/nat_sub_5_2/rocq.v new file mode 100644 index 0000000..d570799 --- /dev/null +++ b/benchmarks/stdlib/corpus/nat_sub_5_2/rocq.v @@ -0,0 +1,2 @@ +Example sub_5_2 : 5 - 2 = 3. +Proof. reflexivity. Qed. diff --git a/benchmarks/stdlib/plots/stdlib_scatter.png b/benchmarks/stdlib/plots/stdlib_scatter.png new file mode 100644 index 0000000000000000000000000000000000000000..2215db8eda091ddc71d76f0edb64075a6fb795d1 GIT binary patch literal 74904 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

ZZ1=X2$WeH3S>)XHsRkPPa(OPu3j!j!l1?Q& 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 CY#gfq literal 0 HcmV?d00001 diff --git a/benchmarks/stdlib/report.py b/benchmarks/stdlib/report.py new file mode 100644 index 0000000..25a37f4 --- /dev/null +++ b/benchmarks/stdlib/report.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Reporting for the Rocq-stdlib benchmark: markdown table + scatter plot. + +Reads results/stdlib.json (written by stdlib_bench.py run) and emits: + - stdlib/results/REPORT.md : per-unit table + geometric-mean summary + - stdlib/plots/stdlib_scatter.png : Rocq time (x) vs MEngine time (y), log-log +""" + +import json +import math +import os + + +def _category(name): + for pre, cat in (("bool_", "bool"), ("nat_", "nat"), ("le_", "le"), + ("logic_", "logic")): + if name.startswith(pre): + return cat + return "other" + + +def _load(cfg): + with open(cfg["results"]) as f: + results = json.load(f) + units = sorted({k.split("_", 1)[1] for k in results if "_" in k}) + rows = [] + for u in units: + m = results.get(f"mengine_{u}") + r = results.get(f"coq_{u}") + if not m or not r: + continue + rows.append({ + "unit": u, "category": _category(u), + "mengine": m["time_taken"] if m["success"] else None, + "rocq": r["time_taken"] if r["success"] else None, + }) + return rows + + +def _fmt_ms(t): + return f"{t*1000:.1f}" if t is not None else "FAIL" + + +def generate(cfg): + rows = _load(cfg) + if not rows: + print("No results yet — run `stdlib_bench.py run` first.") + return + + lines = [] + lines.append("# Rocq stdlib benchmark — MEngine vs Rocq\n") + lines.append("Per-unit wall-clock (best of N trials), whole-process: each engine " + "pays its own startup (MEngine loads `prelude/tactics.me` + the compat " + "prelude; Rocq starts its process and loads `Coq.Init`).\n") + lines.append("| Unit | Category | Rocq (ms) | MEngine (ms) | Speedup (Rocq/MEngine) |") + lines.append("|------|----------|-----------|--------------|------------------------|") + + speedups = [] + for row in sorted(rows, key=lambda r: (r["category"], r["unit"])): + m, r = row["mengine"], row["rocq"] + if m and r and m > 0: + sp = r / m + speedups.append(sp) + sp_s = f"{sp:.2f}×" + else: + sp_s = "-" + lines.append(f"| `{row['unit']}` | {row['category']} | {_fmt_ms(r)} | " + f"{_fmt_ms(m)} | {sp_s} |") + + if speedups: + geo = math.exp(sum(math.log(s) for s in speedups) / len(speedups)) + med = sorted(speedups)[len(speedups) // 2] + lines.append("") + lines.append(f"**Units:** {len(rows)} (all Tier A). " + f"**Both-succeed:** {len(speedups)}.") + lines.append(f"**Geometric-mean speedup (Rocq/MEngine):** {geo:.2f}× " + f"(median {med:.2f}×).") + lines.append("") + lines.append("> MEngine times are dominated by prelude+compat startup at this " + "problem size; the comparison reflects fixed per-invocation cost, " + "not asymptotics. See README for scope and caveats.") + + out_md = os.path.join(os.path.dirname(cfg["results"]), "REPORT.md") + with open(out_md, "w") as f: + f.write("\n".join(lines) + "\n") + print(f"Wrote {os.path.relpath(out_md)}") + + _scatter(cfg, rows) + + +def _scatter(cfg, rows): + try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + except Exception as e: # noqa: BLE001 + print(f"(skipping scatter plot: matplotlib unavailable: {e})") + return + + colors = {"bool": "tab:blue", "nat": "tab:green", "le": "tab:orange", + "logic": "tab:red", "other": "gray"} + pts = [r for r in rows if r["mengine"] and r["rocq"]] + if not pts: + return + fig, ax = plt.subplots(figsize=(6, 6)) + for cat in sorted({r["category"] for r in pts}): + xs = [r["rocq"] * 1000 for r in pts if r["category"] == cat] + ys = [r["mengine"] * 1000 for r in pts if r["category"] == cat] + ax.scatter(xs, ys, label=cat, color=colors.get(cat, "gray"), + s=40, alpha=0.8, edgecolors="k", linewidths=0.3) + + allv = [r["rocq"] * 1000 for r in pts] + [r["mengine"] * 1000 for r in pts] + lo, hi = min(allv) * 0.7, max(allv) * 1.4 + ax.plot([lo, hi], [lo, hi], "k--", lw=1, label="parity") + ax.set_xscale("log"); ax.set_yscale("log") + ax.set_xlim(lo, hi); ax.set_ylim(lo, hi) + ax.set_xlabel("Rocq time (ms, log)") + ax.set_ylabel("MEngine time (ms, log)") + ax.set_title("Rocq stdlib units: MEngine vs Rocq") + ax.legend(fontsize=8, loc="best") + ax.grid(True, which="both", ls=":", alpha=0.4) + os.makedirs(cfg["plots_dir"], exist_ok=True) + out = os.path.join(cfg["plots_dir"], "stdlib_scatter.png") + fig.tight_layout() + fig.savefig(out, dpi=120) + print(f"Wrote {os.path.relpath(out)}") + + +if __name__ == "__main__": + import sys + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + from stdlib_bench import load_config + generate(load_config()) diff --git a/benchmarks/stdlib/results/REPORT.md b/benchmarks/stdlib/results/REPORT.md new file mode 100644 index 0000000..94a0899 --- /dev/null +++ b/benchmarks/stdlib/results/REPORT.md @@ -0,0 +1,37 @@ +# Rocq stdlib benchmark — MEngine vs Rocq + +Per-unit wall-clock (best of N trials), whole-process: each engine pays its own startup (MEngine loads `prelude/tactics.me` + the compat prelude; Rocq starts its process and loads `Coq.Init`). + +| Unit | Category | Rocq (ms) | MEngine (ms) | Speedup (Rocq/MEngine) | +|------|----------|-----------|--------------|------------------------| +| `bool_andb_b_b` | bool | 66.2 | 2.2 | 30.45× | +| `bool_andb_false_l` | bool | 65.4 | 1.9 | 34.10× | +| `bool_andb_true_l` | bool | 64.5 | 2.0 | 32.07× | +| `bool_andb_true_r` | bool | 65.1 | 2.1 | 31.40× | +| `bool_negb_false` | bool | 63.7 | 2.0 | 31.63× | +| `bool_negb_involutive` | bool | 66.7 | 2.2 | 30.67× | +| `bool_negb_true` | bool | 64.0 | 2.0 | 32.43× | +| `bool_orb_b_b` | bool | 66.2 | 2.1 | 31.09× | +| `bool_orb_false_l` | bool | 65.4 | 2.1 | 31.31× | +| `bool_orb_false_r` | bool | 65.8 | 2.0 | 33.04× | +| `bool_orb_true_l` | bool | 63.4 | 2.0 | 31.49× | +| `le_0_n` | le | 63.3 | 1.9 | 33.32× | +| `le_1_2` | le | 67.9 | 2.1 | 32.68× | +| `le_2_4` | le | 65.2 | 2.0 | 32.10× | +| `le_refl_n` | le | 68.7 | 2.0 | 35.01× | +| `logic_and_intro` | logic | 65.4 | 2.0 | 33.48× | +| `logic_and_intro3` | logic | 67.9 | 1.9 | 36.21× | +| `logic_imp_refl` | logic | 65.3 | 2.4 | 27.48× | +| `logic_imp_trans` | logic | 65.2 | 1.8 | 36.12× | +| `logic_or_introl` | logic | 64.6 | 2.1 | 30.17× | +| `logic_or_intror` | logic | 67.0 | 1.9 | 35.86× | +| `nat_add_0_l` | nat | 67.4 | 1.8 | 36.51× | +| `nat_add_2_2` | nat | 65.4 | 2.0 | 32.65× | +| `nat_add_3_4` | nat | 75.8 | 2.1 | 36.39× | +| `nat_mul_2_3` | nat | 66.3 | 2.7 | 24.63× | +| `nat_sub_5_2` | nat | 66.7 | 2.3 | 29.27× | + +**Units:** 26 (all Tier A). **Both-succeed:** 26. +**Geometric-mean speedup (Rocq/MEngine):** 32.25× (median 32.43×). + +> MEngine times are dominated by prelude+compat startup at this problem size; the comparison reflects fixed per-invocation cost, not asymptotics. See README for scope and caveats. diff --git a/benchmarks/stdlib/results/stdlib.json b/benchmarks/stdlib/results/stdlib.json new file mode 100644 index 0000000..5a8a06e --- /dev/null +++ b/benchmarks/stdlib/results/stdlib.json @@ -0,0 +1,1406 @@ +{ + "mengine_bool_andb_b_b": { + "time_taken": 0.0021740803495049477, + "success": true, + "trials": [ + { + "time_taken": 0.005351994186639786, + "success": true + }, + { + "time_taken": 0.005838930606842041, + "success": true + }, + { + "time_taken": 0.002309804782271385, + "success": true + }, + { + "time_taken": 0.005080173723399639, + "success": true + }, + { + "time_taken": 0.0021740803495049477, + "success": true + } + ], + "error": "" + }, + "coq_bool_andb_b_b": { + "time_taken": 0.06619509868323803, + "success": true, + "trials": [ + { + "time_taken": 0.0740803238004446, + "success": true + }, + { + "time_taken": 0.07655169162899256, + "success": true + }, + { + "time_taken": 0.08828787785023451, + "success": true + }, + { + "time_taken": 0.06725370418280363, + "success": true + }, + { + "time_taken": 0.06619509868323803, + "success": true + } + ], + "error": "" + }, + "mengine_bool_andb_false_l": { + "time_taken": 0.0019167512655258179, + "success": true, + "trials": [ + { + "time_taken": 0.002403966151177883, + "success": true + }, + { + "time_taken": 0.00489372294396162, + "success": true + }, + { + "time_taken": 0.002142001874744892, + "success": true + }, + { + "time_taken": 0.0019167512655258179, + "success": true + }, + { + "time_taken": 0.004725058563053608, + "success": true + } + ], + "error": "" + }, + "coq_bool_andb_false_l": { + "time_taken": 0.06536232214421034, + "success": true, + "trials": [ + { + "time_taken": 0.0913093164563179, + "success": true + }, + { + "time_taken": 0.06557705346494913, + "success": true + }, + { + "time_taken": 0.06551979761570692, + "success": true + }, + { + "time_taken": 0.09066289197653532, + "success": true + }, + { + "time_taken": 0.06536232214421034, + "success": true + } + ], + "error": "" + }, + "mengine_bool_andb_true_l": { + "time_taken": 0.002012736164033413, + "success": true, + "trials": [ + { + "time_taken": 0.002243353985249996, + "success": true + }, + { + "time_taken": 0.004768563434481621, + "success": true + }, + { + "time_taken": 0.002012736164033413, + "success": true + }, + { + "time_taken": 0.005158963613212109, + "success": true + }, + { + "time_taken": 0.002134900540113449, + "success": true + } + ], + "error": "" + }, + "coq_bool_andb_true_l": { + "time_taken": 0.06454734224826097, + "success": true, + "trials": [ + { + "time_taken": 0.08336627390235662, + "success": true + }, + { + "time_taken": 0.06454734224826097, + "success": true + }, + { + "time_taken": 0.06653274688869715, + "success": true + }, + { + "time_taken": 0.09328210167586803, + "success": true + }, + { + "time_taken": 0.08236036542803049, + "success": true + } + ], + "error": "" + }, + "mengine_bool_andb_true_r": { + "time_taken": 0.0020732879638671875, + "success": true, + "trials": [ + { + "time_taken": 0.002406270243227482, + "success": true + }, + { + "time_taken": 0.004063849337399006, + "success": true + }, + { + "time_taken": 0.002175631932914257, + "success": true + }, + { + "time_taken": 0.0020732879638671875, + "success": true + }, + { + "time_taken": 0.003917189314961433, + "success": true + } + ], + "error": "" + }, + "coq_bool_andb_true_r": { + "time_taken": 0.06510116718709469, + "success": true, + "trials": [ + { + "time_taken": 0.06510116718709469, + "success": true + }, + { + "time_taken": 0.07451162487268448, + "success": true + }, + { + "time_taken": 0.06923293974250555, + "success": true + }, + { + "time_taken": 0.08497279789298773, + "success": true + }, + { + "time_taken": 0.06970004457980394, + "success": true + } + ], + "error": "" + }, + "mengine_bool_negb_false": { + "time_taken": 0.0020140185952186584, + "success": true, + "trials": [ + { + "time_taken": 0.0047165462747216225, + "success": true + }, + { + "time_taken": 0.0024332813918590546, + "success": true + }, + { + "time_taken": 0.004702294245362282, + "success": true + }, + { + "time_taken": 0.0020140185952186584, + "success": true + }, + { + "time_taken": 0.0023254482075572014, + "success": true + } + ], + "error": "" + }, + "coq_bool_negb_false": { + "time_taken": 0.06370629277080297, + "success": true, + "trials": [ + { + "time_taken": 0.06883997842669487, + "success": true + }, + { + "time_taken": 0.06370629277080297, + "success": true + }, + { + "time_taken": 0.07765176240354776, + "success": true + }, + { + "time_taken": 0.08686848822981119, + "success": true + }, + { + "time_taken": 0.06471100822091103, + "success": true + } + ], + "error": "" + }, + "mengine_bool_negb_involutive": { + "time_taken": 0.0021748607978224754, + "success": true, + "trials": [ + { + "time_taken": 0.0023497147485613823, + "success": true + }, + { + "time_taken": 0.004884419031441212, + "success": true + }, + { + "time_taken": 0.0021748607978224754, + "success": true + }, + { + "time_taken": 0.004625418223440647, + "success": true + }, + { + "time_taken": 0.004726300947368145, + "success": true + } + ], + "error": "" + }, + "coq_bool_negb_involutive": { + "time_taken": 0.06670686043798923, + "success": true, + "trials": [ + { + "time_taken": 0.07896770723164082, + "success": true + }, + { + "time_taken": 0.09257125109434128, + "success": true + }, + { + "time_taken": 0.06993178371340036, + "success": true + }, + { + "time_taken": 0.06808586046099663, + "success": true + }, + { + "time_taken": 0.06670686043798923, + "success": true + } + ], + "error": "" + }, + "mengine_bool_negb_true": { + "time_taken": 0.0019729966297745705, + "success": true, + "trials": [ + { + "time_taken": 0.0021493425592780113, + "success": true + }, + { + "time_taken": 0.004536454565823078, + "success": true + }, + { + "time_taken": 0.0019729966297745705, + "success": true + }, + { + "time_taken": 0.004446428269147873, + "success": true + }, + { + "time_taken": 0.004626359790563583, + "success": true + } + ], + "error": "" + }, + "coq_bool_negb_true": { + "time_taken": 0.06397562939673662, + "success": true, + "trials": [ + { + "time_taken": 0.06397562939673662, + "success": true + }, + { + "time_taken": 0.08838566765189171, + "success": true + }, + { + "time_taken": 0.0750273521989584, + "success": true + }, + { + "time_taken": 0.08242534380406141, + "success": true + }, + { + "time_taken": 0.06548822950571775, + "success": true + } + ], + "error": "" + }, + "mengine_bool_orb_b_b": { + "time_taken": 0.002130454406142235, + "success": true, + "trials": [ + { + "time_taken": 0.00238234456628561, + "success": true + }, + { + "time_taken": 0.002154691144824028, + "success": true + }, + { + "time_taken": 0.004914012737572193, + "success": true + }, + { + "time_taken": 0.0048569580540061, + "success": true + }, + { + "time_taken": 0.002130454406142235, + "success": true + } + ], + "error": "" + }, + "coq_bool_orb_b_b": { + "time_taken": 0.06623977515846491, + "success": true, + "trials": [ + { + "time_taken": 0.08240906987339258, + "success": true + }, + { + "time_taken": 0.06623977515846491, + "success": true + }, + { + "time_taken": 0.07678854931145906, + "success": true + }, + { + "time_taken": 0.08358939085155725, + "success": true + }, + { + "time_taken": 0.0705526415258646, + "success": true + } + ], + "error": "" + }, + "mengine_bool_orb_false_l": { + "time_taken": 0.0020895833149552345, + "success": true, + "trials": [ + { + "time_taken": 0.0022005299106240273, + "success": true + }, + { + "time_taken": 0.0057874806225299835, + "success": true + }, + { + "time_taken": 0.002113298512995243, + "success": true + }, + { + "time_taken": 0.004648342728614807, + "success": true + }, + { + "time_taken": 0.0020895833149552345, + "success": true + } + ], + "error": "" + }, + "coq_bool_orb_false_l": { + "time_taken": 0.06543062441051006, + "success": true, + "trials": [ + { + "time_taken": 0.08560738526284695, + "success": true + }, + { + "time_taken": 0.06543062441051006, + "success": true + }, + { + "time_taken": 0.107350236736238, + "success": true + }, + { + "time_taken": 0.08680164627730846, + "success": true + }, + { + "time_taken": 0.07846035342663527, + "success": true + } + ], + "error": "" + }, + "mengine_bool_orb_false_r": { + "time_taken": 0.0019906237721443176, + "success": true, + "trials": [ + { + "time_taken": 0.005584435537457466, + "success": true + }, + { + "time_taken": 0.004804197698831558, + "success": true + }, + { + "time_taken": 0.004078764468431473, + "success": true + }, + { + "time_taken": 0.0019906237721443176, + "success": true + }, + { + "time_taken": 0.0019935863092541695, + "success": true + } + ], + "error": "" + }, + "coq_bool_orb_false_r": { + "time_taken": 0.06576732266694307, + "success": true, + "trials": [ + { + "time_taken": 0.08065385092049837, + "success": true + }, + { + "time_taken": 0.0669049583375454, + "success": true + }, + { + "time_taken": 0.06576732266694307, + "success": true + }, + { + "time_taken": 0.09204454533755779, + "success": true + }, + { + "time_taken": 0.06678638886660337, + "success": true + } + ], + "error": "" + }, + "mengine_bool_orb_true_l": { + "time_taken": 0.002014538273215294, + "success": true, + "trials": [ + { + "time_taken": 0.0021964823827147484, + "success": true + }, + { + "time_taken": 0.002014538273215294, + "success": true + }, + { + "time_taken": 0.004663505591452122, + "success": true + }, + { + "time_taken": 0.0020369822159409523, + "success": true + }, + { + "time_taken": 0.004412497393786907, + "success": true + } + ], + "error": "" + }, + "coq_bool_orb_true_l": { + "time_taken": 0.06342829391360283, + "success": true, + "trials": [ + { + "time_taken": 0.06498104520142078, + "success": true + }, + { + "time_taken": 0.0878400020301342, + "success": true + }, + { + "time_taken": 0.07843938004225492, + "success": true + }, + { + "time_taken": 0.06342829391360283, + "success": true + }, + { + "time_taken": 0.08014839794486761, + "success": true + } + ], + "error": "" + }, + "mengine_le_0_n": { + "time_taken": 0.0018982738256454468, + "success": true, + "trials": [ + { + "time_taken": 0.002197735942900181, + "success": true + }, + { + "time_taken": 0.001912526786327362, + "success": true + }, + { + "time_taken": 0.0018982738256454468, + "success": true + }, + { + "time_taken": 0.0019145384430885315, + "success": true + }, + { + "time_taken": 0.004819119349122047, + "success": true + } + ], + "error": "" + }, + "coq_le_0_n": { + "time_taken": 0.0632502743974328, + "success": true, + "trials": [ + { + "time_taken": 0.06364869512617588, + "success": true + }, + { + "time_taken": 0.06465807929635048, + "success": true + }, + { + "time_taken": 0.0632502743974328, + "success": true + }, + { + "time_taken": 0.08437193185091019, + "success": true + }, + { + "time_taken": 0.0686585046350956, + "success": true + } + ], + "error": "" + }, + "mengine_le_1_2": { + "time_taken": 0.0020777760073542595, + "success": true, + "trials": [ + { + "time_taken": 0.0023047784343361855, + "success": true + }, + { + "time_taken": 0.004668844863772392, + "success": true + }, + { + "time_taken": 0.0021224217489361763, + "success": true + }, + { + "time_taken": 0.003817237913608551, + "success": true + }, + { + "time_taken": 0.0020777760073542595, + "success": true + } + ], + "error": "" + }, + "coq_le_1_2": { + "time_taken": 0.06790374498814344, + "success": true, + "trials": [ + { + "time_taken": 0.07176924590021372, + "success": true + }, + { + "time_taken": 0.0753260925412178, + "success": true + }, + { + "time_taken": 0.06877670250833035, + "success": true + }, + { + "time_taken": 0.06899416074156761, + "success": true + }, + { + "time_taken": 0.06790374498814344, + "success": true + } + ], + "error": "" + }, + "mengine_le_2_4": { + "time_taken": 0.002030644565820694, + "success": true, + "trials": [ + { + "time_taken": 0.004747071303427219, + "success": true + }, + { + "time_taken": 0.003835677169263363, + "success": true + }, + { + "time_taken": 0.0037500187754631042, + "success": true + }, + { + "time_taken": 0.002030644565820694, + "success": true + }, + { + "time_taken": 0.0020945286378264427, + "success": true + } + ], + "error": "" + }, + "coq_le_2_4": { + "time_taken": 0.0651756701990962, + "success": true, + "trials": [ + { + "time_taken": 0.0692351944744587, + "success": true + }, + { + "time_taken": 0.08934114780277014, + "success": true + }, + { + "time_taken": 0.08167203795164824, + "success": true + }, + { + "time_taken": 0.06681795697659254, + "success": true + }, + { + "time_taken": 0.0651756701990962, + "success": true + } + ], + "error": "" + }, + "mengine_le_refl_n": { + "time_taken": 0.0019616493955254555, + "success": true, + "trials": [ + { + "time_taken": 0.0021845372393727303, + "success": true + }, + { + "time_taken": 0.004463664256036282, + "success": true + }, + { + "time_taken": 0.004248057492077351, + "success": true + }, + { + "time_taken": 0.001974780112504959, + "success": true + }, + { + "time_taken": 0.0019616493955254555, + "success": true + } + ], + "error": "" + }, + "coq_le_refl_n": { + "time_taken": 0.06868398282676935, + "success": true, + "trials": [ + { + "time_taken": 0.08178988564759493, + "success": true + }, + { + "time_taken": 0.07030741218477488, + "success": true + }, + { + "time_taken": 0.06868398282676935, + "success": true + }, + { + "time_taken": 0.07078546285629272, + "success": true + }, + { + "time_taken": 0.06981757190078497, + "success": true + } + ], + "error": "" + }, + "mengine_logic_and_intro": { + "time_taken": 0.0019524255767464638, + "success": true, + "trials": [ + { + "time_taken": 0.0027153370901942253, + "success": true + }, + { + "time_taken": 0.004514399915933609, + "success": true + }, + { + "time_taken": 0.002142813056707382, + "success": true + }, + { + "time_taken": 0.004342632368206978, + "success": true + }, + { + "time_taken": 0.0019524255767464638, + "success": true + } + ], + "error": "" + }, + "coq_logic_and_intro": { + "time_taken": 0.06537349801510572, + "success": true, + "trials": [ + { + "time_taken": 0.07824582792818546, + "success": true + }, + { + "time_taken": 0.06562335509806871, + "success": true + }, + { + "time_taken": 0.06537349801510572, + "success": true + }, + { + "time_taken": 0.08276569750159979, + "success": true + }, + { + "time_taken": 0.06738755572587252, + "success": true + } + ], + "error": "" + }, + "mengine_logic_and_intro3": { + "time_taken": 0.0018761307001113892, + "success": true, + "trials": [ + { + "time_taken": 0.0021279100328683853, + "success": true + }, + { + "time_taken": 0.004665658809244633, + "success": true + }, + { + "time_taken": 0.001949230208992958, + "success": true + }, + { + "time_taken": 0.0045388685539364815, + "success": true + }, + { + "time_taken": 0.0018761307001113892, + "success": true + } + ], + "error": "" + }, + "coq_logic_and_intro3": { + "time_taken": 0.06794031150639057, + "success": true, + "trials": [ + { + "time_taken": 0.07201241329312325, + "success": true + }, + { + "time_taken": 0.08989651687443256, + "success": true + }, + { + "time_taken": 0.08453252259641886, + "success": true + }, + { + "time_taken": 0.06794031150639057, + "success": true + }, + { + "time_taken": 0.07006465457379818, + "success": true + } + ], + "error": "" + }, + "mengine_logic_imp_refl": { + "time_taken": 0.0023775771260261536, + "success": true, + "trials": [ + { + "time_taken": 0.002612692303955555, + "success": true + }, + { + "time_taken": 0.002495034597814083, + "success": true + }, + { + "time_taken": 0.0024922918528318405, + "success": true + }, + { + "time_taken": 0.004849405959248543, + "success": true + }, + { + "time_taken": 0.0023775771260261536, + "success": true + } + ], + "error": "" + }, + "coq_logic_imp_refl": { + "time_taken": 0.06533516012132168, + "success": true, + "trials": [ + { + "time_taken": 0.0705872755497694, + "success": true + }, + { + "time_taken": 0.06823329254984856, + "success": true + }, + { + "time_taken": 0.07213691249489784, + "success": true + }, + { + "time_taken": 0.07505239080637693, + "success": true + }, + { + "time_taken": 0.06533516012132168, + "success": true + } + ], + "error": "" + }, + "mengine_logic_imp_trans": { + "time_taken": 0.001803610473871231, + "success": true, + "trials": [ + { + "time_taken": 0.003330945037305355, + "success": true + }, + { + "time_taken": 0.002065124921500683, + "success": true + }, + { + "time_taken": 0.001896030269563198, + "success": true + }, + { + "time_taken": 0.001842910423874855, + "success": true + }, + { + "time_taken": 0.001803610473871231, + "success": true + } + ], + "error": "" + }, + "coq_logic_imp_trans": { + "time_taken": 0.06515374593436718, + "success": true, + "trials": [ + { + "time_taken": 0.06515374593436718, + "success": true + }, + { + "time_taken": 0.07420218642801046, + "success": true + }, + { + "time_taken": 0.0716068334877491, + "success": true + }, + { + "time_taken": 0.07185128051787615, + "success": true + }, + { + "time_taken": 0.07055314257740974, + "success": true + } + ], + "error": "" + }, + "mengine_logic_or_introl": { + "time_taken": 0.0021398579701781273, + "success": true, + "trials": [ + { + "time_taken": 0.002498149871826172, + "success": true + }, + { + "time_taken": 0.00254646223038435, + "success": true + }, + { + "time_taken": 0.0021398579701781273, + "success": true + }, + { + "time_taken": 0.0021884916350245476, + "success": true + }, + { + "time_taken": 0.0021713552996516228, + "success": true + } + ], + "error": "" + }, + "coq_logic_or_introl": { + "time_taken": 0.06455874908715487, + "success": true, + "trials": [ + { + "time_taken": 0.07866571191698313, + "success": true + }, + { + "time_taken": 0.06972293090075254, + "success": true + }, + { + "time_taken": 0.08866208326071501, + "success": true + }, + { + "time_taken": 0.06455874908715487, + "success": true + }, + { + "time_taken": 0.07155965082347393, + "success": true + } + ], + "error": "" + }, + "mengine_logic_or_intror": { + "time_taken": 0.0018694410100579262, + "success": true, + "trials": [ + { + "time_taken": 0.0021275291219353676, + "success": true + }, + { + "time_taken": 0.0018694410100579262, + "success": true + }, + { + "time_taken": 0.0019745882600545883, + "success": true + }, + { + "time_taken": 0.0019634421914815903, + "success": true + }, + { + "time_taken": 0.0020487727597355843, + "success": true + } + ], + "error": "" + }, + "coq_logic_or_intror": { + "time_taken": 0.06703538540750742, + "success": true, + "trials": [ + { + "time_taken": 0.07749038841575384, + "success": true + }, + { + "time_taken": 0.06703538540750742, + "success": true + }, + { + "time_taken": 0.06928080227226019, + "success": true + }, + { + "time_taken": 0.07001374755054712, + "success": true + }, + { + "time_taken": 0.06879895646125078, + "success": true + } + ], + "error": "" + }, + "mengine_nat_add_0_l": { + "time_taken": 0.0018455525860190392, + "success": true, + "trials": [ + { + "time_taken": 0.002109711989760399, + "success": true + }, + { + "time_taken": 0.003782837651669979, + "success": true + }, + { + "time_taken": 0.0018455525860190392, + "success": true + }, + { + "time_taken": 0.005703866481781006, + "success": true + }, + { + "time_taken": 0.0020541781559586525, + "success": true + } + ], + "error": "" + }, + "coq_nat_add_0_l": { + "time_taken": 0.06738493219017982, + "success": true, + "trials": [ + { + "time_taken": 0.07927136495709419, + "success": true + }, + { + "time_taken": 0.06826803553849459, + "success": true + }, + { + "time_taken": 0.06766507495194674, + "success": true + }, + { + "time_taken": 0.07551105134189129, + "success": true + }, + { + "time_taken": 0.06738493219017982, + "success": true + } + ], + "error": "" + }, + "mengine_nat_add_2_2": { + "time_taken": 0.002002852037549019, + "success": true, + "trials": [ + { + "time_taken": 0.0023316480219364166, + "success": true + }, + { + "time_taken": 0.002466851845383644, + "success": true + }, + { + "time_taken": 0.0020456956699490547, + "success": true + }, + { + "time_taken": 0.0020163925364613533, + "success": true + }, + { + "time_taken": 0.002002852037549019, + "success": true + } + ], + "error": "" + }, + "coq_nat_add_2_2": { + "time_taken": 0.06539278663694859, + "success": true, + "trials": [ + { + "time_taken": 0.07251646369695663, + "success": true + }, + { + "time_taken": 0.06539278663694859, + "success": true + }, + { + "time_taken": 0.06644356064498425, + "success": true + }, + { + "time_taken": 0.08276282344013453, + "success": true + }, + { + "time_taken": 0.0700690420344472, + "success": true + } + ], + "error": "" + }, + "mengine_nat_add_3_4": { + "time_taken": 0.0020826924592256546, + "success": true, + "trials": [ + { + "time_taken": 0.0022964337840676308, + "success": true + }, + { + "time_taken": 0.0020826924592256546, + "success": true + }, + { + "time_taken": 0.002455153502523899, + "success": true + }, + { + "time_taken": 0.0025155944749712944, + "success": true + }, + { + "time_taken": 0.0021605808287858963, + "success": true + } + ], + "error": "" + }, + "coq_nat_add_3_4": { + "time_taken": 0.0757963927462697, + "success": true, + "trials": [ + { + "time_taken": 0.0757963927462697, + "success": true + }, + { + "time_taken": 0.09031297452747822, + "success": true + }, + { + "time_taken": 0.08658439852297306, + "success": true + }, + { + "time_taken": 0.08109507616609335, + "success": true + }, + { + "time_taken": 0.08568170573562384, + "success": true + } + ], + "error": "" + }, + "mengine_nat_mul_2_3": { + "time_taken": 0.0026925019919872284, + "success": true, + "trials": [ + { + "time_taken": 0.002743959426879883, + "success": true + }, + { + "time_taken": 0.006835583597421646, + "success": true + }, + { + "time_taken": 0.0026925019919872284, + "success": true + }, + { + "time_taken": 0.006420597434043884, + "success": true + }, + { + "time_taken": 0.00345437228679657, + "success": true + } + ], + "error": "" + }, + "coq_nat_mul_2_3": { + "time_taken": 0.06630833726376295, + "success": true, + "trials": [ + { + "time_taken": 0.07668156921863556, + "success": true + }, + { + "time_taken": 0.06689528468996286, + "success": true + }, + { + "time_taken": 0.06630833726376295, + "success": true + }, + { + "time_taken": 0.08364323060959578, + "success": true + }, + { + "time_taken": 0.07155932951718569, + "success": true + } + ], + "error": "" + }, + "mengine_nat_sub_5_2": { + "time_taken": 0.002279299311339855, + "success": true, + "trials": [ + { + "time_taken": 0.002385929226875305, + "success": true + }, + { + "time_taken": 0.005345353856682777, + "success": true + }, + { + "time_taken": 0.004974755458533764, + "success": true + }, + { + "time_taken": 0.002279299311339855, + "success": true + }, + { + "time_taken": 0.004986553452908993, + "success": true + } + ], + "error": "" + }, + "coq_nat_sub_5_2": { + "time_taken": 0.06671494245529175, + "success": true, + "trials": [ + { + "time_taken": 0.08216926734894514, + "success": true + }, + { + "time_taken": 0.0669594295322895, + "success": true + }, + { + "time_taken": 0.0773226460441947, + "success": true + }, + { + "time_taken": 0.06671494245529175, + "success": true + }, + { + "time_taken": 0.07971495389938354, + "success": true + } + ], + "error": "" + } +} \ No newline at end of file diff --git a/benchmarks/stdlib/stdlib_bench.py b/benchmarks/stdlib/stdlib_bench.py new file mode 100644 index 0000000..7dfa495 --- /dev/null +++ b/benchmarks/stdlib/stdlib_bench.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 +"""Corpus runner for the Rocq-stdlib benchmark (MEngine vs Rocq). + +A sibling to ``benchmarks/bench.py``: instead of sweeping a parameter range it +iterates a fixed manifest of curated, auto-translated stdlib units. It reuses +the timing discipline of ``framework/runner.run_single`` (process-group kill on +timeout, N trials keeping the minimum) without subclassing ``Benchmark``. + +Subcommands: + list show the corpus and tiers + test [unit ...] faithfulness gate: coqc compiles rocq.v, MEngine runs + mengine.me clean, and the statement digests correspond + run [unit ...] time both engines per unit; write results/stdlib.json + report regenerate the markdown table + scatter plot + manifest regenerate corpus/manifest.json from the corpus dir + triage [--dir D] run translate.py --report over a stdlib checkout +""" + +import argparse +import hashlib +import json +import os +import re +import signal +import subprocess +import sys +import time + +HERE = os.path.dirname(os.path.abspath(__file__)) +BENCH_ROOT = os.path.dirname(HERE) +sys.path.insert(0, HERE) +import translate # noqa: E402 (local module) + + +# ─────────────────────────────── config ────────────────────────────────────── + +def load_config(): + with open(os.path.join(BENCH_ROOT, "config.json")) as f: + cfg = json.load(f) + s = cfg.get("stdlib", {}) + def exp(p): + return os.path.expanduser(p) if p else p + return { + "mengine_path": exp(cfg["mengine_path"]), + "mengine_root": exp(cfg.get("mengine_root", "")), + "coq_path": exp(cfg.get("coq_path", "coqc")), + "corpus_dir": os.path.join(BENCH_ROOT, s.get("corpus_dir", "stdlib/corpus")), + "compat": os.path.join(BENCH_ROOT, s.get("compat", "stdlib/compat/stdlib_compat.me")), + "results": os.path.join(BENCH_ROOT, s.get("results", "stdlib/results/stdlib.json")), + "plots_dir": os.path.join(BENCH_ROOT, s.get("plots_dir", "stdlib/plots")), + "timeout": s.get("timeout", 20), + "trials": s.get("trials", 5), + "coq_timeout_multiplier": cfg.get("coq_timeout_multiplier", 1.5), + "rocq_stdlib_src": exp(s.get("rocq_stdlib_src", "")), + } + + +# ─────────────────────────── corpus discovery ──────────────────────────────── + +# Tier inferred from the unit-name prefix; kept in sync with the manifest. +def unit_tier(name): + if name.startswith("bool_"): + return "bool" + if name.startswith("nat_"): + return "nat_ground" + if name.startswith("le_"): + return "le" + if name.startswith("logic_"): + return "logic" + return "other" + + +def discover_units(cfg): + units = [] + for name in sorted(os.listdir(cfg["corpus_dir"])): + d = os.path.join(cfg["corpus_dir"], name) + if os.path.isdir(d) and os.path.exists(os.path.join(d, "rocq.v")): + units.append(name) + return units + + +def unit_paths(cfg, name): + d = os.path.join(cfg["corpus_dir"], name) + return os.path.join(d, "rocq.v"), os.path.join(d, "mengine.me"), d + + +# ───────────────────────────── timing core ─────────────────────────────────── +# Mirrors framework.runner.run_single: N trials, keep the min successful time, +# kill the whole process group on timeout. + +def time_command(cmd, cwd, timeout, trials): + best = None + success = False + all_trials = [] + last_err = "" + for _ in range(trials): + start = time.perf_counter() + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, cwd=cwd, start_new_session=True) + out, err = proc.communicate(timeout=timeout) + elapsed = time.perf_counter() - start + ok = proc.returncode == 0 + all_trials.append({"time_taken": elapsed, "success": ok}) + if not ok: + last_err = (err or out)[:300] + if ok: + success = True + if best is None or elapsed < best: + best = elapsed + else: + break # don't repeat a failing point + except subprocess.TimeoutExpired: + elapsed = time.perf_counter() - start + try: + os.killpg(proc.pid, signal.SIGTERM) + proc.communicate(timeout=1) + except Exception: + try: + os.killpg(proc.pid, signal.SIGKILL) + except Exception: + pass + all_trials.append({"time_taken": elapsed, "success": False, "timeout": True}) + last_err = f"timeout after {timeout}s" + break + return {"time_taken": best if best is not None else 0.0, + "success": success, "trials": all_trials, "error": last_err} + + +# ─────────────────────────── per-engine commands ───────────────────────────── + +def mengine_combined_file(cfg, mepath): + """Write compat-prelude + unit into a temp .me; return its path.""" + import tempfile + fd, path = tempfile.mkstemp(suffix=".me", prefix="stdlib_unit_") + with os.fdopen(fd, "w") as f: + with open(cfg["compat"]) as c: + f.write(c.read()) + f.write("\n") + with open(mepath) as m: + f.write(m.read()) + return path + + +def run_mengine(cfg, name, timeout, trials): + _v, mepath, _d = unit_paths(cfg, name) + combined = mengine_combined_file(cfg, mepath) + try: + cmd = [cfg["mengine_path"], "-q", combined] + res = time_command(cmd, cfg["mengine_root"] or None, timeout, trials) + finally: + os.remove(combined) + return res + + +def run_rocq(cfg, name, timeout, trials): + vpath, _m, d = unit_paths(cfg, name) + cmd = [cfg["coq_path"], "-q", vpath] + to = timeout * cfg["coq_timeout_multiplier"] + res = time_command(cmd, d, to, trials) + # clean coqc artifacts + for ext in (".vo", ".vok", ".vos", ".glob"): + p = vpath[:-2] + ext + if os.path.exists(p): + os.remove(p) + for fn in os.listdir(d): + if fn.endswith(".aux") or fn.startswith(".rocq") or fn.startswith(".coq"): + try: + os.remove(os.path.join(d, fn)) + except OSError: + pass + return res + + +# ─────────────────────────── faithfulness gate ─────────────────────────────── + +def mengine_statement_digests(cfg, mepath): + with open(mepath) as f: + return dict(translate.statement_digests(f.read())) + + +def rocq_statement_names(vpath): + with open(vpath) as f: + text = translate.strip_comments(f.read()) + names = [] + for m in re.finditer(r"\b(?:Lemma|Theorem|Example|Corollary|Fact|Remark|Proposition)\s+" + r"([A-Za-z_][A-Za-z0-9_']*)", text): + names.append(m.group(1)) + return names + + +def test_unit(cfg, name): + vpath, mepath, d = unit_paths(cfg, name) + problems = [] + + # 1. Rocq compiles. + rr = run_rocq(cfg, name, cfg["timeout"], 1) + if not rr["success"]: + problems.append(f"rocq.v fails coqc: {rr['error'][:120]}") + + # 2. MEngine runs clean. + mr = run_mengine(cfg, name, cfg["timeout"], 1) + if not mr["success"]: + problems.append(f"mengine.me fails: {mr['error'][:120]}") + + # 3. mengine.me re-derivable from rocq.v by the translator (no drift), and + # statement names correspond. + tr = subprocess.run(["python3", os.path.join(HERE, "translate.py"), vpath], + capture_output=True, text=True) + if tr.returncode != 0: + problems.append(f"translator flags rocq.v: {tr.stderr.strip()[:120]}") + else: + with open(mepath) as f: + on_disk = f.read() + if _norm(on_disk) != _norm(tr.stdout): + problems.append("mengine.me is stale vs translate.py output (re-run manifest)") + me_names = set(dict(translate.statement_digests(tr.stdout)).keys()) + rq_names = set(rocq_statement_names(vpath)) + if me_names != rq_names: + problems.append(f"statement names differ: rocq={rq_names} mengine={me_names}") + + return problems + + +def _norm(s): + return "\n".join(line.rstrip() for line in s.strip().splitlines()) + + +# ───────────────────────────── manifest ────────────────────────────────────── + +def sha256_file(path): + with open(path, "rb") as f: + return hashlib.sha256(f.read()).hexdigest()[:16] + + +def build_manifest(cfg): + units = [] + for name in discover_units(cfg): + vpath, mepath, _d = unit_paths(cfg, name) + digests = list(translate.statement_digests(open(mepath).read())) + units.append({ + "name": name, + "tier": "A", + "category": unit_tier(name), + "rocq_sha256": sha256_file(vpath), + "statements": [{"name": n, "digest": dg} for n, dg in digests], + "deps": "Coq.Init (auto-loaded)", + }) + manifest = { + "description": "Tier-A: Rocq stdlib units auto-translated with zero manual " + "edits and proved by MEngine + the compat prelude.", + "engine_note": "Requires the cbv applied-fix and GC-dedup kernel fixes " + "(see benchmarks/stdlib/README.md).", + "units": units, + "excluded": EXCLUDED_DOC, + } + out = os.path.join(cfg["corpus_dir"], "manifest.json") + with open(out, "w") as f: + json.dump(manifest, f, indent=2) + return out, len(units) + + +# Documented Tier-2 boundary (kept out of Tier A); see README. +EXCLUDED_DOC = [ + {"pattern": "destruct/case-analysis with a constant RHS " + "(e.g. andb b false = false)", + "boundary": "tier2-unify", + "reason": "apply cannot solve the eliminator's scrutinee evar when the " + "scrutinee variable has no bare occurrence in the goal; the " + "unifier whnf-folds the function application into a stuck match."}, + {"pattern": "computational induction over a Fixpoint " + "(e.g. add n 0 = n, n + 0 = n)", + "boundary": "tier2-symbolic", + "reason": "needs symbolic-argument fixpoint conversion; hits the documented " + "stack-overflow (bugs/segfault_apply_fixpoint_motive.me, " + "bugs/segfault_exact_eliminator_fixpoint.me)."}, +] + + +# ─────────────────────────────── subcommands ───────────────────────────────── + +def cmd_list(cfg, args): + units = discover_units(cfg) + by_cat = {} + for u in units: + by_cat.setdefault(unit_tier(u), []).append(u) + print(f"Corpus: {len(units)} Tier-A units under {os.path.relpath(cfg['corpus_dir'])}\n") + for cat in sorted(by_cat): + print(f" {cat} ({len(by_cat[cat])}):") + for u in by_cat[cat]: + print(f" - {u}") + + +def cmd_test(cfg, args): + units = args.units or discover_units(cfg) + npass = 0 + for u in units: + problems = test_unit(cfg, u) + if problems: + print(f" [FAIL] {u}") + for p in problems: + print(f" {p}") + else: + print(f" [ OK ] {u}") + npass += 1 + print(f"\n{npass}/{len(units)} units pass the faithfulness gate.") + return 0 if npass == len(units) else 1 + + +def cmd_run(cfg, args): + units = args.units or discover_units(cfg) + results = load_results(cfg["results"]) + for u in units: + mr = run_mengine(cfg, u, cfg["timeout"], cfg["trials"]) + rr = run_rocq(cfg, u, cfg["timeout"], cfg["trials"]) + results[f"mengine_{u}"] = mr + results[f"coq_{u}"] = rr + save_results(cfg["results"], results) + ms = f"{mr['time_taken']*1000:.1f}ms" if mr["success"] else "FAIL" + rs = f"{rr['time_taken']*1000:.1f}ms" if rr["success"] else "FAIL" + ratio = (f"{rr['time_taken']/mr['time_taken']:.2f}x" + if mr["success"] and rr["success"] and mr["time_taken"] > 0 else "-") + print(f" {u:24s} MEngine={ms:>10s} Rocq={rs:>10s} speedup={ratio}") + print(f"\nResults written to {os.path.relpath(cfg['results'])}") + + +def cmd_report(cfg, args): + import report + report.generate(cfg) + + +def cmd_manifest(cfg, args): + out, n = build_manifest(cfg) + print(f"Wrote {os.path.relpath(out)} ({n} units).") + + +def cmd_triage(cfg, args): + d = args.dir or cfg["rocq_stdlib_src"] + if not d or not os.path.isdir(d): + print(f"stdlib source dir not found: {d}", file=sys.stderr) + return 1 + subprocess.run(["python3", os.path.join(HERE, "translate.py"), "--report", "--dir", d]) + + +# ───────────────────────────── results io ──────────────────────────────────── + +def load_results(path): + if os.path.exists(path): + with open(path) as f: + return json.load(f) + return {} + + +def save_results(path, results): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w") as f: + json.dump(results, f, indent=2) + + +# ──────────────────────────────── main ─────────────────────────────────────── + +def main(): + cfg = load_config() + ap = argparse.ArgumentParser(description="Rocq-stdlib benchmark corpus runner") + sub = ap.add_subparsers(dest="command", required=True) + sub.add_parser("list") + p_test = sub.add_parser("test"); p_test.add_argument("units", nargs="*") + p_run = sub.add_parser("run"); p_run.add_argument("units", nargs="*") + sub.add_parser("report") + sub.add_parser("manifest") + p_tri = sub.add_parser("triage"); p_tri.add_argument("--dir") + args = ap.parse_args() + + dispatch = { + "list": cmd_list, "test": cmd_test, "run": cmd_run, + "report": cmd_report, "manifest": cmd_manifest, "triage": cmd_triage, + } + rc = dispatch[args.command](cfg, args) + sys.exit(rc or 0) + + +if __name__ == "__main__": + main() diff --git a/benchmarks/stdlib/translate.py b/benchmarks/stdlib/translate.py new file mode 100644 index 0000000..27d84f4 --- /dev/null +++ b/benchmarks/stdlib/translate.py @@ -0,0 +1,880 @@ +#!/usr/bin/env python3 +"""Mechanical Rocq (.v) -> MEngine (.me) translator for the stdlib benchmark. + +This is a *lightweight* sentence-aware rewriter with a small Pratt parser for +the term sublanguage, NOT a full Coq elaborator. Its guiding principle is +**flag, never guess**: any construct it cannot translate soundly is reported as +an `unhandled` item and (in unit mode) raises `Untranslatable`, so the unit is +excluded from Tier A rather than mistranslated. A wrong translation that happened +to compile would silently corrupt the benchmark, which is the worst outcome. + +Scope (Tier A): the computational/structural corners reachable by today's MEngine ++ compat prelude — Bool, ground Nat, the `le` order, and propositional logic. +Polymorphic list reasoning needs element-type inference and is deferred (Tier B). + +Usage: + translate.py unit.v # emit MEngine source on stdout + translate.py --report unit.v # print handled/unhandled construct summary + translate.py --report --dir D # report over every .v under D (triage) +""" + +import argparse +import os +import re +import sys + + +# ───────────────────────── symbol table (type synthesis) ───────────────────── +# +# Result type (as a MEngine type expression string) of each known head symbol, +# used to recover the implicit type argument of `=`/eq. Heads not listed here +# make eq-type inference fail -> the unit is flagged (never guessed). + +NAT_FUNCS = {"O", "S", "add", "mul", "sub"} +BOOL_FUNCS = {"true", "false", "negb", "andb", "orb", "implb", "xorb", + "eqb", "leb", "ltb"} +# Heads whose application yields a Prop (used to spot non-eq relational goals). +PROP_HEADS = {"le", "lt", "and", "or", "ex", "not", "iff", "True", "False"} + +# Infix/notation operators mapped to prefix heads. Each entry: +# symbol -> (mengine_head_or_None, binding_power, right_assoc, needs_type_arg) +# needs_type_arg: 'eq' for equality (type inferred from LHS), None otherwise. +BINOPS = { + "->": ("__arrow__", 5, True, None), + "/\\": ("and", 20, True, None), + "\\/": ("or", 15, True, None), + "=": ("eq", 30, False, "eq"), + "<=": ("le", 30, False, None), + "+": ("add", 50, False, None), + "-": ("sub", 50, False, None), + "*": ("mul", 60, False, None), +} + + +class Untranslatable(Exception): + def __init__(self, reason): + super().__init__(reason) + self.reason = reason + + +# ───────────────────────────── comment / sentence split ────────────────────── + +def strip_comments(text): + """Remove (* ... *) comments (nested), keeping newlines for line numbers.""" + out = [] + depth = 0 + i = 0 + n = len(text) + while i < n: + if text[i:i + 2] == "(*": + depth += 1 + i += 2 + elif text[i:i + 2] == "*)" and depth > 0: + depth -= 1 + i += 2 + elif depth > 0: + out.append("\n" if text[i] == "\n" else " ") + i += 1 + else: + out.append(text[i]) + i += 1 + return "".join(out) + + +def split_sentences(text): + """Split on '.' followed by whitespace/EOF, ignoring '..'/'...' and decimals.""" + sentences = [] + buf = [] + i = 0 + n = len(text) + while i < n: + c = text[i] + buf.append(c) + if c == ".": + nxt = text[i + 1] if i + 1 < n else " " + prev = text[i - 1] if i > 0 else " " + is_ellipsis = nxt == "." or prev == "." + is_decimal = prev.isdigit() and nxt.isdigit() + if not is_ellipsis and not is_decimal and (nxt.isspace() or i + 1 == n): + sentences.append("".join(buf).strip().rstrip(".")) + buf = [] + i += 1 + tail = "".join(buf).strip().rstrip(".") + if tail: + sentences.append(tail) + return [s for s in sentences if s] + + +# ───────────────────────────────── term lexer ──────────────────────────────── + +TOKEN_RE = re.compile(r""" + (?P\s+) + | (?P->) + | (?P/\\) + | (?P\\/) + | (?P<=) + | (?P=>) + | (?P[-=+*(),:]) + | (?P\d+) + | (?P[A-Za-z_][A-Za-z0-9_']*) +""", re.VERBOSE) + +KEYWORDS = {"forall", "fun", "match", "with", "end", "let", "in", "Prop", + "Type", "Set"} + + +class Tok: + def __init__(self, kind, val): + self.kind = kind + self.val = val + + def __repr__(self): + return f"Tok({self.kind},{self.val!r})" + + +def lex_term(s): + toks = [] + i = 0 + n = len(s) + while i < n: + m = TOKEN_RE.match(s, i) + if not m: + raise Untranslatable(f"unexpected character {s[i]!r} in term") + i = m.end() + kind = m.lastgroup + if kind == "ws": + continue + val = m.group() + if kind == "ident" and val in KEYWORDS: + toks.append(Tok(val, val)) + elif kind == "ident": + toks.append(Tok("ident", val)) + elif kind == "num": + toks.append(Tok("num", val)) + elif kind == "arrow": + toks.append(Tok("op", "->")) + elif kind == "andop": + toks.append(Tok("op", "/\\")) + elif kind == "orop": + toks.append(Tok("op", "\\/")) + elif kind == "dle": + toks.append(Tok("op", "<=")) + elif kind == "fatarrow": + toks.append(Tok("=>", "=>")) + elif kind == "sym": + if val in ("=", "+", "*", "-"): + toks.append(Tok("op", val)) + else: + toks.append(Tok(val, val)) + return toks + + +# ───────────────────────────────── AST ─────────────────────────────────────── +# Node forms (tuples): +# ("var", name) | ("app", f, a) | ("forall", var, ty, body) +# ("fun", var, ty, body) | ("op", sym, lhs, rhs) | ("arrow", a, b) +# ("num", int) + + +class Parser: + def __init__(self, toks): + self.toks = toks + self.pos = 0 + + def peek(self): + return self.toks[self.pos] if self.pos < len(self.toks) else Tok("eof", None) + + def next(self): + t = self.peek() + self.pos += 1 + return t + + def expect(self, kind): + t = self.next() + if t.kind != kind: + raise Untranslatable(f"expected {kind}, got {t.kind} {t.val!r}") + return t + + # binders: parse `(x y : T)` groups or bare `x y` (then ',' or ':') for + # forall/fun. Returns list of (name, type_or_None). + def parse_binders(self): + binders = [] + while True: + t = self.peek() + if t.kind == "(": + self.next() + names = [] + while self.peek().kind == "ident": + names.append(self.next().val) + if not names: + raise Untranslatable("empty binder group") + ty = None + if self.peek().kind == ":": + self.next() + ty = self.parse_expr(0) + self.expect(")") + for nm in names: + binders.append((nm, ty)) + elif t.kind == "ident": + # bare names until ':' or ',' (untyped binders need a later ':') + names = [] + while self.peek().kind == "ident": + names.append(self.next().val) + ty = None + if self.peek().kind == ":": + self.next() + ty = self.parse_expr(0) + for nm in names: + binders.append((nm, ty)) + break + else: + break + return binders + + def parse_atom(self): + t = self.peek() + if t.kind == "(": + self.next() + e = self.parse_expr(0) + self.expect(")") + return e + if t.kind == "forall": + self.next() + binders = self.parse_binders() + self.expect(",") + body = self.parse_expr(0) + node = body + for nm, ty in reversed(binders): + if ty is None: + raise Untranslatable("forall binder without a type annotation") + node = ("forall", nm, ty, node) + return node + if t.kind == "fun": + self.next() + binders = self.parse_binders() + self.expect("=>") + body = self.parse_expr(0) + node = body + for nm, ty in reversed(binders): + if ty is None: + raise Untranslatable("fun binder without a type annotation") + node = ("fun", nm, ty, node) + return node + if t.kind in ("match", "let"): + raise Untranslatable(f"'{t.kind}' in statement term (Tier B)") + if t.kind == "ident": + self.next() + return ("var", t.val) + if t.kind in ("Prop", "Type", "Set"): + self.next() + return ("var", "Prop" if t.kind == "Prop" else "Type") + if t.kind == "num": + self.next() + return ("num", int(t.val)) + raise Untranslatable(f"unexpected token {t.kind} {t.val!r} in term") + + def parse_app(self): + node = self.parse_atom() + while self.peek().kind in ("ident", "(", "num", "Prop", "Type", "Set"): + arg = self.parse_atom() + node = ("app", node, arg) + return node + + def parse_expr(self, min_bp): + left = self.parse_app() + while True: + t = self.peek() + if t.kind != "op": + break + sym = t.val + head, bp, right_assoc, _ = BINOPS[sym] + if bp < min_bp: + break + self.next() + next_min = bp if right_assoc else bp + 1 + right = self.parse_expr(next_min) + if sym == "->": + left = ("arrow", left, right) + else: + left = ("op", sym, left, right) + return left + + +def parse_term(s): + toks = lex_term(s) + p = Parser(toks) + e = p.parse_expr(0) + if p.peek().kind != "eof": + raise Untranslatable(f"trailing tokens after term: {p.peek().val!r}") + return e + + +# ───────────────────────────── type synthesis ──────────────────────────────── +# Minimal bottom-up synthesizer over {nat, bool, Prop, Type} used only to +# recover the implicit type argument of eq. Returns a type string or raises. + +def head_symbol(node): + while node[0] == "app": + node = node[1] + return node + + +def synth_type(node, env): + """Return 'nat' | 'bool' | 'Prop' for node, or raise Untranslatable.""" + if node[0] == "num": + return "nat" + if node[0] == "var": + nm = node[1] + if nm in env: + return env[nm] + if nm in NAT_FUNCS: + return "nat" + if nm in BOOL_FUNCS: + return "bool" + if nm in PROP_HEADS: + return "Prop" + raise Untranslatable(f"cannot synthesize type of '{nm}' for eq") + if node[0] in ("op", "arrow"): + if node[0] == "arrow" or node[1] in ("/\\", "\\/", "="): + return "Prop" + if node[1] == "<=": + return "Prop" + if node[1] in ("+", "-", "*"): + return "nat" + if node[0] == "app": + h = head_symbol(node) + if h[0] == "var": + nm = h[1] + if nm in env: + return env[nm] + if nm in NAT_FUNCS: + return "nat" + if nm in BOOL_FUNCS: + return "bool" + if nm in PROP_HEADS: + return "Prop" + raise Untranslatable("cannot synthesize type of application for eq") + raise Untranslatable("cannot synthesize type for eq argument") + + +# ───────────────────────────── pretty printer ──────────────────────────────── + +def emit(node, env): + """Render an AST node as fully-parenthesized MEngine prefix syntax.""" + kind = node[0] + if kind == "var": + return node[1] + if kind == "num": + out = "O" + for _ in range(node[1]): + out = f"(S {out})" + return out + if kind == "app": + f = emit(node[1], env) + a = emit(node[2], env) + return f"({f} {a})" + if kind == "arrow": + a = emit(node[1], env) + b = emit(node[2], env) + return f"forall (_ : {a}), {b}" + if kind == "forall": + nm, ty, body = node[1], node[2], node[3] + ty_s = emit(ty, env) + env2 = dict(env) + env2[nm] = type_to_synth(ty) + return f"forall ({nm} : {ty_s}), {emit(body, env2)}" + if kind == "fun": + nm, ty, body = node[1], node[2], node[3] + ty_s = emit(ty, env) + env2 = dict(env) + env2[nm] = type_to_synth(ty) + return f"fun ({nm} : {ty_s}) => {emit(body, env2)}" + if kind == "op": + sym, lhs, rhs = node[1], node[2], node[3] + head = BINOPS[sym][0] + if sym == "=": + ty = synth_type(lhs, env) + return f"(((eq {ty}) {emit(lhs, env)}) {emit(rhs, env)})" + return f"(({head} {emit(lhs, env)}) {emit(rhs, env)})" + raise Untranslatable(f"cannot emit node {kind}") + + +def type_to_synth(ty_node): + """Map a (already-parsed) type expression to a synth tag for env tracking.""" + if ty_node[0] == "var": + if ty_node[1] in ("nat", "bool"): + return ty_node[1] + if ty_node[1] in ("Prop", "Type", "Set"): + return "Prop" + return "?" # unknown; eq-synth will fail loudly if it matters + + +def translate_term(src): + return emit(parse_term(src.strip()), {}) + + +# ──────────────────────────── command translation ──────────────────────────── + +DROP_PREFIXES = ("Require", "Import", "Export", "Open", "Close", "Set", "Unset", + "Hint", "Arguments", "Local", "Global", "#[", "Scope", + "Declare", "Generalizable", "Print", "Check", "Search", + "Comments", "Section", "End", "Module", "Include") + +PROOF_FRAMING = ("Proof", "Qed", "Defined", "Admitted", "Abort") + + +def translate_definition(sentence, report): + """Definition/Lemma/Theorem/Example name [binders] : type [:= body].""" + m = re.match(r"^(Definition|Lemma|Theorem|Example|Corollary|Fact|Remark|Proposition)\s+" + r"([A-Za-z_][A-Za-z0-9_']*)\s*(.*)$", sentence, re.S) + if not m: + raise Untranslatable("unrecognized definition form") + kw, name, rest = m.group(1), m.group(2), m.group(3) + is_thm = kw in ("Lemma", "Theorem", "Example", "Corollary", "Fact", + "Remark", "Proposition") + + # Separate optional binders + ': type' + optional ':= body'. + body = None + if ":=" in rest: + head, body = rest.split(":=", 1) + else: + head = rest + if ":" not in head: + raise Untranslatable("definition without ': type'") + binders_src, type_src = head.split(":", 1) + + binders = _parse_binder_src(binders_src) + type_node = parse_term(type_src.strip()) + # Wrap explicit binders as leading foralls (for type) / funs (for body). + env = {} + type_full = type_node + for nm, ty in reversed(binders): + type_full = ("forall", nm, ty, type_full) + type_out = emit(type_full, env) + + if is_thm: + report.add_handled(kw) + return f"Theorem {name} : {type_out}." + # Definition with a body. + if body is None: + report.add_handled("Definition(no body)") + return f"Axiom {name} : {type_out}." + body_node = parse_term(body.strip()) + body_full = body_node + for nm, ty in reversed(binders): + body_full = ("fun", nm, ty, body_full) + report.add_handled("Definition") + return f"Definition {name} : {type_out} := {emit(body_full, env)}." + + +def _parse_binder_src(src): + src = src.strip() + if not src: + return [] + p = Parser(lex_term(src)) + binders = p.parse_binders() + if p.peek().kind != "eof": + raise Untranslatable("could not parse definition binders") + for nm, ty in binders: + if ty is None: + raise Untranslatable(f"binder '{nm}' lacks a type annotation") + return binders + + +def translate_axiom(sentence, report): + m = re.match(r"^(Axiom|Parameter|Conjecture|Variable|Hypothesis)\s+" + r"([A-Za-z_][A-Za-z0-9_']*)\s*:\s*(.*)$", sentence, re.S) + if not m: + raise Untranslatable("unrecognized axiom form") + name, type_src = m.group(2), m.group(3) + report.add_handled("Axiom") + return f"Axiom {name} : {translate_term(type_src)}." + + +def is_dropped(sentence): + for p in DROP_PREFIXES: + if sentence.startswith(p): + return True + return False + + +def is_framing(sentence): + word = re.match(r"^([A-Za-z]+)", sentence) + return bool(word and word.group(1) in PROOF_FRAMING) + + +# ──────────────────────────── tactic translation ───────────────────────────── +# +# Token-level mapping of a single Rocq tactic atom (no ';') to MEngine. Unknown +# tactics hard-stop (Untranslatable) so the unit leaves Tier A. + +# Inductive data for the induction/destruct scaffold (compat-prelude types). +# Each case: (constructor, [recursive-arg flags]); a True flag means the arg is +# recursive and carries an induction hypothesis in _ind. +INDUCTIVES = { + "bool": {"ind": "bool_ind", "cases": [("true", []), ("false", [])]}, + "nat": {"ind": "nat_ind", "cases": [("O", []), ("S", [True])]}, +} + +DIRECT_TACTICS = { + "reflexivity", "assumption", "split", "left", "right", "auto", + "constructor", "simpl", "symmetry", "trivial", "easy", "idtac", +} + +UNSUPPORTED = { + "lia", "ring", "omega", "nia", "field", "auto with", "eauto", "inversion", + "congruence", "f_equal", "unfold", "fold", "replace", "generalize", + "revert", "case", "pose", "set", "specialize", "discriminate", "injection", + "contradiction", "exfalso", "cbn", "red", "change", "rename", "clear", + "remember", "induction'", "dependent", "functional", "decide", "destruct'", +} + + +def translate_tactic_atom(atom, report): + atom = atom.strip() + if not atom: + return None + head = re.match(r"^([A-Za-z_][A-Za-z0-9_']*)", atom) + name = head.group(1) if head else None + + if name in UNSUPPORTED: + raise Untranslatable(f"unsupported tactic '{name}'") + + if name in DIRECT_TACTICS: + rest = atom[len(name):].strip() + if rest: + raise Untranslatable(f"tactic '{name}' with unexpected arguments") + report.add_handled(f"tac:{name}") + return name + + if name in ("intro", "intros"): + args = atom[len(name):].strip() + report.add_handled("tac:intro") + if not args: + return "intros" if name == "intros" else "intro" + names = args.split() + return " ".join(f"intro {nm}." for nm in names).rstrip(".") + + if name in ("apply", "eapply"): + arg = atom[len(name):].strip() + if " in " in f" {arg} " or arg.endswith(" in"): + raise Untranslatable("apply ... in H (Tier B)") + report.add_handled(f"tac:{name}") + return f"{name} ({translate_term(arg)})" + + if name == "exact": + arg = atom[len("exact"):].strip() + report.add_handled("tac:exact") + return f"exact ({translate_term(arg)})" + + if name in ("exists",): + arg = atom[len("exists"):].strip() + report.add_handled("tac:exists") + return f"exists ({translate_term(arg)})" + + if name == "rewrite": + rest = atom[len("rewrite"):].strip() + if rest.startswith("<-"): + raise Untranslatable("rewrite <- (Tier B: rewrite_s is forward-only)") + if " in " in f" {rest} ": + raise Untranslatable("rewrite ... in H (Tier B)") + report.add_handled("tac:rewrite") + return f"rewrite_s ({translate_term(rest)})" + + if name in ("now",): + # now tac ≈ tac; easy. Bare 'now' is unusual; treat 'now' alone as easy. + rest = atom[len("now"):].strip() + report.add_handled("tac:now") + if not rest: + return "easy" + inner = translate_tactic_atom(rest, report) + return f"{inner}; easy" + + raise Untranslatable(f"unknown tactic '{name or atom}'") + + +def translate_tactic_sentence(sentence, report): + """Translate one Rocq tactic sentence (may contain ';') -> MEngine, '.'-ended.""" + parts = _split_semicolons(sentence) + out = [] + for part in parts: + t = translate_tactic_atom(part, report) + if t is not None: + out.append(t) + if not out: + return "" + return "; ".join(out) + "." + + +def _split_semicolons(s): + """Split on top-level ';' (not inside brackets/parens).""" + parts = [] + depth = 0 + buf = [] + for c in s: + if c in "([{": + depth += 1 + elif c in ")]}": + depth -= 1 + if c == ";" and depth == 0: + parts.append("".join(buf)) + buf = [] + else: + buf.append(c) + parts.append("".join(buf)) + return [p.strip() for p in parts if p.strip()] + + +# ──────────────────────────── proof translation ────────────────────────────── + +def parse_statement_leading_binder(type_out): + """From an emitted 'forall (x : T), Body' string, return (x, T, Body) or None.""" + m = re.match(r"^forall \(([A-Za-z_][A-Za-z0-9_']*) : ([^,]+)\), (.*)$", + type_out, re.S) + if not m: + return None + return m.group(1), m.group(2).strip(), m.group(3).strip() + + +def translate_proof(proof_sentences, stmt_type_out, report): + """Translate a list of Rocq proof tactic-sentences into MEngine proof lines. + + Handles two shapes: + (A) straight-line tactic sequence (no induction/destruct); + (B) a leading `induction x` / `destruct x` on the goal's leading binder, + with bullet- or position-segmented cases (nat/bool only). + Anything else is flagged (Untranslatable).""" + # Drop proof framing sentences. + body = [s for s in proof_sentences if not is_framing(s)] + if not body: + raise Untranslatable("empty proof") + + first = body[0].strip() + m = re.match(r"^(induction|destruct)\s+([A-Za-z_][A-Za-z0-9_']*)\s*$", first) + if m: + return _translate_induction(m.group(1), m.group(2), body[1:], + stmt_type_out, report) + + # Shape (A): straight-line. Forbid any later induction/destruct. + for s in body: + if re.match(r"^(induction|destruct)\b", s.strip()): + raise Untranslatable("induction/destruct not as first tactic (Tier B)") + lines = [] + for s in body: + t = translate_tactic_sentence(s, report) + if t: + lines.append(t) + return lines + + +def _segment_cases(case_sentences, ncases): + """Segment proof remainder into per-case sentence lists, by '-' bullets or + (fallback) one sentence per case.""" + bulleted = any(s.lstrip().startswith(("-", "+", "*")) for s in case_sentences) + if bulleted: + cases = [] + cur = None + for s in case_sentences: + st = s.lstrip() + if st.startswith(("-", "+", "*")): + if cur is not None: + cases.append(cur) + cur = [st.lstrip("-+* ").strip()] + else: + if cur is None: + raise Untranslatable("proof text before first bullet") + cur.append(s) + if cur is not None: + cases.append(cur) + return cases + # Fallback: require exactly one sentence per case. + if len(case_sentences) != ncases: + raise Untranslatable( + f"cannot segment {len(case_sentences)} sentences into {ncases} cases " + "(use '-' bullets)") + return [[s] for s in case_sentences] + + +def _translate_induction(kind, var, remainder, stmt_type_out, report): + lb = parse_statement_leading_binder(stmt_type_out) + if lb is None: + raise Untranslatable("induction/destruct goal has no leading forall binder") + x, ty, body = lb + if x != var: + raise Untranslatable( + f"{kind} on '{var}' but leading binder is '{x}' (Tier B)") + if ty not in INDUCTIVES: + raise Untranslatable(f"{kind} on type '{ty}' not supported (nat/bool only)") + info = INDUCTIVES[ty] + motive = f"fun ({x} : {ty}) => {body}" + + lines = [f"intro {x}.", f"apply ({info['ind']} ({motive}))."] + cases = _segment_cases(remainder, len(info["cases"])) + if len(cases) != len(info["cases"]): + raise Untranslatable( + f"{kind}: {len(cases)} cases provided, expected {len(info['cases'])}") + + report.add_handled(f"tac:{kind}") + for (ctor, rec_flags), case_body in zip(info["cases"], cases): + case_lines = ["simpl."] + if kind == "induction": + # Introduce recursive args and their IHs (named like Rocq: IH). + for _ in rec_flags: + case_lines.append(f"intro {x}.") + case_lines.append(f"intro IH{x}.") + for s in case_body: + t = translate_tactic_sentence(s, report) + if t: + case_lines.append(t) + lines.extend(case_lines) + return lines + + +# ─────────────────────────────── report ────────────────────────────────────── + +class Report: + def __init__(self): + self.handled = {} + self.unhandled = [] + + def add_handled(self, key): + self.handled[key] = self.handled.get(key, 0) + 1 + + def add_unhandled(self, reason): + self.unhandled.append(reason) + + def ok(self): + return not self.unhandled + + +# ─────────────────────────── unit-level translation ────────────────────────── + +def translate_unit(text, report): + """Translate a whole .v unit to a MEngine source string, or raise.""" + text = strip_comments(text) + sentences = split_sentences(text) + + out_lines = [] + i = 0 + n = len(sentences) + while i < n: + s = sentences[i].strip() + if not s: + i += 1 + continue + word = re.match(r"^([A-Za-z_#\[]+)", s) + kw = word.group(1) if word else "" + + if is_dropped(s): + i += 1 + continue + if kw in ("Inductive", "Fixpoint", "CoFixpoint"): + raise Untranslatable(f"'{kw}' in unit (define it in the compat prelude)") + if kw in ("Axiom", "Parameter", "Conjecture", "Variable", "Hypothesis"): + out_lines.append(translate_axiom(s, report)) + i += 1 + continue + if kw in ("Definition",): + out_lines.append(translate_definition(s, report)) + i += 1 + continue + if kw in ("Lemma", "Theorem", "Example", "Corollary", "Fact", "Remark", + "Proposition"): + stmt = translate_definition(s, report) + out_lines.append(stmt) + # Collect the proof: subsequent sentences until Qed/Defined/Admitted/Abort. + type_out = stmt[len(f"Theorem "):].split(" : ", 1)[1].rstrip(".") + proof = [] + i += 1 + while i < n: + ps = sentences[i].strip() + proof.append(ps) + w = re.match(r"^([A-Za-z]+)", ps) + if w and w.group(1) in ("Qed", "Defined", "Admitted", "Abort"): + break + i += 1 + if any(re.match(r"^Admitted", p) or re.match(r"^Abort", p) for p in proof): + raise Untranslatable("proof is Admitted/Abort") + proof_lines = translate_proof(proof, type_out, report) + out_lines.extend(proof_lines) + i += 1 + continue + # Unknown command. + raise Untranslatable(f"unrecognized command '{kw}'") + + return "\n".join(out_lines) + "\n" + + +# ─────────────────────────────── statement digest ──────────────────────────── + +def statement_digests(mengine_src): + """Normalized digest of each Theorem statement, for correspondence checking.""" + digs = [] + for line in mengine_src.splitlines(): + m = re.match(r"^Theorem\s+(\S+)\s*:\s*(.*)\.$", line) + if m: + norm = re.sub(r"\s+", " ", m.group(2)).strip() + digs.append((m.group(1), norm)) + return digs + + +# ─────────────────────────────────── main ──────────────────────────────────── + +def report_over_file(path): + with open(path) as f: + text = f.read() + report = Report() + try: + translate_unit(text, report) + status = "OK" + except Untranslatable as e: + status = f"FLAG: {e.reason}" + return status, report + + +def main(): + ap = argparse.ArgumentParser(description="Rocq .v -> MEngine .me translator") + ap.add_argument("input", nargs="?", help="input .v file") + ap.add_argument("--report", action="store_true", help="report handled/unhandled") + ap.add_argument("--dir", help="report over every .v under this directory") + args = ap.parse_args() + + if args.dir: + rows = [] + for root, _dirs, files in os.walk(args.dir): + for fn in sorted(files): + if fn.endswith(".v"): + p = os.path.join(root, fn) + status, _rep = report_over_file(p) + rows.append((os.path.relpath(p, args.dir), status)) + ok = sum(1 for _, s in rows if s == "OK") + for rel, status in rows: + print(f" [{'OK ' if status == 'OK' else 'FLAG'}] {rel}: {status}") + print(f"\n{ok}/{len(rows)} files fully translatable (Tier A).") + return + + if not args.input: + ap.error("input file required (or use --dir)") + + with open(args.input) as f: + text = f.read() + report = Report() + try: + src = translate_unit(text, report) + except Untranslatable as e: + if args.report: + print(f"FLAG: {e.reason}") + sys.exit(2) + sys.stderr.write(f"Untranslatable: {e.reason}\n") + sys.exit(2) + + if args.report: + print(f"OK ({args.input})") + for k in sorted(report.handled): + print(f" handled {k}: {report.handled[k]}") + else: + sys.stdout.write(src) + + +if __name__ == "__main__": + main() From 334516a5900d2f1c066e8a536d0bc1d3a4a0697c Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sun, 28 Jun 2026 00:36:11 +0000 Subject: [PATCH 05/71] Elaborate stdlib statement types via Rocq Set Printing All The surface translator had to desugar notation and *synthesise* the implicit type argument of `=`, which failed for any non-nat/bool equality and for polymorphic lists (eq-type inference and list element types were the cases we'd given up on). Replace this for statements: `translate.py --elaborate` replays the unit through Rocq with `Set Printing All` + one `Check` per statement and translates the fully-explicit, notation-free type Rocq prints. Every implicit (the `T` of `@eq T x y`, a list's element type) is already supplied, so no synthesis is needed and notation/literals are gone. - New elaborated-form parser (lex_elab/ElabParser/parse_elab) over the Set Printing All grammar (`@head`, qualified names, multi-binder forall/fun, `->`); reuses the existing `emit`. Only Nat.* heads are qualified and are mapped to compat names; other qualified heads are flagged, never guessed. - rocq_elaborate(): temp .v + coqc, parse Check output to {name: type}. - translate_definition/translate_axiom/translate_unit take an `elab` map; tactic terms and `--report`/`--dir` triage stay on the surface path (terms in tactics aren't always elaborable). - stdlib_bench.py `test` runs the translator with --elaborate --coq. - Drop `From ... Require` so list units' preambles strip cleanly. All 26 Tier-A units regenerate byte-identically through the new path and pass the faithfulness gate. Docs (PLAN/README) updated. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/PLAN.md | 18 +- benchmarks/stdlib/README.md | 41 +++- benchmarks/stdlib/stdlib_bench.py | 6 +- benchmarks/stdlib/translate.py | 355 +++++++++++++++++++++++++++--- 4 files changed, 386 insertions(+), 34 deletions(-) diff --git a/benchmarks/stdlib/PLAN.md b/benchmarks/stdlib/PLAN.md index 2a16ebc..d189709 100644 --- a/benchmarks/stdlib/PLAN.md +++ b/benchmarks/stdlib/PLAN.md @@ -5,7 +5,8 @@ the realized scope; the per-proof mode (phase 6) remains optional/future. The sections below are the original design; where the implementation refined a decision (e.g. corpus drawn from curated single-lemma units rather than whole stdlib files, two targeted kernel fixes landed, the symbolic-fixpoint induction -crash deferred), `README.md` is authoritative. +crash deferred, **statement types elaborated through Rocq's `Set Printing All` +instead of hand-desugared** — see §5), `README.md` is authoritative. Scope decisions locked with the user: - **Granularity:** per-file first; per-proof is a later optional phase. @@ -109,7 +110,20 @@ on `.` respecting `(* *)` comments and strings), not a full Coq parser. Ordered - Remove `Proof.`/`Qed.`/`Defined.`/`Admitted.` framing (MEngine ends a proof when goals close). - `Parameter`/`Conjecture` -> `Axiom`. -**Term desugaring (the bulk):** +> **Refinement (implemented): statement types via `Set Printing All`.** The +> term-desugaring rules below describe the original *surface* rewriter, which had +> to synthesise the implicit type argument of `=` (failing for any non-`nat`/`bool` +> equality and for polymorphic lists). `translate.py --elaborate` instead replays +> the unit through Rocq (`Set Printing All` + one `Check` per statement) and +> translates the **fully-explicit, notation-free** type Rocq prints — every +> implicit argument, including the `T` of `@eq T x y` and a list's element type, +> is already supplied, so no synthesis is needed and notation/literals are gone. +> This applies to definition and theorem *statements*; the surface rules still +> govern *tactic* terms (not always elaborable) and the `--report`/`--dir` triage +> mode (over uncompilable stdlib files). See `README.md` → "How statements are +> translated". + +**Term desugaring (the bulk) — surface rewriter, superseded for statements:** - Non-dependent `A -> B` -> `forall (_ : A), B`. - Multi-binder expansion: `forall x y z, P` / `forall (x y : T), P` -> nested single binders; same for `fun`. diff --git a/benchmarks/stdlib/README.md b/benchmarks/stdlib/README.md index 601872b..47971f4 100644 --- a/benchmarks/stdlib/README.md +++ b/benchmarks/stdlib/README.md @@ -13,6 +13,32 @@ Each *unit* is one stdlib-style lemma in two forms: - `corpus//mengine.me` — the MEngine source, produced **mechanically** by [`translate.py`](translate.py) with zero manual edits (Tier A). +### How statements are translated (`Set Printing All`) + +MEngine has no notation system and no elaboration, so notation, implicit +arguments, and numeric literals in a Rocq statement must all be made explicit. +Rather than re-implement Rocq's elaborator (the old translator hand-desugared +notation and *synthesised* the implicit type argument of `=`, which failed for +any non-`nat`/`bool` equality and for polymorphic lists), `translate.py +--elaborate` replays the unit through Rocq with `Set Printing All` and a `Check` +per statement, and translates the **fully-explicit, notation-free** form it +prints back: + +``` +(* surface *) forall (A:Type) (l:list A), nil ++ l = l +(* Printing All *) forall (A : Type) (l : list A), @eq (list A) (@app A (@nil A) l) l +(* MEngine *) forall (A : Type), forall (l : (list A)), + (((eq (list A)) (((app A) (nil A)) l)) l) +``` + +Because every implicit is already supplied, no type synthesis is needed: the +element type of an equality or a list comes straight from Rocq. The only +qualified heads `Set Printing All` emits are the `Nat.*` arithmetic ops, mapped +to the compat-prelude names; any *other* qualified head is flagged, never +guessed. This covers **definition and theorem statements**; terms appearing +inside *tactics* are still translated from the surface source (they are not +always elaborable), which is why the proof side keeps the surface rules below. + `stdlib_bench.py run` times each unit end-to-end in both engines (whole process, best of N trials). Both engines pay their own fixed startup: MEngine loads `prelude/tactics.me` plus the compat prelude; Rocq starts its process and loads @@ -58,8 +84,10 @@ Before any timing, `test` verifies per unit (plan §7): 1. `rocq.v` compiles under `coqc`. 2. `mengine.me` runs clean under `mengine -q` (compat prelude prepended). -3. `mengine.me` is exactly what `translate.py` re-emits from `rocq.v` (no drift), - and the **theorem names match** between the two sides. +3. `mengine.me` is exactly what `translate.py --elaborate` re-emits from `rocq.v` + (no drift), and the **theorem names match** between the two sides. Because + step 3 elaborates through Rocq (`Set Printing All`), the gate needs `coqc` on + `PATH` (or `coq_path` in `config.json`). The guiding principle of the translator is **flag, never guess**: any construct it cannot translate soundly is reported and the unit is excluded, rather than @@ -103,8 +131,13 @@ What stays **out of Tier A** (documented in `corpus/manifest.json` → `excluded first-order `apply` cannot solve the eliminator's scrutinee evar when the scrutinee variable has no *bare* occurrence in the goal, because the unifier whnf-folds the function application into a stuck `match`. -- **Polymorphic list reasoning** (`app`/`length` lemmas): needs element-type - inference in the translator (Tier B). +- **Polymorphic list reasoning** (`app`/`length` lemmas): the *statement* now + translates faithfully — `Set Printing All` supplies the element type, so + `nil ++ l = l` becomes `eq (list A) (app A (nil A) l) l` with no inference (the + old element-type-inference blocker is gone). What still keeps these out of + Tier A is the *proof/engine* side: reducing a parametric `Fixpoint` (`app`) + over `list A` hits the same symbolic-fixpoint conversion limits as + computational `nat` induction below. ## Why whole stdlib files don't translate diff --git a/benchmarks/stdlib/stdlib_bench.py b/benchmarks/stdlib/stdlib_bench.py index 7dfa495..d3721e8 100644 --- a/benchmarks/stdlib/stdlib_bench.py +++ b/benchmarks/stdlib/stdlib_bench.py @@ -204,8 +204,10 @@ def test_unit(cfg, name): problems.append(f"mengine.me fails: {mr['error'][:120]}") # 3. mengine.me re-derivable from rocq.v by the translator (no drift), and - # statement names correspond. - tr = subprocess.run(["python3", os.path.join(HERE, "translate.py"), vpath], + # statement names correspond. Statement types are elaborated through Rocq + # (`Set Printing All`), so the gate runs the translator the same way. + tr = subprocess.run(["python3", os.path.join(HERE, "translate.py"), + "--elaborate", "--coq", cfg["coq_path"], vpath], capture_output=True, text=True) if tr.returncode != 0: problems.append(f"translator flags rocq.v: {tr.stderr.strip()[:120]}") diff --git a/benchmarks/stdlib/translate.py b/benchmarks/stdlib/translate.py index 27d84f4..f2100e4 100644 --- a/benchmarks/stdlib/translate.py +++ b/benchmarks/stdlib/translate.py @@ -21,7 +21,9 @@ import argparse import os import re +import subprocess import sys +import tempfile # ───────────────────────── symbol table (type synthesis) ───────────────────── @@ -309,6 +311,187 @@ def parse_term(s): return e +# ─────────────────── elaborated-form parser (Set Printing All) ──────────────── +# +# When a unit is elaborated through Rocq (`--elaborate`), each statement's type +# is obtained from `Set Printing All` output instead of the surface source. That +# form is *notation-free and fully explicit*: every implicit argument is shown, +# `@head` marks an application with all implicits supplied, numerals are expanded +# to `O`/`S`, and the only qualified heads are the `Nat.*` arithmetic ops. +# Translating this form needs neither the notation table nor eq-type synthesis — +# the implicit type arguments (e.g. the `T` of `@eq T x y`, the element type of a +# list) are already present — which is exactly why statements and definition +# types are routed through Rocq first. See PLAN/README. + +# Qualified heads that `Set Printing All` emits, mapped to compat-prelude names. +# Any *other* dotted (qualified) head is flagged, never guessed. +NAME_MAP = { + "Nat.add": "add", "Nat.mul": "mul", "Nat.sub": "sub", + "Nat.eqb": "eqb", "Nat.leb": "leb", "Nat.ltb": "ltb", +} + +ELAB_TOKEN_RE = re.compile(r""" + (?P\s+) + | (?P->) + | (?P=>) + | (?P@) + | (?P[(),:]) + | (?P[A-Za-z_][A-Za-z0-9_']*(?:\.[A-Za-z_][A-Za-z0-9_']*)*) +""", re.VERBOSE) + +ELAB_KEYWORDS = {"forall", "fun", "match", "with", "end", "return", "in", + "let", "as", "Prop", "Type", "Set", "SProp"} + + +def lex_elab(s): + toks = [] + i, n = 0, len(s) + while i < n: + m = ELAB_TOKEN_RE.match(s, i) + if not m: + raise Untranslatable(f"unexpected character {s[i]!r} in elaborated term") + i = m.end() + kind = m.lastgroup + if kind == "ws": + continue + val = m.group() + if kind == "arrow": + toks.append(Tok("op", "->")) + elif kind == "fatarrow": + toks.append(Tok("=>", "=>")) + elif kind == "at": + toks.append(Tok("@", "@")) + elif kind == "sym": + toks.append(Tok(val, val)) + elif kind == "qident": + toks.append(Tok(val if val in ELAB_KEYWORDS else "ident", val)) + return toks + + +def _map_name(name): + if name in NAME_MAP: + return NAME_MAP[name] + if "." in name: + raise Untranslatable(f"unmapped qualified name '{name}' in elaborated term") + return name + + +class ElabParser: + """Recursive-descent parser for `Set Printing All` term syntax. Emits the + same AST tuples (`var`/`app`/`forall`/`fun`/`arrow`) that ``emit`` renders.""" + + def __init__(self, toks): + self.toks = toks + self.pos = 0 + + def peek(self): + return self.toks[self.pos] if self.pos < len(self.toks) else Tok("eof", None) + + def next(self): + t = self.peek() + self.pos += 1 + return t + + def expect(self, kind): + t = self.next() + if t.kind != kind: + raise Untranslatable(f"elaborated: expected {kind}, got {t.kind} {t.val!r}") + return t + + def parse_binders(self): + """Parse `(x y : T) (z : U)` groups or a single bare `x : T`.""" + binders = [] + while True: + t = self.peek() + if t.kind == "(": + self.next() + names = [] + while self.peek().kind == "ident": + names.append(self.next().val) + if not names: + raise Untranslatable("elaborated: empty binder group") + self.expect(":") + ty = self.parse_expr() + self.expect(")") + for nm in names: + binders.append((nm, ty)) + elif t.kind == "ident": + names = [] + while self.peek().kind == "ident": + names.append(self.next().val) + self.expect(":") + ty = self.parse_expr() + for nm in names: + binders.append((nm, ty)) + break + else: + break + return binders + + def parse_atom(self): + t = self.peek() + if t.kind == "@": + self.next() + return ("var", _map_name(self.expect("ident").val)) + if t.kind == "(": + self.next() + e = self.parse_expr() + self.expect(")") + return e + if t.kind == "ident": + self.next() + return ("var", _map_name(t.val)) + if t.kind in ("Prop", "Type", "Set", "SProp"): + self.next() + return ("var", "Type" if t.kind == "Type" else "Prop") + if t.kind in ("match", "let", "fix"): + raise Untranslatable(f"'{t.kind}' in elaborated statement type (Tier B)") + raise Untranslatable(f"elaborated: unexpected token {t.kind} {t.val!r}") + + def parse_app(self): + node = self.parse_atom() + while self.peek().kind in ("ident", "@", "(", "Prop", "Type", "Set", "SProp"): + node = ("app", node, self.parse_atom()) + return node + + def parse_expr(self): + t = self.peek() + if t.kind == "forall": + self.next() + binders = self.parse_binders() + self.expect(",") + node = self.parse_expr() + for nm, ty in reversed(binders): + node = ("forall", nm, ty, node) + return node + if t.kind == "fun": + self.next() + binders = self.parse_binders() + self.expect("=>") + node = self.parse_expr() + for nm, ty in reversed(binders): + node = ("fun", nm, ty, node) + return node + left = self.parse_app() + if self.peek().kind == "op" and self.peek().val == "->": + self.next() + return ("arrow", left, self.parse_expr()) # '->' is right-associative + return left + + +def parse_elab(s): + p = ElabParser(lex_elab(s)) + e = p.parse_expr() + if p.peek().kind != "eof": + raise Untranslatable(f"elaborated: trailing tokens {p.peek().val!r}") + return e + + +def translate_elab_type(type_str): + """Render a `Set Printing All` type string as MEngine prefix syntax.""" + return emit(parse_elab(type_str), {}) + + # ───────────────────────────── type synthesis ──────────────────────────────── # Minimal bottom-up synthesizer over {nat, bool, Prop, Type} used only to # recover the implicit type argument of eq. Returns a type string or raises. @@ -415,16 +598,21 @@ def translate_term(src): # ──────────────────────────── command translation ──────────────────────────── -DROP_PREFIXES = ("Require", "Import", "Export", "Open", "Close", "Set", "Unset", - "Hint", "Arguments", "Local", "Global", "#[", "Scope", +DROP_PREFIXES = ("Require", "From", "Import", "Export", "Open", "Close", "Set", + "Unset", "Hint", "Arguments", "Local", "Global", "#[", "Scope", "Declare", "Generalizable", "Print", "Check", "Search", "Comments", "Section", "End", "Module", "Include") PROOF_FRAMING = ("Proof", "Qed", "Defined", "Admitted", "Abort") -def translate_definition(sentence, report): - """Definition/Lemma/Theorem/Example name [binders] : type [:= body].""" +def translate_definition(sentence, report, elab=None): + """Definition/Lemma/Theorem/Example name [binders] : type [:= body]. + + When ``elab`` carries an elaborated type for ``name`` (``--elaborate`` mode), + the statement type is taken from Rocq's `Set Printing All` output rather than + parsed from the surface source; a Definition *body* is still translated from + the surface (terms are not always elaborable).""" m = re.match(r"^(Definition|Lemma|Theorem|Example|Corollary|Fact|Remark|Proposition)\s+" r"([A-Za-z_][A-Za-z0-9_']*)\s*(.*)$", sentence, re.S) if not m: @@ -432,6 +620,7 @@ def translate_definition(sentence, report): kw, name, rest = m.group(1), m.group(2), m.group(3) is_thm = kw in ("Lemma", "Theorem", "Example", "Corollary", "Fact", "Remark", "Proposition") + have_elab = elab is not None and name in elab # Separate optional binders + ': type' + optional ':= body'. body = None @@ -439,18 +628,19 @@ def translate_definition(sentence, report): head, body = rest.split(":=", 1) else: head = rest - if ":" not in head: - raise Untranslatable("definition without ': type'") - binders_src, type_src = head.split(":", 1) - - binders = _parse_binder_src(binders_src) - type_node = parse_term(type_src.strip()) - # Wrap explicit binders as leading foralls (for type) / funs (for body). - env = {} - type_full = type_node - for nm, ty in reversed(binders): - type_full = ("forall", nm, ty, type_full) - type_out = emit(type_full, env) + + binders = None + if have_elab: + type_out = translate_elab_type(elab[name]) + else: + if ":" not in head: + raise Untranslatable("definition without ': type'") + binders_src, type_src = head.split(":", 1) + binders = _parse_binder_src(binders_src) + type_full = parse_term(type_src.strip()) + for nm, ty in reversed(binders): # explicit binders -> leading foralls + type_full = ("forall", nm, ty, type_full) + type_out = emit(type_full, {}) if is_thm: report.add_handled(kw) @@ -459,12 +649,14 @@ def translate_definition(sentence, report): if body is None: report.add_handled("Definition(no body)") return f"Axiom {name} : {type_out}." - body_node = parse_term(body.strip()) - body_full = body_node + if binders is None: # elaborate mode: still need surface binders to wrap body + binders_src = head.split(":", 1)[0] if ":" in head else head + binders = _parse_binder_src(binders_src) if binders_src.strip() else [] + body_full = parse_term(body.strip()) for nm, ty in reversed(binders): body_full = ("fun", nm, ty, body_full) report.add_handled("Definition") - return f"Definition {name} : {type_out} := {emit(body_full, env)}." + return f"Definition {name} : {type_out} := {emit(body_full, {})}." def _parse_binder_src(src): @@ -481,13 +673,15 @@ def _parse_binder_src(src): return binders -def translate_axiom(sentence, report): +def translate_axiom(sentence, report, elab=None): m = re.match(r"^(Axiom|Parameter|Conjecture|Variable|Hypothesis)\s+" r"([A-Za-z_][A-Za-z0-9_']*)\s*:\s*(.*)$", sentence, re.S) if not m: raise Untranslatable("unrecognized axiom form") name, type_src = m.group(2), m.group(3) report.add_handled("Axiom") + if elab is not None and name in elab: + return f"Axiom {name} : {translate_elab_type(elab[name])}." return f"Axiom {name} : {translate_term(type_src)}." @@ -749,8 +943,12 @@ def ok(self): # ─────────────────────────── unit-level translation ────────────────────────── -def translate_unit(text, report): - """Translate a whole .v unit to a MEngine source string, or raise.""" +def translate_unit(text, report, elab=None): + """Translate a whole .v unit to a MEngine source string, or raise. + + ``elab`` (optional) maps statement names to their `Set Printing All` types + (see ``rocq_elaborate``); when present, statement types are taken from there + rather than parsed from the surface source.""" text = strip_comments(text) sentences = split_sentences(text) @@ -771,16 +969,16 @@ def translate_unit(text, report): if kw in ("Inductive", "Fixpoint", "CoFixpoint"): raise Untranslatable(f"'{kw}' in unit (define it in the compat prelude)") if kw in ("Axiom", "Parameter", "Conjecture", "Variable", "Hypothesis"): - out_lines.append(translate_axiom(s, report)) + out_lines.append(translate_axiom(s, report, elab)) i += 1 continue if kw in ("Definition",): - out_lines.append(translate_definition(s, report)) + out_lines.append(translate_definition(s, report, elab)) i += 1 continue if kw in ("Lemma", "Theorem", "Example", "Corollary", "Fact", "Remark", "Proposition"): - stmt = translate_definition(s, report) + stmt = translate_definition(s, report, elab) out_lines.append(stmt) # Collect the proof: subsequent sentences until Qed/Defined/Admitted/Abort. type_out = stmt[len(f"Theorem "):].split(" : ", 1)[1].rstrip(".") @@ -805,6 +1003,104 @@ def translate_unit(text, report): return "\n".join(out_lines) + "\n" +# ─────────────────────── Rocq elaboration (Set Printing All) ────────────────── +# +# Obtain the fully-explicit, notation-free type of each top-level statement by +# replaying the unit through Rocq with `Set Printing All` and a `Check` per name. +# This is what lets eq/list statements translate without surface type synthesis +# (the implicit type arguments are printed for us). Failure (the unit does not +# compile, or a name is missing) is reported as Untranslatable — never guessed. + +STMT_DECL_RE = re.compile( + r"^(Definition|Lemma|Theorem|Example|Corollary|Fact|Remark|Proposition" + r"|Axiom|Parameter|Conjecture)\s+([A-Za-z_][A-Za-z0-9_']*)") + + +def statement_names(text): + """Ordered, de-duplicated names of every elaboratable top-level statement.""" + names, seen = [], set() + for s in split_sentences(strip_comments(text)): + m = STMT_DECL_RE.match(s.strip()) + if m and m.group(2) not in seen: + seen.add(m.group(2)) + names.append(m.group(2)) + return names + + +def _parse_check_output(stdout, names): + """Parse `Check`/`Print` output into {name: type-string}. + + Each `Check name.` prints the name flush-left on its own line, then the type + on the following line(s) prefixed ` : ` (continuations are indented). We + set the print width effectively unbounded, so a forall/application/arrow type + lands on a single line; we still join any continuations defensively.""" + name_set = set(names) + out = {} + lines = stdout.splitlines() + i = 0 + while i < len(lines): + ln = lines[i] + if ln and not ln[0].isspace() and ln.strip() in name_set and ln.strip() not in out: + head = ln.strip() + buf, j = [], i + 1 + while j < len(lines): + cont = lines[j] + if cont.strip() == "" or (cont and not cont[0].isspace()): + break + buf.append(cont.strip()) + j += 1 + joined = " ".join(buf).strip() + if joined.startswith(":"): + joined = joined[1:].strip() + out[head] = joined + i = j + else: + i += 1 + missing = [n for n in names if n not in out] + if missing: + raise Untranslatable(f"rocq elaboration produced no type for {missing}") + return out + + +def rocq_elaborate(text, vpath, coq_path, names): + """Return {name: elaborated-type-string} via `coqc` + `Set Printing All`.""" + if not names: + return {} + unit_dir = os.path.dirname(os.path.abspath(vpath)) + appendix = ["", "Set Printing All.", + "Set Printing Width 2000000000.", + "Set Printing Depth 2000000000."] + appendix += [f"Check {nm}." for nm in names] + fd, tmp = tempfile.mkstemp(suffix=".v", prefix="elab_", dir=unit_dir) + base = os.path.splitext(tmp)[0] + try: + with os.fdopen(fd, "w") as f: + f.write(text.rstrip() + "\n" + "\n".join(appendix) + "\n") + try: + proc = subprocess.run([coq_path, "-q", os.path.basename(tmp)], + capture_output=True, text=True, cwd=unit_dir) + except OSError as e: + raise Untranslatable(f"could not run '{coq_path}': {e}") + if proc.returncode != 0: + msg = (proc.stderr or proc.stdout).strip().replace("\n", " ") + raise Untranslatable(f"rocq elaboration failed: {msg[:200]}") + return _parse_check_output(proc.stdout, names) + finally: + for ext in (".v", ".vo", ".vok", ".vos", ".glob"): + p = base + ext + if os.path.exists(p): + try: + os.remove(p) + except OSError: + pass + aux = os.path.join(unit_dir, "." + os.path.basename(base) + ".aux") + if os.path.exists(aux): + try: + os.remove(aux) + except OSError: + pass + + # ─────────────────────────────── statement digest ──────────────────────────── def statement_digests(mengine_src): @@ -837,6 +1133,11 @@ def main(): ap.add_argument("input", nargs="?", help="input .v file") ap.add_argument("--report", action="store_true", help="report handled/unhandled") ap.add_argument("--dir", help="report over every .v under this directory") + ap.add_argument("--elaborate", action="store_true", + help="take statement types from Rocq `Set Printing All` " + "(notation-free, fully explicit) instead of the surface source") + ap.add_argument("--coq", default="coqc", + help="coqc/rocq binary used by --elaborate (default: coqc)") args = ap.parse_args() if args.dir: @@ -860,7 +1161,9 @@ def main(): text = f.read() report = Report() try: - src = translate_unit(text, report) + elab = (rocq_elaborate(text, args.input, args.coq, statement_names(text)) + if args.elaborate else None) + src = translate_unit(text, report, elab=elab) except Untranslatable as e: if args.report: print(f"FLAG: {e.reason}") From c24ac01656b016dc4b75620bd5c78893ebd353f8 Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sun, 28 Jun 2026 00:51:27 +0000 Subject: [PATCH 06/71] Expand stdlib corpus with polymorphic eq + existential lemmas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Set Printing All elaboration (prev commit) supplies every implicit argument, so polymorphic statements that the old surface translator could not handle — anything with an implicit type argument it had to synthesise — now translate faithfully. This adds the units that unlock and that prove today (no fixpoint reduction needed on the proof side): - eq (5): eq_refl_x, eq_sym, eq_trans, f_equal, f_equal2 over an arbitrary type A. The `T` of `@eq T x y` (and the `B` of `@eq B (f x) (f y)`) now come straight from Rocq instead of being synthesised. Proved by reflexivity/symmetry/rewrite. - ex (2): ex_intro and `exists y, y = x`. The implicit A of `@ex A P` is now supplied. Emitting these surfaced a translator bug: a binder-headed application argument (the predicate `fun y => …` of `ex A (fun y => …)`) was printed without parentheses, which MEngine's parser rejects. `emit` now parenthesizes fun/forall/arrow arguments. Corpus 26 -> 33 units; all pass the faithfulness gate (rocq compiles, mengine proves, mengine.me re-derivable with no drift). New `eq`/`ex` categories wired through stdlib_bench.py + report.py; README scope updated. Lists stay excluded: statements translate but the proof side still needs symbolic-fixpoint conversion. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/README.md | 14 + .../stdlib/corpus/eq_f_equal/mengine.me | 4 + benchmarks/stdlib/corpus/eq_f_equal/rocq.v | 2 + .../stdlib/corpus/eq_f_equal2/mengine.me | 5 + benchmarks/stdlib/corpus/eq_f_equal2/rocq.v | 2 + benchmarks/stdlib/corpus/eq_refl_x/mengine.me | 3 + benchmarks/stdlib/corpus/eq_refl_x/rocq.v | 2 + benchmarks/stdlib/corpus/eq_sym/mengine.me | 4 + benchmarks/stdlib/corpus/eq_sym/rocq.v | 2 + benchmarks/stdlib/corpus/eq_trans/mengine.me | 4 + benchmarks/stdlib/corpus/eq_trans/rocq.v | 2 + benchmarks/stdlib/corpus/ex_eq/mengine.me | 4 + benchmarks/stdlib/corpus/ex_eq/rocq.v | 2 + benchmarks/stdlib/corpus/ex_intro/mengine.me | 4 + benchmarks/stdlib/corpus/ex_intro/rocq.v | 2 + benchmarks/stdlib/corpus/manifest.json | 91 ++ benchmarks/stdlib/plots/stdlib_scatter.png | Bin 74904 -> 74738 bytes benchmarks/stdlib/report.py | 5 +- benchmarks/stdlib/results/REPORT.md | 63 +- benchmarks/stdlib/results/stdlib.json | 1002 ++++++++++++----- benchmarks/stdlib/stdlib_bench.py | 4 + benchmarks/stdlib/translate.py | 5 + 22 files changed, 884 insertions(+), 342 deletions(-) create mode 100644 benchmarks/stdlib/corpus/eq_f_equal/mengine.me create mode 100644 benchmarks/stdlib/corpus/eq_f_equal/rocq.v create mode 100644 benchmarks/stdlib/corpus/eq_f_equal2/mengine.me create mode 100644 benchmarks/stdlib/corpus/eq_f_equal2/rocq.v create mode 100644 benchmarks/stdlib/corpus/eq_refl_x/mengine.me create mode 100644 benchmarks/stdlib/corpus/eq_refl_x/rocq.v create mode 100644 benchmarks/stdlib/corpus/eq_sym/mengine.me create mode 100644 benchmarks/stdlib/corpus/eq_sym/rocq.v create mode 100644 benchmarks/stdlib/corpus/eq_trans/mengine.me create mode 100644 benchmarks/stdlib/corpus/eq_trans/rocq.v create mode 100644 benchmarks/stdlib/corpus/ex_eq/mengine.me create mode 100644 benchmarks/stdlib/corpus/ex_eq/rocq.v create mode 100644 benchmarks/stdlib/corpus/ex_intro/mengine.me create mode 100644 benchmarks/stdlib/corpus/ex_intro/rocq.v diff --git a/benchmarks/stdlib/README.md b/benchmarks/stdlib/README.md index 47971f4..6254d85 100644 --- a/benchmarks/stdlib/README.md +++ b/benchmarks/stdlib/README.md @@ -106,6 +106,20 @@ compat prelude: - **`le` / order** — structural induction with a fixpoint-free motive, and constructor chains. - **Logic** — propositional introduction (`split`/`left`/`right`/`apply`). +- **Polymorphic equality** — `eq_refl`/`eq_sym`/`eq_trans`/`f_equal`/`f_equal2` + over an arbitrary type `A`, proved by `reflexivity`/`symmetry`/`rewrite`. + These are exactly what the `Set Printing All` elaboration unlocked: the old + surface translator had to *synthesise* the implicit type argument of `=` and + gave up on any non-`nat`/`bool` equality, so a generic `forall (A:Type) (x:A), + x = x` was untranslatable. Now Rocq supplies the `T` of `@eq T x y` directly + (and the `B` of `@eq B (f x) (f y)`), so these statements translate with no + inference — and their proofs need no fixpoint reduction, so they prove today. +- **Polymorphic existentials** — `ex_intro` (`P x -> exists y, P y`) and + `exists y, y = x`, proved by `exists`/`exact`/`reflexivity`. Same unlock: the + implicit type argument of `@ex A P` is now supplied. Emitting these surfaced a + translator bug — a binder-headed application argument (the predicate `fun y => + …` of `ex A (fun y => …)`) was printed without parentheses, which MEngine's + parser rejects; `emit` now parenthesizes `fun`/`forall`/`arrow` arguments. Two engine fixes on this branch enable the computational part (both are pure, soundness-preserving kernel changes; all 431 kernel tests still pass): diff --git a/benchmarks/stdlib/corpus/eq_f_equal/mengine.me b/benchmarks/stdlib/corpus/eq_f_equal/mengine.me new file mode 100644 index 0000000..c7a32af --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_f_equal/mengine.me @@ -0,0 +1,4 @@ +Theorem f_equal_ex : forall (A : Type), forall (B : Type), forall (f : forall (_ : A), B), forall (x : A), forall (y : A), forall (_ : (((eq A) x) y)), (((eq B) (f x)) (f y)). +intro A. intro B. intro f. intro x. intro y. intro H. +rewrite_s (H). +reflexivity. diff --git a/benchmarks/stdlib/corpus/eq_f_equal/rocq.v b/benchmarks/stdlib/corpus/eq_f_equal/rocq.v new file mode 100644 index 0000000..72fced0 --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_f_equal/rocq.v @@ -0,0 +1,2 @@ +Lemma f_equal_ex : forall (A B : Type) (f : A -> B) (x y : A), x = y -> f x = f y. +Proof. intros A B f x y H. rewrite H. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/eq_f_equal2/mengine.me b/benchmarks/stdlib/corpus/eq_f_equal2/mengine.me new file mode 100644 index 0000000..f82cea9 --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_f_equal2/mengine.me @@ -0,0 +1,5 @@ +Theorem f_equal2_ex : forall (A : Type), forall (B : Type), forall (C : Type), forall (f : forall (_ : A), forall (_ : B), C), forall (x1 : A), forall (y1 : A), forall (x2 : B), forall (y2 : B), forall (_ : (((eq A) x1) y1)), forall (_ : (((eq B) x2) y2)), (((eq C) ((f x1) x2)) ((f y1) y2)). +intro A. intro B. intro C. intro f. intro x1. intro y1. intro x2. intro y2. intro H1. intro H2. +rewrite_s (H1). +rewrite_s (H2). +reflexivity. diff --git a/benchmarks/stdlib/corpus/eq_f_equal2/rocq.v b/benchmarks/stdlib/corpus/eq_f_equal2/rocq.v new file mode 100644 index 0000000..7759e5c --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_f_equal2/rocq.v @@ -0,0 +1,2 @@ +Lemma f_equal2_ex : forall (A B C : Type) (f : A -> B -> C) (x1 y1 : A) (x2 y2 : B), x1 = y1 -> x2 = y2 -> f x1 x2 = f y1 y2. +Proof. intros A B C f x1 y1 x2 y2 H1 H2. rewrite H1. rewrite H2. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/eq_refl_x/mengine.me b/benchmarks/stdlib/corpus/eq_refl_x/mengine.me new file mode 100644 index 0000000..ca65a6e --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_refl_x/mengine.me @@ -0,0 +1,3 @@ +Theorem eq_refl_x : forall (A : Type), forall (x : A), (((eq A) x) x). +intro A. intro x. +reflexivity. diff --git a/benchmarks/stdlib/corpus/eq_refl_x/rocq.v b/benchmarks/stdlib/corpus/eq_refl_x/rocq.v new file mode 100644 index 0000000..3b9f233 --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_refl_x/rocq.v @@ -0,0 +1,2 @@ +Lemma eq_refl_x : forall (A : Type) (x : A), x = x. +Proof. intros A x. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/eq_sym/mengine.me b/benchmarks/stdlib/corpus/eq_sym/mengine.me new file mode 100644 index 0000000..3185a23 --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_sym/mengine.me @@ -0,0 +1,4 @@ +Theorem eq_sym_ex : forall (A : Type), forall (x : A), forall (y : A), forall (_ : (((eq A) x) y)), (((eq A) y) x). +intro A. intro x. intro y. intro H. +symmetry. +exact (H). diff --git a/benchmarks/stdlib/corpus/eq_sym/rocq.v b/benchmarks/stdlib/corpus/eq_sym/rocq.v new file mode 100644 index 0000000..d084a6a --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_sym/rocq.v @@ -0,0 +1,2 @@ +Lemma eq_sym_ex : forall (A : Type) (x y : A), x = y -> y = x. +Proof. intros A x y H. symmetry. exact H. Qed. diff --git a/benchmarks/stdlib/corpus/eq_trans/mengine.me b/benchmarks/stdlib/corpus/eq_trans/mengine.me new file mode 100644 index 0000000..360cf4b --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_trans/mengine.me @@ -0,0 +1,4 @@ +Theorem eq_trans_ex : forall (A : Type), forall (x : A), forall (y : A), forall (z : A), forall (_ : (((eq A) x) y)), forall (_ : (((eq A) y) z)), (((eq A) x) z). +intro A. intro x. intro y. intro z. intro H1. intro H2. +rewrite_s (H1). +exact (H2). diff --git a/benchmarks/stdlib/corpus/eq_trans/rocq.v b/benchmarks/stdlib/corpus/eq_trans/rocq.v new file mode 100644 index 0000000..b5ef123 --- /dev/null +++ b/benchmarks/stdlib/corpus/eq_trans/rocq.v @@ -0,0 +1,2 @@ +Lemma eq_trans_ex : forall (A : Type) (x y z : A), x = y -> y = z -> x = z. +Proof. intros A x y z H1 H2. rewrite H1. exact H2. Qed. diff --git a/benchmarks/stdlib/corpus/ex_eq/mengine.me b/benchmarks/stdlib/corpus/ex_eq/mengine.me new file mode 100644 index 0000000..56e4d9f --- /dev/null +++ b/benchmarks/stdlib/corpus/ex_eq/mengine.me @@ -0,0 +1,4 @@ +Theorem ex_eq : forall (A : Type), forall (x : A), ((ex A) (fun (y : A) => (((eq A) y) x))). +intro A. intro x. +exists (x). +reflexivity. diff --git a/benchmarks/stdlib/corpus/ex_eq/rocq.v b/benchmarks/stdlib/corpus/ex_eq/rocq.v new file mode 100644 index 0000000..2fc6bdc --- /dev/null +++ b/benchmarks/stdlib/corpus/ex_eq/rocq.v @@ -0,0 +1,2 @@ +Lemma ex_eq : forall (A : Type) (x : A), exists y, y = x. +Proof. intros A x. exists x. reflexivity. Qed. diff --git a/benchmarks/stdlib/corpus/ex_intro/mengine.me b/benchmarks/stdlib/corpus/ex_intro/mengine.me new file mode 100644 index 0000000..6ff6231 --- /dev/null +++ b/benchmarks/stdlib/corpus/ex_intro/mengine.me @@ -0,0 +1,4 @@ +Theorem ex_intro_ex : forall (A : Type), forall (P : forall (_ : A), Prop), forall (x : A), forall (_ : (P x)), ((ex A) (fun (y : A) => (P y))). +intro A. intro P. intro x. intro H. +exists (x). +exact (H). diff --git a/benchmarks/stdlib/corpus/ex_intro/rocq.v b/benchmarks/stdlib/corpus/ex_intro/rocq.v new file mode 100644 index 0000000..edb0f04 --- /dev/null +++ b/benchmarks/stdlib/corpus/ex_intro/rocq.v @@ -0,0 +1,2 @@ +Lemma ex_intro_ex : forall (A : Type) (P : A -> Prop) (x : A), P x -> exists y, P y. +Proof. intros A P x H. exists x. exact H. Qed. diff --git a/benchmarks/stdlib/corpus/manifest.json b/benchmarks/stdlib/corpus/manifest.json index c076290..9884478 100644 --- a/benchmarks/stdlib/corpus/manifest.json +++ b/benchmarks/stdlib/corpus/manifest.json @@ -145,6 +145,97 @@ ], "deps": "Coq.Init (auto-loaded)" }, + { + "name": "eq_f_equal", + "tier": "A", + "category": "eq", + "rocq_sha256": "fd0d2a3478438ee8", + "statements": [ + { + "name": "f_equal_ex", + "digest": "forall (A : Type), forall (B : Type), forall (f : forall (_ : A), B), forall (x : A), forall (y : A), forall (_ : (((eq A) x) y)), (((eq B) (f x)) (f y))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "eq_f_equal2", + "tier": "A", + "category": "eq", + "rocq_sha256": "269f4f06a8e69e7a", + "statements": [ + { + "name": "f_equal2_ex", + "digest": "forall (A : Type), forall (B : Type), forall (C : Type), forall (f : forall (_ : A), forall (_ : B), C), forall (x1 : A), forall (y1 : A), forall (x2 : B), forall (y2 : B), forall (_ : (((eq A) x1) y1)), forall (_ : (((eq B) x2) y2)), (((eq C) ((f x1) x2)) ((f y1) y2))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "eq_refl_x", + "tier": "A", + "category": "eq", + "rocq_sha256": "6701e0b00941e4e4", + "statements": [ + { + "name": "eq_refl_x", + "digest": "forall (A : Type), forall (x : A), (((eq A) x) x)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "eq_sym", + "tier": "A", + "category": "eq", + "rocq_sha256": "7bad55101a4d8c3b", + "statements": [ + { + "name": "eq_sym_ex", + "digest": "forall (A : Type), forall (x : A), forall (y : A), forall (_ : (((eq A) x) y)), (((eq A) y) x)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "eq_trans", + "tier": "A", + "category": "eq", + "rocq_sha256": "76a3d8e1b93d6489", + "statements": [ + { + "name": "eq_trans_ex", + "digest": "forall (A : Type), forall (x : A), forall (y : A), forall (z : A), forall (_ : (((eq A) x) y)), forall (_ : (((eq A) y) z)), (((eq A) x) z)" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "ex_eq", + "tier": "A", + "category": "ex", + "rocq_sha256": "baf4f4142fabaf30", + "statements": [ + { + "name": "ex_eq", + "digest": "forall (A : Type), forall (x : A), ((ex A) (fun (y : A) => (((eq A) y) x)))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, + { + "name": "ex_intro", + "tier": "A", + "category": "ex", + "rocq_sha256": "e9ed5cda9c65d8ab", + "statements": [ + { + "name": "ex_intro_ex", + "digest": "forall (A : Type), forall (P : forall (_ : A), Prop), forall (x : A), forall (_ : (P x)), ((ex A) (fun (y : A) => (P y)))" + } + ], + "deps": "Coq.Init (auto-loaded)" + }, { "name": "le_0_n", "tier": "A", diff --git a/benchmarks/stdlib/plots/stdlib_scatter.png b/benchmarks/stdlib/plots/stdlib_scatter.png index 2215db8eda091ddc71d76f0edb64075a6fb795d1..b92f1099c19b076d79ee8dfbaf43edad37f01c6f 100644 GIT binary patch literal 74738 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

ZZ1=X2$WeH3S>)XHsRkPPa(OPu3j!j!l1?Q& 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 CY#gfq diff --git a/benchmarks/stdlib/report.py b/benchmarks/stdlib/report.py index 25a37f4..86a0ef2 100644 --- a/benchmarks/stdlib/report.py +++ b/benchmarks/stdlib/report.py @@ -13,7 +13,7 @@ def _category(name): for pre, cat in (("bool_", "bool"), ("nat_", "nat"), ("le_", "le"), - ("logic_", "logic")): + ("logic_", "logic"), ("eq_", "eq"), ("ex_", "ex")): if name.startswith(pre): return cat return "other" @@ -98,7 +98,8 @@ def _scatter(cfg, rows): return colors = {"bool": "tab:blue", "nat": "tab:green", "le": "tab:orange", - "logic": "tab:red", "other": "gray"} + "logic": "tab:red", "eq": "tab:purple", "ex": "tab:brown", + "other": "gray"} pts = [r for r in rows if r["mengine"] and r["rocq"]] if not pts: return diff --git a/benchmarks/stdlib/results/REPORT.md b/benchmarks/stdlib/results/REPORT.md index 94a0899..2d58c38 100644 --- a/benchmarks/stdlib/results/REPORT.md +++ b/benchmarks/stdlib/results/REPORT.md @@ -4,34 +4,41 @@ Per-unit wall-clock (best of N trials), whole-process: each engine pays its own | Unit | Category | Rocq (ms) | MEngine (ms) | Speedup (Rocq/MEngine) | |------|----------|-----------|--------------|------------------------| -| `bool_andb_b_b` | bool | 66.2 | 2.2 | 30.45× | -| `bool_andb_false_l` | bool | 65.4 | 1.9 | 34.10× | -| `bool_andb_true_l` | bool | 64.5 | 2.0 | 32.07× | -| `bool_andb_true_r` | bool | 65.1 | 2.1 | 31.40× | -| `bool_negb_false` | bool | 63.7 | 2.0 | 31.63× | -| `bool_negb_involutive` | bool | 66.7 | 2.2 | 30.67× | -| `bool_negb_true` | bool | 64.0 | 2.0 | 32.43× | -| `bool_orb_b_b` | bool | 66.2 | 2.1 | 31.09× | -| `bool_orb_false_l` | bool | 65.4 | 2.1 | 31.31× | -| `bool_orb_false_r` | bool | 65.8 | 2.0 | 33.04× | -| `bool_orb_true_l` | bool | 63.4 | 2.0 | 31.49× | -| `le_0_n` | le | 63.3 | 1.9 | 33.32× | -| `le_1_2` | le | 67.9 | 2.1 | 32.68× | -| `le_2_4` | le | 65.2 | 2.0 | 32.10× | -| `le_refl_n` | le | 68.7 | 2.0 | 35.01× | -| `logic_and_intro` | logic | 65.4 | 2.0 | 33.48× | -| `logic_and_intro3` | logic | 67.9 | 1.9 | 36.21× | -| `logic_imp_refl` | logic | 65.3 | 2.4 | 27.48× | -| `logic_imp_trans` | logic | 65.2 | 1.8 | 36.12× | -| `logic_or_introl` | logic | 64.6 | 2.1 | 30.17× | -| `logic_or_intror` | logic | 67.0 | 1.9 | 35.86× | -| `nat_add_0_l` | nat | 67.4 | 1.8 | 36.51× | -| `nat_add_2_2` | nat | 65.4 | 2.0 | 32.65× | -| `nat_add_3_4` | nat | 75.8 | 2.1 | 36.39× | -| `nat_mul_2_3` | nat | 66.3 | 2.7 | 24.63× | -| `nat_sub_5_2` | nat | 66.7 | 2.3 | 29.27× | +| `bool_andb_b_b` | bool | 65.5 | 4.6 | 14.12× | +| `bool_andb_false_l` | bool | 65.5 | 2.1 | 31.58× | +| `bool_andb_true_l` | bool | 64.3 | 2.2 | 28.96× | +| `bool_andb_true_r` | bool | 65.5 | 2.1 | 31.57× | +| `bool_negb_false` | bool | 64.1 | 1.9 | 33.55× | +| `bool_negb_involutive` | bool | 66.6 | 2.3 | 29.31× | +| `bool_negb_true` | bool | 64.8 | 2.1 | 30.37× | +| `bool_orb_b_b` | bool | 69.1 | 2.1 | 32.86× | +| `bool_orb_false_l` | bool | 66.4 | 2.1 | 31.19× | +| `bool_orb_false_r` | bool | 65.7 | 2.3 | 28.70× | +| `bool_orb_true_l` | bool | 64.4 | 2.0 | 32.05× | +| `eq_f_equal` | eq | 67.2 | 2.3 | 29.19× | +| `eq_f_equal2` | eq | 66.6 | 3.3 | 20.27× | +| `eq_refl_x` | eq | 65.7 | 1.9 | 34.36× | +| `eq_sym` | eq | 69.1 | 2.2 | 31.59× | +| `eq_trans` | eq | 65.6 | 2.0 | 32.27× | +| `ex_eq` | ex | 76.5 | 2.1 | 37.19× | +| `ex_intro` | ex | 68.3 | 2.0 | 33.81× | +| `le_0_n` | le | 66.3 | 2.1 | 31.40× | +| `le_1_2` | le | 63.5 | 1.9 | 33.51× | +| `le_2_4` | le | 68.4 | 2.0 | 34.94× | +| `le_refl_n` | le | 66.0 | 2.3 | 28.92× | +| `logic_and_intro` | logic | 65.9 | 2.1 | 30.89× | +| `logic_and_intro3` | logic | 66.4 | 2.1 | 31.72× | +| `logic_imp_refl` | logic | 64.0 | 1.8 | 35.28× | +| `logic_imp_trans` | logic | 65.9 | 1.9 | 34.09× | +| `logic_or_introl` | logic | 64.5 | 2.0 | 32.67× | +| `logic_or_intror` | logic | 66.5 | 2.0 | 33.03× | +| `nat_add_0_l` | nat | 74.3 | 1.9 | 38.82× | +| `nat_add_2_2` | nat | 66.2 | 2.5 | 26.87× | +| `nat_add_3_4` | nat | 65.1 | 2.1 | 31.06× | +| `nat_mul_2_3` | nat | 63.6 | 2.4 | 26.33× | +| `nat_sub_5_2` | nat | 65.0 | 2.3 | 27.97× | -**Units:** 26 (all Tier A). **Both-succeed:** 26. -**Geometric-mean speedup (Rocq/MEngine):** 32.25× (median 32.43×). +**Units:** 33 (all Tier A). **Both-succeed:** 33. +**Geometric-mean speedup (Rocq/MEngine):** 30.51× (median 31.58×). > MEngine times are dominated by prelude+compat startup at this problem size; the comparison reflects fixed per-invocation cost, not asymptotics. See README for scope and caveats. diff --git a/benchmarks/stdlib/results/stdlib.json b/benchmarks/stdlib/results/stdlib.json index 5a8a06e..6cbf637 100644 --- a/benchmarks/stdlib/results/stdlib.json +++ b/benchmarks/stdlib/results/stdlib.json @@ -1,1403 +1,1781 @@ { "mengine_bool_andb_b_b": { - "time_taken": 0.0021740803495049477, + "time_taken": 0.00463766697794199, "success": true, "trials": [ { - "time_taken": 0.005351994186639786, + "time_taken": 0.0049459319561719894, "success": true }, { - "time_taken": 0.005838930606842041, + "time_taken": 0.005192723125219345, "success": true }, { - "time_taken": 0.002309804782271385, + "time_taken": 0.00463766697794199, "success": true }, { - "time_taken": 0.005080173723399639, + "time_taken": 0.006048243492841721, "success": true }, { - "time_taken": 0.0021740803495049477, + "time_taken": 0.004930107854306698, "success": true } ], "error": "" }, "coq_bool_andb_b_b": { - "time_taken": 0.06619509868323803, + "time_taken": 0.06549833249300718, "success": true, "trials": [ { - "time_taken": 0.0740803238004446, + "time_taken": 0.07997223734855652, "success": true }, { - "time_taken": 0.07655169162899256, + "time_taken": 0.0904836356639862, "success": true }, { - "time_taken": 0.08828787785023451, + "time_taken": 0.08864159695804119, "success": true }, { - "time_taken": 0.06725370418280363, + "time_taken": 0.0687748258933425, "success": true }, { - "time_taken": 0.06619509868323803, + "time_taken": 0.06549833249300718, "success": true } ], "error": "" }, "mengine_bool_andb_false_l": { - "time_taken": 0.0019167512655258179, + "time_taken": 0.0020751794800162315, "success": true, "trials": [ { - "time_taken": 0.002403966151177883, + "time_taken": 0.004930559545755386, "success": true }, { - "time_taken": 0.00489372294396162, + "time_taken": 0.0020751794800162315, "success": true }, { - "time_taken": 0.002142001874744892, + "time_taken": 0.005081837996840477, "success": true }, { - "time_taken": 0.0019167512655258179, + "time_taken": 0.004537994973361492, "success": true }, { - "time_taken": 0.004725058563053608, + "time_taken": 0.0046741412952542305, "success": true } ], "error": "" }, "coq_bool_andb_false_l": { - "time_taken": 0.06536232214421034, + "time_taken": 0.0655443724244833, "success": true, "trials": [ { - "time_taken": 0.0913093164563179, + "time_taken": 0.0663163848221302, "success": true }, { - "time_taken": 0.06557705346494913, + "time_taken": 0.06564455386251211, "success": true }, { - "time_taken": 0.06551979761570692, + "time_taken": 0.0655443724244833, "success": true }, { - "time_taken": 0.09066289197653532, + "time_taken": 0.09319732896983624, "success": true }, { - "time_taken": 0.06536232214421034, + "time_taken": 0.10056179948151112, "success": true } ], "error": "" }, "mengine_bool_andb_true_l": { - "time_taken": 0.002012736164033413, + "time_taken": 0.002221140079200268, "success": true, "trials": [ { - "time_taken": 0.002243353985249996, + "time_taken": 0.0026553859934210777, "success": true }, { - "time_taken": 0.004768563434481621, + "time_taken": 0.0022950423881411552, "success": true }, { - "time_taken": 0.002012736164033413, + "time_taken": 0.0024812035262584686, "success": true }, { - "time_taken": 0.005158963613212109, + "time_taken": 0.0024242177605628967, "success": true }, { - "time_taken": 0.002134900540113449, + "time_taken": 0.002221140079200268, "success": true } ], "error": "" }, "coq_bool_andb_true_l": { - "time_taken": 0.06454734224826097, + "time_taken": 0.06432981882244349, "success": true, "trials": [ { - "time_taken": 0.08336627390235662, + "time_taken": 0.06606138218194246, "success": true }, { - "time_taken": 0.06454734224826097, + "time_taken": 0.06823317799717188, "success": true }, { - "time_taken": 0.06653274688869715, + "time_taken": 0.06432981882244349, "success": true }, { - "time_taken": 0.09328210167586803, + "time_taken": 0.06724991369992495, "success": true }, { - "time_taken": 0.08236036542803049, + "time_taken": 0.0727572925388813, "success": true } ], "error": "" }, "mengine_bool_andb_true_r": { - "time_taken": 0.0020732879638671875, + "time_taken": 0.002076033502817154, "success": true, "trials": [ { - "time_taken": 0.002406270243227482, + "time_taken": 0.0025372281670570374, "success": true }, { - "time_taken": 0.004063849337399006, + "time_taken": 0.0024595102295279503, "success": true }, { - "time_taken": 0.002175631932914257, + "time_taken": 0.0022240346297621727, "success": true }, { - "time_taken": 0.0020732879638671875, + "time_taken": 0.00514322891831398, "success": true }, { - "time_taken": 0.003917189314961433, + "time_taken": 0.002076033502817154, "success": true } ], "error": "" }, "coq_bool_andb_true_r": { - "time_taken": 0.06510116718709469, + "time_taken": 0.06554151698946953, "success": true, "trials": [ { - "time_taken": 0.06510116718709469, + "time_taken": 0.08620802592486143, "success": true }, { - "time_taken": 0.07451162487268448, + "time_taken": 0.08414206840097904, "success": true }, { - "time_taken": 0.06923293974250555, + "time_taken": 0.09217677172273397, "success": true }, { - "time_taken": 0.08497279789298773, + "time_taken": 0.06558381114155054, "success": true }, { - "time_taken": 0.06970004457980394, + "time_taken": 0.06554151698946953, "success": true } ], "error": "" }, "mengine_bool_negb_false": { - "time_taken": 0.0020140185952186584, + "time_taken": 0.0019089793786406517, "success": true, "trials": [ { - "time_taken": 0.0047165462747216225, + "time_taken": 0.002184155397117138, "success": true }, { - "time_taken": 0.0024332813918590546, + "time_taken": 0.0051285261288285255, "success": true }, { - "time_taken": 0.004702294245362282, + "time_taken": 0.001947527751326561, "success": true }, { - "time_taken": 0.0020140185952186584, + "time_taken": 0.004444223828613758, "success": true }, { - "time_taken": 0.0023254482075572014, + "time_taken": 0.0019089793786406517, "success": true } ], "error": "" }, "coq_bool_negb_false": { - "time_taken": 0.06370629277080297, + "time_taken": 0.06405089888721704, "success": true, "trials": [ { - "time_taken": 0.06883997842669487, + "time_taken": 0.06405089888721704, "success": true }, { - "time_taken": 0.06370629277080297, + "time_taken": 0.09183580707758665, "success": true }, { - "time_taken": 0.07765176240354776, + "time_taken": 0.09061233978718519, "success": true }, { - "time_taken": 0.08686848822981119, + "time_taken": 0.09861306846141815, "success": true }, { - "time_taken": 0.06471100822091103, + "time_taken": 0.06454489380121231, "success": true } ], "error": "" }, "mengine_bool_negb_involutive": { - "time_taken": 0.0021748607978224754, + "time_taken": 0.002270805649459362, "success": true, "trials": [ { - "time_taken": 0.0023497147485613823, + "time_taken": 0.002270805649459362, "success": true }, { - "time_taken": 0.004884419031441212, + "time_taken": 0.0049414560198783875, "success": true }, { - "time_taken": 0.0021748607978224754, + "time_taken": 0.002855047583580017, "success": true }, { - "time_taken": 0.004625418223440647, + "time_taken": 0.005327477119863033, "success": true }, { - "time_taken": 0.004726300947368145, + "time_taken": 0.00493487436324358, "success": true } ], "error": "" }, "coq_bool_negb_involutive": { - "time_taken": 0.06670686043798923, + "time_taken": 0.06656290777027607, "success": true, "trials": [ { - "time_taken": 0.07896770723164082, + "time_taken": 0.08739108126610518, "success": true }, { - "time_taken": 0.09257125109434128, + "time_taken": 0.0686976183205843, "success": true }, { - "time_taken": 0.06993178371340036, + "time_taken": 0.06656290777027607, "success": true }, { - "time_taken": 0.06808586046099663, + "time_taken": 0.0925722774118185, "success": true }, { - "time_taken": 0.06670686043798923, + "time_taken": 0.07752962224185467, "success": true } ], "error": "" }, "mengine_bool_negb_true": { - "time_taken": 0.0019729966297745705, + "time_taken": 0.0021347813308238983, "success": true, "trials": [ { - "time_taken": 0.0021493425592780113, + "time_taken": 0.0024364860728383064, "success": true }, { - "time_taken": 0.004536454565823078, + "time_taken": 0.004513167776167393, "success": true }, { - "time_taken": 0.0019729966297745705, + "time_taken": 0.0021347813308238983, "success": true }, { - "time_taken": 0.004446428269147873, + "time_taken": 0.0045669302344322205, "success": true }, { - "time_taken": 0.004626359790563583, + "time_taken": 0.00451928935945034, "success": true } ], "error": "" }, "coq_bool_negb_true": { - "time_taken": 0.06397562939673662, + "time_taken": 0.06483905762434006, "success": true, "trials": [ { - "time_taken": 0.06397562939673662, + "time_taken": 0.06483905762434006, "success": true }, { - "time_taken": 0.08838566765189171, + "time_taken": 0.06622126325964928, "success": true }, { - "time_taken": 0.0750273521989584, + "time_taken": 0.08963744901120663, "success": true }, { - "time_taken": 0.08242534380406141, + "time_taken": 0.06843385100364685, "success": true }, { - "time_taken": 0.06548822950571775, + "time_taken": 0.08374005183577538, "success": true } ], "error": "" }, "mengine_bool_orb_b_b": { - "time_taken": 0.002130454406142235, + "time_taken": 0.002101670950651169, "success": true, "trials": [ { - "time_taken": 0.00238234456628561, + "time_taken": 0.0023409510031342506, "success": true }, { - "time_taken": 0.002154691144824028, + "time_taken": 0.005133394151926041, "success": true }, { - "time_taken": 0.004914012737572193, + "time_taken": 0.002216714434325695, "success": true }, { - "time_taken": 0.0048569580540061, + "time_taken": 0.005013203248381615, "success": true }, { - "time_taken": 0.002130454406142235, + "time_taken": 0.002101670950651169, "success": true } ], "error": "" }, "coq_bool_orb_b_b": { - "time_taken": 0.06623977515846491, + "time_taken": 0.06906632427126169, "success": true, "trials": [ { - "time_taken": 0.08240906987339258, + "time_taken": 0.08478866331279278, "success": true }, { - "time_taken": 0.06623977515846491, + "time_taken": 0.0736290980130434, "success": true }, { - "time_taken": 0.07678854931145906, + "time_taken": 0.06906632427126169, "success": true }, { - "time_taken": 0.08358939085155725, + "time_taken": 0.06970675010234118, "success": true }, { - "time_taken": 0.0705526415258646, + "time_taken": 0.06945477053523064, "success": true } ], "error": "" }, "mengine_bool_orb_false_l": { - "time_taken": 0.0020895833149552345, + "time_taken": 0.0021279212087392807, "success": true, "trials": [ { - "time_taken": 0.0022005299106240273, + "time_taken": 0.002295423299074173, "success": true }, { - "time_taken": 0.0057874806225299835, + "time_taken": 0.004923548549413681, "success": true }, { - "time_taken": 0.002113298512995243, + "time_taken": 0.0021576453000307083, "success": true }, { - "time_taken": 0.004648342728614807, + "time_taken": 0.005329250358045101, "success": true }, { - "time_taken": 0.0020895833149552345, + "time_taken": 0.0021279212087392807, "success": true } ], "error": "" }, "coq_bool_orb_false_l": { - "time_taken": 0.06543062441051006, + "time_taken": 0.06636645179241896, "success": true, "trials": [ { - "time_taken": 0.08560738526284695, + "time_taken": 0.06753111816942692, "success": true }, { - "time_taken": 0.06543062441051006, + "time_taken": 0.10252160578966141, "success": true }, { - "time_taken": 0.107350236736238, + "time_taken": 0.06646296847611666, "success": true }, { - "time_taken": 0.08680164627730846, + "time_taken": 0.0686428053304553, "success": true }, { - "time_taken": 0.07846035342663527, + "time_taken": 0.06636645179241896, "success": true } ], "error": "" }, "mengine_bool_orb_false_r": { - "time_taken": 0.0019906237721443176, + "time_taken": 0.0022901343181729317, "success": true, "trials": [ { - "time_taken": 0.005584435537457466, + "time_taken": 0.0025775395333766937, "success": true }, { - "time_taken": 0.004804197698831558, + "time_taken": 0.005370021797716618, "success": true }, { - "time_taken": 0.004078764468431473, + "time_taken": 0.0025513991713523865, "success": true }, { - "time_taken": 0.0019906237721443176, + "time_taken": 0.0022901343181729317, "success": true }, { - "time_taken": 0.0019935863092541695, + "time_taken": 0.006559295579791069, "success": true } ], "error": "" }, "coq_bool_orb_false_r": { - "time_taken": 0.06576732266694307, + "time_taken": 0.06573137361556292, "success": true, "trials": [ { - "time_taken": 0.08065385092049837, + "time_taken": 0.09115675091743469, "success": true }, { - "time_taken": 0.0669049583375454, + "time_taken": 0.07187472190707922, "success": true }, { - "time_taken": 0.06576732266694307, + "time_taken": 0.06573137361556292, "success": true }, { - "time_taken": 0.09204454533755779, + "time_taken": 0.06920829880982637, "success": true }, { - "time_taken": 0.06678638886660337, + "time_taken": 0.07483362592756748, "success": true } ], "error": "" }, "mengine_bool_orb_true_l": { - "time_taken": 0.002014538273215294, + "time_taken": 0.0020088907331228256, "success": true, "trials": [ { - "time_taken": 0.0021964823827147484, + "time_taken": 0.0022747423499822617, "success": true }, { - "time_taken": 0.002014538273215294, + "time_taken": 0.0039001237601041794, "success": true }, { - "time_taken": 0.004663505591452122, + "time_taken": 0.0020088907331228256, "success": true }, { - "time_taken": 0.0020369822159409523, + "time_taken": 0.003575262613594532, "success": true }, { - "time_taken": 0.004412497393786907, + "time_taken": 0.0023359740152955055, "success": true } ], "error": "" }, "coq_bool_orb_true_l": { - "time_taken": 0.06342829391360283, + "time_taken": 0.06438982952386141, "success": true, "trials": [ { - "time_taken": 0.06498104520142078, + "time_taken": 0.07598469220101833, "success": true }, { - "time_taken": 0.0878400020301342, + "time_taken": 0.06946132145822048, "success": true }, { - "time_taken": 0.07843938004225492, + "time_taken": 0.06438982952386141, "success": true }, { - "time_taken": 0.06342829391360283, + "time_taken": 0.08038393780589104, "success": true }, { - "time_taken": 0.08014839794486761, + "time_taken": 0.06463508866727352, "success": true } ], "error": "" }, "mengine_le_0_n": { - "time_taken": 0.0018982738256454468, + "time_taken": 0.0021121054887771606, "success": true, "trials": [ { - "time_taken": 0.002197735942900181, + "time_taken": 0.002382934093475342, "success": true }, { - "time_taken": 0.001912526786327362, + "time_taken": 0.004793311469256878, "success": true }, { - "time_taken": 0.0018982738256454468, + "time_taken": 0.0022020209580659866, "success": true }, { - "time_taken": 0.0019145384430885315, + "time_taken": 0.0050757769495248795, "success": true }, { - "time_taken": 0.004819119349122047, + "time_taken": 0.0021121054887771606, "success": true } ], "error": "" }, "coq_le_0_n": { - "time_taken": 0.0632502743974328, + "time_taken": 0.06632644217461348, "success": true, "trials": [ { - "time_taken": 0.06364869512617588, + "time_taken": 0.0867515467107296, "success": true }, { - "time_taken": 0.06465807929635048, + "time_taken": 0.06632644217461348, "success": true }, { - "time_taken": 0.0632502743974328, + "time_taken": 0.0758205447345972, "success": true }, { - "time_taken": 0.08437193185091019, + "time_taken": 0.09448828548192978, "success": true }, { - "time_taken": 0.0686585046350956, + "time_taken": 0.07834336068481207, "success": true } ], "error": "" }, "mengine_le_1_2": { - "time_taken": 0.0020777760073542595, + "time_taken": 0.0018957890570163727, "success": true, "trials": [ { - "time_taken": 0.0023047784343361855, + "time_taken": 0.002260279841721058, "success": true }, { - "time_taken": 0.004668844863772392, + "time_taken": 0.0024246983230113983, "success": true }, { - "time_taken": 0.0021224217489361763, + "time_taken": 0.0019703423604369164, "success": true }, { - "time_taken": 0.003817237913608551, + "time_taken": 0.0023520486429333687, "success": true }, { - "time_taken": 0.0020777760073542595, + "time_taken": 0.0018957890570163727, "success": true } ], "error": "" }, "coq_le_1_2": { - "time_taken": 0.06790374498814344, + "time_taken": 0.06352399289608002, "success": true, "trials": [ { - "time_taken": 0.07176924590021372, + "time_taken": 0.06632776465266943, "success": true }, { - "time_taken": 0.0753260925412178, + "time_taken": 0.08375401329249144, "success": true }, { - "time_taken": 0.06877670250833035, + "time_taken": 0.0795758692547679, "success": true }, { - "time_taken": 0.06899416074156761, + "time_taken": 0.06352399289608002, "success": true }, { - "time_taken": 0.06790374498814344, + "time_taken": 0.07028551399707794, "success": true } ], "error": "" }, "mengine_le_2_4": { - "time_taken": 0.002030644565820694, + "time_taken": 0.0019586943089962006, "success": true, "trials": [ { - "time_taken": 0.004747071303427219, + "time_taken": 0.0022512562572956085, "success": true }, { - "time_taken": 0.003835677169263363, + "time_taken": 0.0019586943089962006, "success": true }, { - "time_taken": 0.0037500187754631042, + "time_taken": 0.002231576479971409, "success": true }, { - "time_taken": 0.002030644565820694, + "time_taken": 0.0020045340061187744, "success": true }, { - "time_taken": 0.0020945286378264427, + "time_taken": 0.0020744595676660538, "success": true } ], "error": "" }, "coq_le_2_4": { - "time_taken": 0.0651756701990962, + "time_taken": 0.0684300260618329, "success": true, "trials": [ { - "time_taken": 0.0692351944744587, + "time_taken": 0.0896862018853426, "success": true }, { - "time_taken": 0.08934114780277014, + "time_taken": 0.0684300260618329, "success": true }, { - "time_taken": 0.08167203795164824, + "time_taken": 0.08331043366342783, "success": true }, { - "time_taken": 0.06681795697659254, + "time_taken": 0.08574073016643524, "success": true }, { - "time_taken": 0.0651756701990962, + "time_taken": 0.08435884676873684, "success": true } ], "error": "" }, "mengine_le_refl_n": { - "time_taken": 0.0019616493955254555, + "time_taken": 0.0022805901244282722, "success": true, "trials": [ { - "time_taken": 0.0021845372393727303, + "time_taken": 0.0022805901244282722, "success": true }, { - "time_taken": 0.004463664256036282, + "time_taken": 0.004808033816516399, "success": true }, { - "time_taken": 0.004248057492077351, + "time_taken": 0.004414590075612068, "success": true }, { - "time_taken": 0.001974780112504959, + "time_taken": 0.0026773493736982346, "success": true }, { - "time_taken": 0.0019616493955254555, + "time_taken": 0.0023717693984508514, "success": true } ], "error": "" }, "coq_le_refl_n": { - "time_taken": 0.06868398282676935, + "time_taken": 0.06596558727324009, "success": true, "trials": [ { - "time_taken": 0.08178988564759493, + "time_taken": 0.08483497425913811, "success": true }, { - "time_taken": 0.07030741218477488, + "time_taken": 0.08571016415953636, "success": true }, { - "time_taken": 0.06868398282676935, + "time_taken": 0.06596558727324009, "success": true }, { - "time_taken": 0.07078546285629272, + "time_taken": 0.08646883815526962, "success": true }, { - "time_taken": 0.06981757190078497, + "time_taken": 0.08170174807310104, "success": true } ], "error": "" }, "mengine_logic_and_intro": { - "time_taken": 0.0019524255767464638, + "time_taken": 0.002131734974682331, "success": true, "trials": [ { - "time_taken": 0.0027153370901942253, + "time_taken": 0.005348087288439274, "success": true }, { - "time_taken": 0.004514399915933609, + "time_taken": 0.0022791987285017967, "success": true }, { - "time_taken": 0.002142813056707382, + "time_taken": 0.004807693883776665, "success": true }, { - "time_taken": 0.004342632368206978, + "time_taken": 0.002131734974682331, "success": true }, { - "time_taken": 0.0019524255767464638, + "time_taken": 0.004488712176680565, "success": true } ], "error": "" }, "coq_logic_and_intro": { - "time_taken": 0.06537349801510572, + "time_taken": 0.06585466861724854, "success": true, "trials": [ { - "time_taken": 0.07824582792818546, + "time_taken": 0.06884935684502125, "success": true }, { - "time_taken": 0.06562335509806871, + "time_taken": 0.06585466861724854, "success": true }, { - "time_taken": 0.06537349801510572, + "time_taken": 0.06611263006925583, "success": true }, { - "time_taken": 0.08276569750159979, + "time_taken": 0.06744634080678225, "success": true }, { - "time_taken": 0.06738755572587252, + "time_taken": 0.06716502644121647, "success": true } ], "error": "" }, "mengine_logic_and_intro3": { - "time_taken": 0.0018761307001113892, + "time_taken": 0.0020926371216773987, "success": true, "trials": [ { - "time_taken": 0.0021279100328683853, + "time_taken": 0.0023488840088248253, "success": true }, { - "time_taken": 0.004665658809244633, + "time_taken": 0.005143108777701855, "success": true }, { - "time_taken": 0.001949230208992958, + "time_taken": 0.0021332083269953728, "success": true }, { - "time_taken": 0.0045388685539364815, + "time_taken": 0.004733380861580372, "success": true }, { - "time_taken": 0.0018761307001113892, + "time_taken": 0.0020926371216773987, "success": true } ], "error": "" }, "coq_logic_and_intro3": { - "time_taken": 0.06794031150639057, + "time_taken": 0.06637261155992746, "success": true, "trials": [ { - "time_taken": 0.07201241329312325, + "time_taken": 0.0842178026214242, "success": true }, { - "time_taken": 0.08989651687443256, + "time_taken": 0.06637261155992746, "success": true }, { - "time_taken": 0.08453252259641886, + "time_taken": 0.08457879722118378, "success": true }, { - "time_taken": 0.06794031150639057, + "time_taken": 0.06759936176240444, "success": true }, { - "time_taken": 0.07006465457379818, + "time_taken": 0.08286975976079702, "success": true } ], "error": "" }, "mengine_logic_imp_refl": { - "time_taken": 0.0023775771260261536, + "time_taken": 0.0018127365037798882, "success": true, "trials": [ { - "time_taken": 0.002612692303955555, + "time_taken": 0.004719128832221031, "success": true }, { - "time_taken": 0.002495034597814083, + "time_taken": 0.00427654106169939, "success": true }, { - "time_taken": 0.0024922918528318405, + "time_taken": 0.002036603167653084, "success": true }, { - "time_taken": 0.004849405959248543, + "time_taken": 0.0018127365037798882, "success": true }, { - "time_taken": 0.0023775771260261536, + "time_taken": 0.004712238907814026, "success": true } ], "error": "" }, "coq_logic_imp_refl": { - "time_taken": 0.06533516012132168, + "time_taken": 0.06395651586353779, "success": true, "trials": [ { - "time_taken": 0.0705872755497694, + "time_taken": 0.07988059986382723, "success": true }, { - "time_taken": 0.06823329254984856, + "time_taken": 0.07170805986970663, "success": true }, { - "time_taken": 0.07213691249489784, + "time_taken": 0.06901444680988789, "success": true }, { - "time_taken": 0.07505239080637693, + "time_taken": 0.07241684012115002, "success": true }, { - "time_taken": 0.06533516012132168, + "time_taken": 0.06395651586353779, "success": true } ], "error": "" }, "mengine_logic_imp_trans": { - "time_taken": 0.001803610473871231, + "time_taken": 0.0019318833947181702, "success": true, "trials": [ { - "time_taken": 0.003330945037305355, + "time_taken": 0.0021711448207497597, "success": true }, { - "time_taken": 0.002065124921500683, + "time_taken": 0.0019761519506573677, "success": true }, { - "time_taken": 0.001896030269563198, + "time_taken": 0.0027523422613739967, "success": true }, { - "time_taken": 0.001842910423874855, + "time_taken": 0.0019318833947181702, "success": true }, { - "time_taken": 0.001803610473871231, + "time_taken": 0.004634041339159012, "success": true } ], "error": "" }, "coq_logic_imp_trans": { - "time_taken": 0.06515374593436718, + "time_taken": 0.0658657057210803, "success": true, "trials": [ { - "time_taken": 0.06515374593436718, + "time_taken": 0.08446066919714212, "success": true }, { - "time_taken": 0.07420218642801046, + "time_taken": 0.0785121051594615, "success": true }, { - "time_taken": 0.0716068334877491, + "time_taken": 0.08537982497364283, "success": true }, { - "time_taken": 0.07185128051787615, + "time_taken": 0.06623136810958385, "success": true }, { - "time_taken": 0.07055314257740974, + "time_taken": 0.0658657057210803, "success": true } ], "error": "" }, "mengine_logic_or_introl": { - "time_taken": 0.0021398579701781273, + "time_taken": 0.001973136328160763, "success": true, "trials": [ { - "time_taken": 0.002498149871826172, + "time_taken": 0.003850228153169155, "success": true }, { - "time_taken": 0.00254646223038435, + "time_taken": 0.0037799030542373657, "success": true }, { - "time_taken": 0.0021398579701781273, + "time_taken": 0.002291478216648102, "success": true }, { - "time_taken": 0.0021884916350245476, + "time_taken": 0.003672279417514801, "success": true }, { - "time_taken": 0.0021713552996516228, + "time_taken": 0.001973136328160763, "success": true } ], "error": "" }, "coq_logic_or_introl": { - "time_taken": 0.06455874908715487, + "time_taken": 0.06446282006800175, "success": true, "trials": [ { - "time_taken": 0.07866571191698313, + "time_taken": 0.06461680214852095, "success": true }, { - "time_taken": 0.06972293090075254, + "time_taken": 0.06446282006800175, "success": true }, { - "time_taken": 0.08866208326071501, + "time_taken": 0.06457839533686638, "success": true }, { - "time_taken": 0.06455874908715487, + "time_taken": 0.0750699732452631, "success": true }, { - "time_taken": 0.07155965082347393, + "time_taken": 0.07473572716116905, "success": true } ], "error": "" }, "mengine_logic_or_intror": { - "time_taken": 0.0018694410100579262, + "time_taken": 0.00201426912099123, "success": true, "trials": [ { - "time_taken": 0.0021275291219353676, + "time_taken": 0.0027339551597833633, "success": true }, { - "time_taken": 0.0018694410100579262, + "time_taken": 0.002339388243854046, "success": true }, { - "time_taken": 0.0019745882600545883, + "time_taken": 0.00201426912099123, "success": true }, { - "time_taken": 0.0019634421914815903, + "time_taken": 0.004607111215591431, "success": true }, { - "time_taken": 0.0020487727597355843, + "time_taken": 0.0020930180326104164, "success": true } ], "error": "" }, "coq_logic_or_intror": { - "time_taken": 0.06703538540750742, + "time_taken": 0.06654090620577335, "success": true, "trials": [ { - "time_taken": 0.07749038841575384, + "time_taken": 0.07926558144390583, "success": true }, { - "time_taken": 0.06703538540750742, + "time_taken": 0.08230762183666229, "success": true }, { - "time_taken": 0.06928080227226019, + "time_taken": 0.06658150441944599, "success": true }, { - "time_taken": 0.07001374755054712, + "time_taken": 0.06654090620577335, "success": true }, { - "time_taken": 0.06879895646125078, + "time_taken": 0.0870300056412816, "success": true } ], "error": "" }, "mengine_nat_add_0_l": { - "time_taken": 0.0018455525860190392, + "time_taken": 0.001913687214255333, "success": true, "trials": [ { - "time_taken": 0.002109711989760399, + "time_taken": 0.0021476000547409058, "success": true }, { - "time_taken": 0.003782837651669979, + "time_taken": 0.0019213100895285606, "success": true }, { - "time_taken": 0.0018455525860190392, + "time_taken": 0.001913687214255333, "success": true }, { - "time_taken": 0.005703866481781006, + "time_taken": 0.005090869031846523, "success": true }, { - "time_taken": 0.0020541781559586525, + "time_taken": 0.0020480798557400703, "success": true } ], "error": "" }, "coq_nat_add_0_l": { - "time_taken": 0.06738493219017982, + "time_taken": 0.0742837954312563, "success": true, "trials": [ { - "time_taken": 0.07927136495709419, + "time_taken": 0.07685774005949497, "success": true }, { - "time_taken": 0.06826803553849459, + "time_taken": 0.08292183745652437, "success": true }, { - "time_taken": 0.06766507495194674, + "time_taken": 0.0745353251695633, "success": true }, { - "time_taken": 0.07551105134189129, + "time_taken": 0.0742837954312563, "success": true }, { - "time_taken": 0.06738493219017982, + "time_taken": 0.07469198107719421, "success": true } ], "error": "" }, "mengine_nat_add_2_2": { - "time_taken": 0.002002852037549019, + "time_taken": 0.0024623246863484383, "success": true, "trials": [ { - "time_taken": 0.0023316480219364166, + "time_taken": 0.0024623246863484383, "success": true }, { - "time_taken": 0.002466851845383644, + "time_taken": 0.004984287545084953, "success": true }, { - "time_taken": 0.0020456956699490547, + "time_taken": 0.002562396228313446, "success": true }, { - "time_taken": 0.0020163925364613533, + "time_taken": 0.004665889777243137, "success": true }, { - "time_taken": 0.002002852037549019, + "time_taken": 0.004965130239725113, "success": true } ], "error": "" }, "coq_nat_add_2_2": { - "time_taken": 0.06539278663694859, + "time_taken": 0.06615251954644918, "success": true, "trials": [ { - "time_taken": 0.07251646369695663, + "time_taken": 0.08780174888670444, "success": true }, { - "time_taken": 0.06539278663694859, + "time_taken": 0.06615251954644918, "success": true }, { - "time_taken": 0.06644356064498425, + "time_taken": 0.06643617618829012, "success": true }, { - "time_taken": 0.08276282344013453, + "time_taken": 0.08336795121431351, "success": true }, { - "time_taken": 0.0700690420344472, + "time_taken": 0.06765239126980305, "success": true } ], "error": "" }, "mengine_nat_add_3_4": { - "time_taken": 0.0020826924592256546, + "time_taken": 0.0020968345925211906, "success": true, "trials": [ { - "time_taken": 0.0022964337840676308, + "time_taken": 0.0023789694532752037, "success": true }, { - "time_taken": 0.0020826924592256546, + "time_taken": 0.0020968345925211906, "success": true }, { - "time_taken": 0.002455153502523899, + "time_taken": 0.0021431325003504753, "success": true }, { - "time_taken": 0.0025155944749712944, + "time_taken": 0.002117583528161049, "success": true }, { - "time_taken": 0.0021605808287858963, + "time_taken": 0.0021068593487143517, "success": true } ], "error": "" }, "coq_nat_add_3_4": { - "time_taken": 0.0757963927462697, + "time_taken": 0.06511866021901369, "success": true, "trials": [ { - "time_taken": 0.0757963927462697, + "time_taken": 0.06590109970420599, "success": true }, { - "time_taken": 0.09031297452747822, + "time_taken": 0.06511866021901369, "success": true }, { - "time_taken": 0.08658439852297306, + "time_taken": 0.07989962864667177, "success": true }, { - "time_taken": 0.08109507616609335, + "time_taken": 0.08352363668382168, "success": true }, { - "time_taken": 0.08568170573562384, + "time_taken": 0.07712439261376858, "success": true } ], "error": "" }, "mengine_nat_mul_2_3": { - "time_taken": 0.0026925019919872284, + "time_taken": 0.0024166060611605644, "success": true, "trials": [ { - "time_taken": 0.002743959426879883, + "time_taken": 0.006676902994513512, "success": true }, { - "time_taken": 0.006835583597421646, + "time_taken": 0.0025032367557287216, "success": true }, { - "time_taken": 0.0026925019919872284, + "time_taken": 0.005660690367221832, "success": true }, { - "time_taken": 0.006420597434043884, + "time_taken": 0.0024166060611605644, "success": true }, { - "time_taken": 0.00345437228679657, + "time_taken": 0.005849584937095642, "success": true } ], "error": "" }, "coq_nat_mul_2_3": { - "time_taken": 0.06630833726376295, + "time_taken": 0.06362138036638498, "success": true, "trials": [ { - "time_taken": 0.07668156921863556, + "time_taken": 0.06992145348340273, "success": true }, { - "time_taken": 0.06689528468996286, + "time_taken": 0.06375658512115479, "success": true }, { - "time_taken": 0.06630833726376295, + "time_taken": 0.06458192970603704, "success": true }, { - "time_taken": 0.08364323060959578, + "time_taken": 0.0857118871062994, "success": true }, { - "time_taken": 0.07155932951718569, + "time_taken": 0.06362138036638498, "success": true } ], "error": "" }, "mengine_nat_sub_5_2": { - "time_taken": 0.002279299311339855, + "time_taken": 0.002322763204574585, "success": true, "trials": [ { - "time_taken": 0.002385929226875305, + "time_taken": 0.002322763204574585, "success": true }, { - "time_taken": 0.005345353856682777, + "time_taken": 0.007078767754137516, "success": true }, { - "time_taken": 0.004974755458533764, + "time_taken": 0.004992461763322353, "success": true }, { - "time_taken": 0.002279299311339855, + "time_taken": 0.004753822460770607, "success": true }, { - "time_taken": 0.004986553452908993, + "time_taken": 0.005986601114273071, "success": true } ], "error": "" }, "coq_nat_sub_5_2": { - "time_taken": 0.06671494245529175, + "time_taken": 0.06497769709676504, "success": true, "trials": [ { - "time_taken": 0.08216926734894514, + "time_taken": 0.08255233056843281, "success": true }, { - "time_taken": 0.0669594295322895, + "time_taken": 0.08258469868451357, "success": true }, { - "time_taken": 0.0773226460441947, + "time_taken": 0.06497769709676504, "success": true }, { - "time_taken": 0.06671494245529175, + "time_taken": 0.07826078590005636, "success": true }, { - "time_taken": 0.07971495389938354, + "time_taken": 0.07975918613374233, + "success": true + } + ], + "error": "" + }, + "mengine_eq_f_equal": { + "time_taken": 0.0023021921515464783, + "success": true, + "trials": [ + { + "time_taken": 0.0025494061410427094, + "success": true + }, + { + "time_taken": 0.005503702908754349, + "success": true + }, + { + "time_taken": 0.0023021921515464783, + "success": true + }, + { + "time_taken": 0.002609006129205227, + "success": true + }, + { + "time_taken": 0.005915033631026745, + "success": true + } + ], + "error": "" + }, + "coq_eq_f_equal": { + "time_taken": 0.06720572803169489, + "success": true, + "trials": [ + { + "time_taken": 0.09473317675292492, + "success": true + }, + { + "time_taken": 0.06720572803169489, + "success": true + }, + { + "time_taken": 0.09213932510465384, + "success": true + }, + { + "time_taken": 0.07902595959603786, + "success": true + }, + { + "time_taken": 0.08310448285192251, + "success": true + } + ], + "error": "" + }, + "mengine_eq_f_equal2": { + "time_taken": 0.0032867081463336945, + "success": true, + "trials": [ + { + "time_taken": 0.0032867081463336945, + "success": true + }, + { + "time_taken": 0.003486078232526779, + "success": true + }, + { + "time_taken": 0.0038406942039728165, + "success": true + }, + { + "time_taken": 0.003721705637872219, + "success": true + }, + { + "time_taken": 0.004119003191590309, + "success": true + } + ], + "error": "" + }, + "coq_eq_f_equal2": { + "time_taken": 0.06660663429647684, + "success": true, + "trials": [ + { + "time_taken": 0.07883837819099426, + "success": true + }, + { + "time_taken": 0.06739858910441399, + "success": true + }, + { + "time_taken": 0.06785499677062035, + "success": true + }, + { + "time_taken": 0.06992525979876518, + "success": true + }, + { + "time_taken": 0.06660663429647684, + "success": true + } + ], + "error": "" + }, + "mengine_eq_refl_x": { + "time_taken": 0.0019109733402729034, + "success": true, + "trials": [ + { + "time_taken": 0.004513178952038288, + "success": true + }, + { + "time_taken": 0.0019109733402729034, + "success": true + }, + { + "time_taken": 0.004584507085382938, + "success": true + }, + { + "time_taken": 0.0019502229988574982, + "success": true + }, + { + "time_taken": 0.004398996010422707, + "success": true + } + ], + "error": "" + }, + "coq_eq_refl_x": { + "time_taken": 0.0656595854088664, + "success": true, + "trials": [ + { + "time_taken": 0.06692878063768148, + "success": true + }, + { + "time_taken": 0.06856753211468458, + "success": true + }, + { + "time_taken": 0.06999567709863186, + "success": true + }, + { + "time_taken": 0.0656595854088664, + "success": true + }, + { + "time_taken": 0.06731435190886259, + "success": true + } + ], + "error": "" + }, + "mengine_eq_sym": { + "time_taken": 0.0021871207281947136, + "success": true, + "trials": [ + { + "time_taken": 0.002621966414153576, + "success": true + }, + { + "time_taken": 0.0025716982781887054, + "success": true + }, + { + "time_taken": 0.002424446865916252, + "success": true + }, + { + "time_taken": 0.002843420021235943, + "success": true + }, + { + "time_taken": 0.0021871207281947136, + "success": true + } + ], + "error": "" + }, + "coq_eq_sym": { + "time_taken": 0.06909861322492361, + "success": true, + "trials": [ + { + "time_taken": 0.06909861322492361, + "success": true + }, + { + "time_taken": 0.08817581366747618, + "success": true + }, + { + "time_taken": 0.07619977742433548, + "success": true + }, + { + "time_taken": 0.07162346318364143, + "success": true + }, + { + "time_taken": 0.07528565730899572, + "success": true + } + ], + "error": "" + }, + "mengine_eq_trans": { + "time_taken": 0.0020335176959633827, + "success": true, + "trials": [ + { + "time_taken": 0.0025460515171289444, + "success": true + }, + { + "time_taken": 0.0020335176959633827, + "success": true + }, + { + "time_taken": 0.0023518484085798264, + "success": true + }, + { + "time_taken": 0.002618982456624508, + "success": true + }, + { + "time_taken": 0.0026467619463801384, + "success": true + } + ], + "error": "" + }, + "coq_eq_trans": { + "time_taken": 0.06562336161732674, + "success": true, + "trials": [ + { + "time_taken": 0.0688506793230772, + "success": true + }, + { + "time_taken": 0.06562336161732674, + "success": true + }, + { + "time_taken": 0.07860386371612549, + "success": true + }, + { + "time_taken": 0.07477541826665401, + "success": true + }, + { + "time_taken": 0.06566756777465343, + "success": true + } + ], + "error": "" + }, + "mengine_ex_eq": { + "time_taken": 0.0020573334768414497, + "success": true, + "trials": [ + { + "time_taken": 0.002191276289522648, + "success": true + }, + { + "time_taken": 0.0051902104169130325, + "success": true + }, + { + "time_taken": 0.0020573334768414497, + "success": true + }, + { + "time_taken": 0.0052135661244392395, + "success": true + }, + { + "time_taken": 0.0021401289850473404, + "success": true + } + ], + "error": "" + }, + "coq_ex_eq": { + "time_taken": 0.07652152143418789, + "success": true, + "trials": [ + { + "time_taken": 0.09101590979844332, + "success": true + }, + { + "time_taken": 0.08207838423550129, + "success": true + }, + { + "time_taken": 0.09275389090180397, + "success": true + }, + { + "time_taken": 0.07652152143418789, + "success": true + }, + { + "time_taken": 0.08868295885622501, + "success": true + } + ], + "error": "" + }, + "mengine_ex_intro": { + "time_taken": 0.0020213406533002853, + "success": true, + "trials": [ + { + "time_taken": 0.0021457262337207794, + "success": true + }, + { + "time_taken": 0.004445827566087246, + "success": true + }, + { + "time_taken": 0.0020213406533002853, + "success": true + }, + { + "time_taken": 0.0020739883184432983, + "success": true + }, + { + "time_taken": 0.002167920581996441, + "success": true + } + ], + "error": "" + }, + "coq_ex_intro": { + "time_taken": 0.06833333801478148, + "success": true, + "trials": [ + { + "time_taken": 0.08532416261732578, + "success": true + }, + { + "time_taken": 0.07514207065105438, + "success": true + }, + { + "time_taken": 0.073098368011415, + "success": true + }, + { + "time_taken": 0.07338623236864805, + "success": true + }, + { + "time_taken": 0.06833333801478148, "success": true } ], diff --git a/benchmarks/stdlib/stdlib_bench.py b/benchmarks/stdlib/stdlib_bench.py index d3721e8..e050bdf 100644 --- a/benchmarks/stdlib/stdlib_bench.py +++ b/benchmarks/stdlib/stdlib_bench.py @@ -67,6 +67,10 @@ def unit_tier(name): return "le" if name.startswith("logic_"): return "logic" + if name.startswith("eq_"): + return "eq" + if name.startswith("ex_"): + return "ex" return "other" diff --git a/benchmarks/stdlib/translate.py b/benchmarks/stdlib/translate.py index f2100e4..1e885f1 100644 --- a/benchmarks/stdlib/translate.py +++ b/benchmarks/stdlib/translate.py @@ -555,6 +555,11 @@ def emit(node, env): if kind == "app": f = emit(node[1], env) a = emit(node[2], env) + # A binder-headed argument (fun/forall/arrow) must be parenthesized: + # MEngine's parser will not accept a bare `fun …`/`forall …` as an + # application argument (e.g. the predicate of `ex A (fun y => …)`). + if node[2][0] in ("fun", "forall", "arrow"): + a = f"({a})" return f"({f} {a})" if kind == "arrow": a = emit(node[1], env) From 8098b0ca1b250879291c9be1e184405bf43f9e27 Mon Sep 17 00:00:00 2001 From: Dustin Jamner Date: Sun, 28 Jun 2026 00:59:23 +0000 Subject: [PATCH 07/71] Subtract per-engine startup floor from stdlib benchmark times Whole-file timing was unfair to Rocq: an empty .v already costs ~62 ms (process + auto-loaded Prelude), which is essentially the entire measured time for every trivial unit, so the headline ~30x "speedup" was really a process-startup ratio, not a proof-speed ratio. MEngine likewise pays ~4 ms for prelude + compat before any proof. `run` now times each engine's preamble alone (empty .v for Rocq, compat prelude with no unit for MEngine) with extra trials, stores it as a startup baseline, and `report` subtracts it to isolate the marginal statement+proof cost. Residuals at or below the baseline's own jitter are shown as ~0 (indistinguishable from startup), and the scatter draws both startup floors so the "everything sits on the floor" effect is visible. For the current Tier-A corpus all 33 proofs land below the noise floor on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01GNvD2hCU3jo1wq6fCLv9GT --- benchmarks/stdlib/README.md | 18 +- benchmarks/stdlib/plots/stdlib_scatter.png | Bin 74738 -> 86098 bytes benchmarks/stdlib/report.py | 175 +++- benchmarks/stdlib/results/REPORT.md | 80 +- benchmarks/stdlib/results/stdlib.json | 886 ++++++++++++--------- benchmarks/stdlib/stdlib_bench.py | 68 +- 6 files changed, 753 insertions(+), 474 deletions(-) diff --git a/benchmarks/stdlib/README.md b/benchmarks/stdlib/README.md index 6254d85..c76151d 100644 --- a/benchmarks/stdlib/README.md +++ b/benchmarks/stdlib/README.md @@ -41,9 +41,21 @@ always elaborable), which is why the proof side keeps the surface rules below. `stdlib_bench.py run` times each unit end-to-end in both engines (whole process, best of N trials). Both engines pay their own fixed startup: MEngine loads -`prelude/tactics.me` plus the compat prelude; Rocq starts its process and loads -`Coq.Init`. At this problem size the comparison reflects per-invocation cost, -not asymptotics. +`prelude/tactics.me` plus the compat prelude (~4 ms); Rocq starts its process and +loads its auto-loaded `Prelude` (~65 ms). At this problem size the *whole-file* +number is almost entirely this per-invocation cost, not the proof. + +**Startup-subtracted ("proof") times.** To compare proof cost fairly, `run` +first times each engine's preamble *alone* — an empty `.v` (the corpus has no +`Require`, so this loads exactly the same `Prelude` every unit does) and the +compat prelude with no unit appended — and records it as a startup baseline. +`report` subtracts that floor from each unit's whole-file time (clamped at 0) to +get the marginal statement+proof cost, and reports a residual at or below the +baseline's own run-to-run jitter as `~0` (indistinguishable from startup). For +the current Tier-A corpus every proof is trivial enough that its +startup-subtracted cost lands below the noise floor on both engines — so the +headline ~30× whole-file ratio is a *process-startup* ratio, and measuring proof +speed above the floor needs heavier units. ## Layout diff --git a/benchmarks/stdlib/plots/stdlib_scatter.png b/benchmarks/stdlib/plots/stdlib_scatter.png index b92f1099c19b076d79ee8dfbaf43edad37f01c6f..a32f4e7faade9f8f6ed265bdb44e8d85849f3548 100644 GIT binary patch literal 86098 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