Compare commits

..

33 Commits

Author SHA1 Message Date
Leonardo de Moura
32adce928c feat: add grind_pattern command
This PR introduces a command for specifying patterns used in the
heuristic instantiation of global theorems in the `grind` tactic.
Note that this PR only adds the parser.
2024-12-28 16:27:26 -08:00
Leonardo de Moura
5930db946c fix: Int.reduceNeg simproc (#6468)
This PR fixes issue #6467 


closes #6467
2024-12-28 22:58:09 +00:00
Leonardo de Moura
3fc74854d7 fix: check function types when detecting congruences in grind (#6466)
This PR completes the implementation of `addCongrTable` in the (WIP)
`grind` tactic. It also adds a new test to demonstrate why the extra
check is needed. It also updates the field `cgRoot` (congruence root).
2024-12-28 19:53:02 +00:00
Leonardo de Moura
fe45ddd610 feat: projections in grind (#6465)
This PR adds support for projection functions to the (WIP) `grind`
tactic.
2024-12-27 23:50:58 +00:00
Leonardo de Moura
f545df9922 feat: literal values in grind (#6464)
This PR completes support for literal values in the (WIP) `grind`
tactic. `grind` now closes the goal whenever it merges two equivalence
classes with distinct literal values.
2024-12-27 22:18:56 +00:00
Leonardo de Moura
844e82e176 feat: constructors in grind (#6463)
This PR adds support for constructors to the (WIP) `grind` tactic. When
merging equivalence classes, `grind` checks for equalities between
constructors. If they are distinct, it closes the goal; if they are the
same, it applies injectivity.
2024-12-27 21:15:02 +00:00
Leonardo de Moura
2d7d3388e2 fix: missing Not propagation rule in grind (#6461)
This PR adds a new propagation rule for negation to the (WIP) `grind`
tactic.
2024-12-27 17:37:32 +00:00
Henrik Böving
c14e5ae7de chore: implement reduceCond for bv_decide (#6460)
This PR implements the equivalent of `reduceIte` for `cond` in
`bv_decide` as we switched to `bif` for the `if` normal form.
2024-12-27 10:12:52 +00:00
Leonardo de Moura
6a839796fd feat: add grind tactic (#6459)
This PR adds the (WIP) `grind` tactic. It currently generates a warning
message to make it clear that the tactic is not ready for production.
2024-12-27 03:48:01 +00:00
Leonardo de Moura
e76dc20200 feat: use compact congruence proofs in grind if applicable (#6458)
This PR adds support for compact congruence proofs in the (WIP) `grind`
tactic. The `mkCongrProof` function now verifies whether the congruence
proof can be constructed using only `congr`, `congrFun`, and `congrArg`,
avoiding the need to generate the more complex `hcongr` auxiliary
theorems.
2024-12-26 23:58:04 +00:00
Leonardo de Moura
dca874ea57 feat: congruence proofs for grind (#6457)
This PR adds support for generating congruence proofs for congruences
detected by the `grind` tactic.
2024-12-26 22:20:36 +00:00
Leonardo de Moura
c282d558fa fix: fix: bug in mkEqProof within grind (#6456)
This PR fixes another bug in the equality proof generator in the (WIP)
`grind` tactic.
2024-12-26 19:03:35 +00:00
Leonardo de Moura
57050be3ab fix: bug in mkEqProof within grind (#6455)
This PR fixes a bug in the equality proof generator in the (WIP) `grind`
tactic.
2024-12-26 18:25:11 +00:00
Henrik Böving
37b53b70d0 perf: improve bv_decide performance with large literals (#6453)
This PR improves bv_decide's performance in the presence of large
literals.

The core change of this PR is the reformulation of the reflection code
for literals to:
```diff
 def eval (assign : Assignment) : BVExpr w → BitVec w
   | .var idx =>
-    let ⟨bv⟩ := assign.get idx
-    bv.truncate w
+    let packedBv := assign.get idx
+    /-
+    This formulation improves performance, as in a well formed expression the condition always holds
+    so there is no need for the more involved `BitVec.truncate` logic.
+    -/
+    if h : packedBv.w = w then
+      h ▸ packedBv.bv
+    else
+      packedBv.bv.truncate w
```
The remainder is merely further simplifications that make the terms
smaller and easier to deal with in general. This change is motivated by
applying the following diff to the kernel:
```diff
diff --git a/src/kernel/type_checker.cpp b/src/kernel/type_checker.cpp
index b0e6844dca..f13bb96bd4 100644
--- a/src/kernel/type_checker.cpp
+++ b/src/kernel/type_checker.cpp
@@ -518,6 +518,7 @@ optional<constant_info> type_checker::is_delta(expr const & e) const {
 optional<expr> type_checker::unfold_definition_core(expr const & e) {
     if (is_constant(e)) {
         if (auto d = is_delta(e)) {
+//            std::cout << "Working on unfolding: " << d->get_name() << std::endl;
             if (length(const_levels(e)) == d->get_num_lparams()) {
                 if (m_diag) {
                     m_diag->record_unfold(d->get_name());
```
and observing that in the test case from #6043 we see a long series of
```
Working on unfolding: Bool.decEq
Working on unfolding: Bool.decEq.match_1
Working on unfolding: Bool.casesOn
Working on unfolding: Nat.ble
Working on unfolding: Nat.brecOn
Working on unfolding: Nat.beq.match_1
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.beq.match_1
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.casesOn
```
the chain begins with `BitVec.truncate`, works through a few
abstractions and then continues like above forever, so I avoid the call
to truncate like this. It is not quite clear to me why removing `ofBool`
helps so much here, maybe some other kernel heuristic kicks in to rescue
us.

Either way this diff is a general improvement for reflection of `BitVec`
constants as we should never have to run `BitVec.truncate` again!

Fixes: #6043
2024-12-26 16:50:00 +00:00
Leonardo de Moura
8a1e50f0b9 feat: equality proof generation for grind (#6452)
This PR adds support for generating (small) proofs for any two
expressions that belong to the same equivalence class in the `grind`
tactic state.
2024-12-26 06:01:45 +00:00
Leonardo de Moura
bdcb7914b5 chore: check whether pointer equality implies structural equality in grind (#6451)
This PR checks whether in the internal state of the `grind` tactic
pointer equality implies structural equality.
2024-12-26 03:50:39 +00:00
Leonardo de Moura
0ebe9e5ba3 feat: support for builtin grind propagators (part 2) (#6449)
This PR completes the implementation of the command
`builtin_grind_propagator`.
2024-12-25 23:54:55 +00:00
Lean stage0 autoupdater
65e8ba0574 chore: update stage0 2024-12-25 23:27:32 +00:00
Leonardo de Moura
3cddae6492 feat: support for builtin grind propagators (#6448)
This PR declares the command `builtin_grind_propagator` for registering
equation propagator for `grind`. It also declares the auxiliary the
attribute.
2024-12-25 22:55:39 +00:00
Leonardo de Moura
977b8e001f refactor: move simplifier support to GrindM (#6447)
This PR refactors `grind` and adds support for invoking the simplifier
using the `GrindM` monad.
2024-12-25 21:01:32 +00:00
Leonardo de Moura
f9f8abe2a3 feat: propagate equality in grind (#6443)
This PR adds support for propagating the truth value of equalities in
the (WIP) `grind` tactic.
2024-12-24 23:54:36 +00:00
Leonardo de Moura
ec80de231e fix: checkParents in grind (#6442)
This PR fixes the `checkParents` sanity check in `grind`.
2024-12-24 22:45:18 +00:00
Leonardo de Moura
630577a9ea feat: truth value propagation for grind (#6441)
This PR adds basic truth value propagation rules to the (WIP) `grind`
tactic.
2024-12-24 21:12:53 +00:00
Leonardo de Moura
cde35bcc0d test: grind (#6440)
This PR adds additional tests for `grind` and fixed minor issues.
2024-12-24 04:33:05 +00:00
Leonardo de Moura
b18f3a3877 feat: detect congruent terms in grind (#6437)
This PR adds support for detecting congruent terms in the (WIP) `grind`
tactic. It also introduces the `grind.debug` option, which, when set to
`true`, checks many invariants after each equivalence class is merged.
This option is intended solely for debugging purposes.
2024-12-24 00:11:36 +00:00
Leonardo de Moura
5240405cf4 feat: congruence table for grind tactic (#6435)
This PR implements the congruence table for the (WIP) `grind` tactic. It
also fixes several bugs, and adds a new preprocessing step.
2024-12-23 02:31:42 +00:00
Leonardo de Moura
eb6c52e7e2 feat: canonicalizer for the grind tactic (#6433)
This PR adds a custom type and instance canonicalizer for the (WIP)
`grind` tactic. The `grind` tactic uses congruence closure but
disregards types, type formers, instances, and proofs. Proofs are
ignored due to proof irrelevance. Types, type formers, and instances are
considered supporting elements and are not factored into congruence
detection. Instead, `grind` only checks whether elements are
structurally equal, which, in the context of the `grind` tactic, is
equivalent to pointer equality. See new tests for examples where the
canonicalizer is important.
2024-12-21 22:32:25 +00:00
Kyle Miller
71942631d7 feat: explanations for cases applied to non-inductive types (#6378)
This PR adds an explanation to the error message when `cases` and
`induction` are applied to a term whose type is not an inductive type.
For `Prop`, these tactics now suggest the `by_cases` tactic. Example:
```
tactic 'cases' failed, major premise type is not an inductive type
  Prop

Explanation: the 'cases' tactic is for constructor-based reasoning as well as for applying
custom cases principles with a 'using' clause or a registered '@[cases_eliminator]' theorem.
The above type neither is an inductive type nor has a registered theorem.

Consider using the 'by_cases' tactic, which does true/false reasoning for propositions.
```

[Zulip
discussion](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Improving.20the.20error.20for.20.60cases.20p.60.20when.20.60p.60.20is.20a.20proposition/near/488882682)
2024-12-21 21:38:30 +00:00
Leonardo de Moura
16bc6ebcb6 fix: ensure simp and dsimp do not unfold too much (#6397)
This PR ensures that `simp` and `dsimp` do not unfold definitions that
are not intended to be unfolded by the user. See issue #5755 for an
example affected by this issue.

Closes #5755

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2024-12-21 04:16:15 +00:00
Leonardo de Moura
9e30ac3265 feat: add Expr.fvarsSubset (#6430)
This PR adds the predicate `Expr.fvarsSet a b`, which returns `true` if
and only if the free variables in `a` are a subset of the free variables
in `b`.
2024-12-20 22:29:47 +00:00
Cameron Zwarich
bf1d253764 feat: add support for extern LCNF decls (#6429)
This PR adds support for extern LCNF decls, which is required for parity
with the existing code generator.
2024-12-20 21:20:56 +00:00
Leonardo de Moura
052f3f54c8 fix: normalize universe levels in grind preprocessor (#6428)
This PR adds a new preprocessing step to the `grind` tactic:
universe-level normalization. The goal is to avoid missing equalities in
the congruence closure module.
2024-12-20 20:41:59 +00:00
Kim Morrison
39eaa214d4 chore: protect some lemmas in List/Array/Vector namespace (#6425) 2024-12-20 11:23:56 +00:00
224 changed files with 3147 additions and 843 deletions

View File

@@ -5,6 +5,11 @@ See below for the checklist for release candidates.
We'll use `v4.6.0` as the intended release version as a running example.
- One week before the planned release, ensure that
(1) someone has written the release notes and
(2) someone has written the first draft of the release blog post.
If there is any material in `./releases_drafts/` on the `releases/v4.6.0` branch, then the release notes are not done.
(See the section "Writing the release notes".)
- `git checkout releases/v4.6.0`
(This branch should already exist, from the release candidates.)
- `git pull`
@@ -81,7 +86,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
- Toolchain bump PR notes:
- In addition to updating the `lean-toolchain` and `lakefile.lean`,
in `.github/workflows/lean4checker.yml` update the line
`git checkout v4.6.0` to the appropriate tag.
`git checkout v4.6.0` to the appropriate tag.
- Push the PR branch to the main Mathlib repository rather than a fork, or CI may not work reliably
- Create and push the tag
- Create a new branch from the tag, push it, and open a pull request against `stable`.
@@ -134,13 +139,16 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
git checkout -b releases/v4.7.0
```
- In `RELEASES.md` replace `Development in progress` in the `v4.7.0` section with `Release notes to be written.`
- It is essential to choose the nightly that will become the release candidate as early as possible, to avoid confusion.
- We will rely on automatically generated release notes for release candidates,
and the written release notes will be used for stable versions only.
It is essential to choose the nightly that will become the release candidate as early as possible, to avoid confusion.
- In `src/CMakeLists.txt`,
- verify that you see `set(LEAN_VERSION_MINOR 7)` (for whichever `7` is appropriate); this should already have been updated when the development cycle began.
- `set(LEAN_VERSION_IS_RELEASE 1)` (this should be a change; on `master` and nightly releases it is always `0`).
- Commit your changes to `src/CMakeLists.txt`, and push.
- `git tag v4.7.0-rc1`
- `git push origin v4.7.0-rc1`
- Ping the FRO Zulip that release notes need to be written. The release notes do not block completing the rest of this checklist.
- Now wait, while CI runs.
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`, looking for the `v4.7.0-rc1` tag.
- This step can take up to an hour.
@@ -240,12 +248,15 @@ Please read https://leanprover-community.github.io/contribute/tags_and_branches.
# Writing the release notes
Release notes are automatically generated from the commit history, using `script/release_notes.py`.
We are currently trying a system where release notes are compiled all at once from someone looking through the commit history.
The exact steps are a work in progress.
Here is the general idea:
Run this as `script/release_notes.py v4.6.0`, where `v4.6.0` is the *previous* release version. This will generate output
for all commits since that tag. Note that there is output on both stderr, which should be manually reviewed,
and on stdout, which should be manually copied to `RELEASES.md`.
There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
* The work is done right on the `releases/v4.6.0` branch sometime after it is created but before the stable release is made.
The release notes for `v4.6.0` will later be copied to `master` when we begin a new development cycle.
* There can be material for release notes entries in commit messages.
* There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
See `./releases_drafts/README.md` for more information.
* The release notes should be written from a downstream expert user's point of view.
This section will be updated when the next release notes are written (for `v4.10.0`).

View File

@@ -1,145 +0,0 @@
#!/usr/bin/env python3
import sys
import re
import json
import requests
import subprocess
from collections import defaultdict
from git import Repo
def get_commits_since_tag(repo, tag):
try:
tag_commit = repo.commit(tag)
commits = list(repo.iter_commits(f"{tag_commit.hexsha}..HEAD"))
return [
(commit.hexsha, commit.message.splitlines()[0], commit.message)
for commit in commits
]
except Exception as e:
sys.stderr.write(f"Error retrieving commits: {e}\n")
sys.exit(1)
def check_pr_number(first_line):
match = re.search(r"\(\#(\d+)\)$", first_line)
if match:
return int(match.group(1))
return None
def fetch_pr_labels(pr_number):
try:
# Use gh CLI to fetch PR details
result = subprocess.run([
"gh", "api", f"repos/leanprover/lean4/pulls/{pr_number}"
], capture_output=True, text=True, check=True)
pr_data = result.stdout
pr_json = json.loads(pr_data)
return [label["name"] for label in pr_json.get("labels", [])]
except subprocess.CalledProcessError as e:
sys.stderr.write(f"Failed to fetch PR #{pr_number} using gh: {e.stderr}\n")
return []
def format_section_title(label):
title = label.replace("changelog-", "").capitalize()
if title == "Doc":
return "Documentation"
elif title == "Pp":
return "Pretty Printing"
return title
def sort_sections_order():
return [
"Language",
"Library",
"Compiler",
"Pretty Printing",
"Documentation",
"Server",
"Lake",
"Other",
"Uncategorised"
]
def format_markdown_description(pr_number, description):
link = f"[#{pr_number}](https://github.com/leanprover/lean4/pull/{pr_number})"
return f"{link} {description}"
def main():
if len(sys.argv) != 2:
sys.stderr.write("Usage: script.py <git-tag>\n")
sys.exit(1)
tag = sys.argv[1]
try:
repo = Repo(".")
except Exception as e:
sys.stderr.write(f"Error opening Git repository: {e}\n")
sys.exit(1)
commits = get_commits_since_tag(repo, tag)
sys.stderr.write(f"Found {len(commits)} commits since tag {tag}:\n")
for commit_hash, first_line, _ in commits:
sys.stderr.write(f"- {commit_hash}: {first_line}\n")
changelog = defaultdict(list)
for commit_hash, first_line, full_message in commits:
# Skip commits with the specific first lines
if first_line == "chore: update stage0" or first_line.startswith("chore: CI: bump "):
continue
pr_number = check_pr_number(first_line)
if not pr_number:
sys.stderr.write(f"No PR number found in {first_line}\n")
continue
# Remove the first line from the full_message for further processing
body = full_message[len(first_line):].strip()
paragraphs = body.split('\n\n')
second_paragraph = paragraphs[0] if len(paragraphs) > 0 else ""
labels = fetch_pr_labels(pr_number)
# Skip entries with the "changelog-no" label
if "changelog-no" in labels:
continue
report_errors = first_line.startswith("feat:") or first_line.startswith("fix:")
if not second_paragraph.startswith("This PR "):
if report_errors:
sys.stderr.write(f"No PR description found in commit:\n{commit_hash}\n{first_line}\n{body}\n\n")
fallback_description = re.sub(r":$", "", first_line.split(" ", 1)[1]).rsplit(" (#", 1)[0]
markdown_description = format_markdown_description(pr_number, fallback_description)
else:
continue
else:
markdown_description = format_markdown_description(pr_number, second_paragraph.replace("This PR ", ""))
changelog_labels = [label for label in labels if label.startswith("changelog-")]
if len(changelog_labels) > 1:
sys.stderr.write(f"Warning: Multiple changelog-* labels found for PR #{pr_number}: {changelog_labels}\n")
if not changelog_labels:
if report_errors:
sys.stderr.write(f"Warning: No changelog-* label found for PR #{pr_number}\n")
else:
continue
for label in changelog_labels:
changelog[label].append((pr_number, markdown_description))
section_order = sort_sections_order()
sorted_changelog = sorted(changelog.items(), key=lambda item: section_order.index(format_section_title(item[0])) if format_section_title(item[0]) in section_order else len(section_order))
for label, entries in sorted_changelog:
section_title = format_section_title(label) if label != "Uncategorised" else "Uncategorised"
print(f"## {section_title}\n")
for _, entry in sorted(entries, key=lambda x: x[0]):
print(f"* {entry}\n")
if __name__ == "__main__":
main()

View File

@@ -954,13 +954,18 @@ theorem size_eq_of_beq [BEq α] {xs ys : Array α} (h : xs == ys) : xs.size = ys
rw [Bool.eq_iff_iff]
simp +contextual
private theorem beq_of_beq_singleton [BEq α] {a b : α} : #[a] == #[b] a == b := by
intro h
have : isEqv #[a] #[b] BEq.beq = true := h
simp [isEqv, isEqvAux] at this
assumption
@[simp] theorem reflBEq_iff [BEq α] : ReflBEq (Array α) ReflBEq α := by
constructor
· intro h
constructor
intro a
suffices (#[a] == #[a]) = true by
simpa only [instBEq, isEqv, isEqvAux, Bool.and_true]
apply beq_of_beq_singleton
simp
· intro h
constructor
@@ -973,11 +978,9 @@ theorem size_eq_of_beq [BEq α] {xs ys : Array α} (h : xs == ys) : xs.size = ys
· intro a b h
apply singleton_inj.1
apply eq_of_beq
simp only [instBEq, isEqv, isEqvAux]
simpa
simpa [instBEq, isEqv, isEqvAux]
· intro a
suffices (#[a] == #[a]) = true by
simpa only [instBEq, isEqv, isEqvAux, Bool.and_true]
apply beq_of_beq_singleton
simp
· intro h
constructor

View File

@@ -17,8 +17,8 @@ namespace Array
@[simp] theorem lt_toList [LT α] (l₁ l₂ : Array α) : l₁.toList < l₂.toList l₁ < l₂ := Iff.rfl
@[simp] theorem le_toList [LT α] (l₁ l₂ : Array α) : l₁.toList l₂.toList l₁ l₂ := Iff.rfl
theorem not_lt_iff_ge [LT α] (l₁ l₂ : List α) : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
protected theorem not_lt_iff_ge [LT α] (l₁ l₂ : List α) : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
protected theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
¬ l₁ l₂ l₂ < l₁ :=
Decidable.not_not
@@ -135,7 +135,7 @@ protected theorem le_of_lt [DecidableEq α] [LT α] [DecidableLT α]
{l₁ l₂ : Array α} (h : l₁ < l₂) : l₁ l₂ :=
List.le_of_lt h
theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]
[Std.Total (¬ · < · : α α Prop)]
@@ -211,7 +211,7 @@ theorem lex_eq_false_iff_exists [BEq α] [PartialEquivBEq α] (lt : αα
cases l₂
simp_all [List.lex_eq_false_iff_exists]
theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Array α} :
protected theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Array α} :
l₁ < l₂
(l₁ = l₂.take l₁.size l₁.size < l₂.size)
( (i : Nat) (h₁ : i < l₁.size) (h₂ : i < l₂.size),
@@ -221,7 +221,7 @@ theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Arr
cases l₂
simp [List.lt_iff_exists]
theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)] {l₁ l₂ : Array α} :
@@ -258,14 +258,14 @@ theorem le_append_left [LT α] [Std.Irrefl (· < · : αα → Prop)]
cases l₂
simpa using List.le_append_left
theorem map_lt [LT α] [LT β]
protected theorem map_lt [LT α] [LT β]
{l₁ l₂ : Array α} {f : α β} (w : x y, x < y f x < f y) (h : l₁ < l₂) :
map f l₁ < map f l₂ := by
cases l₁
cases l₂
simpa using List.map_lt w h
theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
protected theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]

View File

@@ -14,8 +14,8 @@ namespace List
@[simp] theorem lex_lt [LT α] (l₁ l₂ : List α) : Lex (· < ·) l₁ l₂ l₁ < l₂ := Iff.rfl
@[simp] theorem not_lex_lt [LT α] (l₁ l₂ : List α) : ¬ Lex (· < ·) l₁ l₂ l₂ l₁ := Iff.rfl
theorem not_lt_iff_ge [LT α] (l₁ l₂ : List α) : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
protected theorem not_lt_iff_ge [LT α] (l₁ l₂ : List α) : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
protected theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
¬ l₁ l₂ l₂ < l₁ :=
Decidable.not_not
@@ -260,7 +260,7 @@ protected theorem le_of_lt [DecidableEq α] [LT α] [DecidableLT α]
· exfalso
exact h' h
theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]
[Std.Total (¬ · < · : α α Prop)]
@@ -333,7 +333,7 @@ theorem lex_eq_true_iff_exists [BEq α] (lt : αα → Bool) :
cases l₂ with
| nil => simp [lex]
| cons b l₂ =>
simp only [lex_cons_cons, Bool.or_eq_true, Bool.and_eq_true, ih, isEqv, length_cons]
simp [lex_cons_cons, Bool.or_eq_true, Bool.and_eq_true, ih, isEqv, length_cons]
constructor
· rintro (hab | hab, h₁, h₂ | i, h₁, h₂, w₁, w₂)
· exact .inr 0, by simp [hab]
@@ -397,7 +397,7 @@ theorem lex_eq_false_iff_exists [BEq α] [PartialEquivBEq α] (lt : αα
cases l₂ with
| nil => simp [lex]
| cons b l₂ =>
simp only [lex_cons_cons, Bool.or_eq_false_iff, Bool.and_eq_false_imp, ih, isEqv,
simp [lex_cons_cons, Bool.or_eq_false_iff, Bool.and_eq_false_imp, ih, isEqv,
Bool.and_eq_true, length_cons]
constructor
· rintro hab, h
@@ -435,7 +435,7 @@ theorem lex_eq_false_iff_exists [BEq α] [PartialEquivBEq α] (lt : αα
simpa using w₁ (j + 1) (by simpa)
· simpa using w₂
theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : List α} :
protected theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : List α} :
l₁ < l₂
(l₁ = l₂.take l₁.length l₁.length < l₂.length)
( (i : Nat) (h₁ : i < l₁.length) (h₂ : i < l₂.length),
@@ -444,7 +444,7 @@ theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Lis
rw [ lex_eq_true_iff_lt, lex_eq_true_iff_exists]
simp
theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)] {l₁ l₂ : List α} :
@@ -489,7 +489,7 @@ theorem IsPrefix.le [LT α] [Std.Irrefl (· < · : αα → Prop)]
rcases h with _, rfl
apply le_append_left
theorem map_lt [LT α] [LT β]
protected theorem map_lt [LT α] [LT β]
{l₁ l₂ : List α} {f : α β} (w : x y, x < y f x < f y) (h : l₁ < l₂) :
map f l₁ < map f l₂ := by
match l₁, l₂, h with
@@ -497,11 +497,11 @@ theorem map_lt [LT α] [LT β]
| nil, cons b l₂, h => simp
| cons a l₁, nil, h => simp at h
| cons a l₁, cons _ l₂, .cons h =>
simp [cons_lt_cons_iff, map_lt w (by simpa using h)]
simp [cons_lt_cons_iff, List.map_lt w (by simpa using h)]
| cons a l₁, cons b l₂, .rel h =>
simp [cons_lt_cons_iff, w, h]
theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
protected theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]
@@ -510,7 +510,7 @@ theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β
[Std.Antisymm (¬ · < · : β β Prop)]
{l₁ l₂ : List α} {f : α β} (w : x y, x < y f x < f y) (h : l₁ l₂) :
map f l₁ map f l₂ := by
rw [le_iff_exists] at h
rw [List.le_iff_exists] at h
obtain (h | i, h₁, h₂, w₁, w₂) := h
· left
rw [h]

View File

@@ -259,7 +259,7 @@ theorem zip_map (f : αγ) (g : β → δ) :
| [], _ => rfl
| _, [] => by simp only [map, zip_nil_right]
| _ :: _, _ :: _ => by
simp only [map, zip_cons_cons, zip_map, Prod.map]; constructor
simp only [map, zip_cons_cons, zip_map, Prod.map]; try constructor -- TODO: remove try constructor after update stage0
theorem zip_map_left (f : α γ) (l₁ : List α) (l₂ : List β) :
zip (l₁.map f) l₂ = (zip l₁ l₂).map (Prod.map f id) := by rw [ zip_map, map_id]

View File

@@ -19,6 +19,11 @@ namespace Vector
@[simp] theorem lt_toList [LT α] (l₁ l₂ : Vector α n) : l₁.toList < l₂.toList l₁ < l₂ := Iff.rfl
@[simp] theorem le_toList [LT α] (l₁ l₂ : Vector α n) : l₁.toList l₂.toList l₁ l₂ := Iff.rfl
protected theorem not_lt_iff_ge [LT α] (l₁ l₂ : Vector α n) : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
protected theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : Vector α n) :
¬ l₁ l₂ l₂ < l₁ :=
Decidable.not_not
@[simp] theorem mk_lt_mk [LT α] :
Vector.mk (α := α) (n := n) data₁ size₁ < Vector.mk data₂ size₂ data₁ < data₂ := Iff.rfl
@@ -133,7 +138,7 @@ protected theorem le_of_lt [DecidableEq α] [LT α] [DecidableLT α]
{l₁ l₂ : Vector α n} (h : l₁ < l₂) : l₁ l₂ :=
Array.le_of_lt h
theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_lt_or_eq [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]
[Std.Total (¬ · < · : α α Prop)]
@@ -200,14 +205,14 @@ theorem lex_eq_false_iff_exists [BEq α] [PartialEquivBEq α] (lt : αα
rcases l₂ with l₂, n₂
simp_all [Array.lex_eq_false_iff_exists, n₂]
theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Vector α n} :
protected theorem lt_iff_exists [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : Vector α n} :
l₁ < l₂
( (i : Nat) (h : i < n), ( j, (hj : j < i) l₁[j] = l₂[j]) l₁[i] < l₂[i]) := by
cases l₁
cases l₂
simp_all [Array.lt_iff_exists]
theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
protected theorem le_iff_exists [DecidableEq α] [LT α] [DecidableLT α]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)] {l₁ l₂ : Vector α n} :
@@ -230,12 +235,12 @@ theorem append_left_le [DecidableEq α] [LT α] [DecidableLT α]
l₁ ++ l₂ l₁ ++ l₃ := by
simpa using Array.append_left_le h
theorem map_lt [LT α] [LT β]
protected theorem map_lt [LT α] [LT β]
{l₁ l₂ : Vector α n} {f : α β} (w : x y, x < y f x < f y) (h : l₁ < l₂) :
map f l₁ < map f l₂ := by
simpa using Array.map_lt w h
theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
protected theorem map_le [DecidableEq α] [LT α] [DecidableLT α] [DecidableEq β] [LT β] [DecidableLT β]
[Std.Irrefl (· < · : α α Prop)]
[Std.Asymm (· < · : α α Prop)]
[Std.Antisymm (¬ · < · : α α Prop)]

View File

@@ -8,3 +8,5 @@ import Init.Grind.Norm
import Init.Grind.Tactics
import Init.Grind.Lemmas
import Init.Grind.Cases
import Init.Grind.Propagator
import Init.Grind.Util

View File

@@ -5,10 +5,49 @@ Authors: Leonardo de Moura
-/
prelude
import Init.Core
import Init.SimpLemmas
import Init.Classical
import Init.ByCases
namespace Lean.Grind
theorem intro_with_eq (p p' q : Prop) (he : p = p') (h : p' q) : p q :=
fun hp => h (he.mp hp)
/-! And -/
theorem and_eq_of_eq_true_left {a b : Prop} (h : a = True) : (a b) = b := by simp [h]
theorem and_eq_of_eq_true_right {a b : Prop} (h : b = True) : (a b) = a := by simp [h]
theorem and_eq_of_eq_false_left {a b : Prop} (h : a = False) : (a b) = False := by simp [h]
theorem and_eq_of_eq_false_right {a b : Prop} (h : b = False) : (a b) = False := by simp [h]
theorem eq_true_of_and_eq_true_left {a b : Prop} (h : (a b) = True) : a = True := by simp_all
theorem eq_true_of_and_eq_true_right {a b : Prop} (h : (a b) = True) : b = True := by simp_all
/-! Or -/
theorem or_eq_of_eq_true_left {a b : Prop} (h : a = True) : (a b) = True := by simp [h]
theorem or_eq_of_eq_true_right {a b : Prop} (h : b = True) : (a b) = True := by simp [h]
theorem or_eq_of_eq_false_left {a b : Prop} (h : a = False) : (a b) = b := by simp [h]
theorem or_eq_of_eq_false_right {a b : Prop} (h : b = False) : (a b) = a := by simp [h]
theorem eq_false_of_or_eq_false_left {a b : Prop} (h : (a b) = False) : a = False := by simp_all
theorem eq_false_of_or_eq_false_right {a b : Prop} (h : (a b) = False) : b = False := by simp_all
/-! Not -/
theorem not_eq_of_eq_true {a : Prop} (h : a = True) : (Not a) = False := by simp [h]
theorem not_eq_of_eq_false {a : Prop} (h : a = False) : (Not a) = True := by simp [h]
theorem eq_false_of_not_eq_true {a : Prop} (h : (Not a) = True) : a = False := by simp_all
theorem eq_true_of_not_eq_false {a : Prop} (h : (Not a) = False) : a = True := by simp_all
theorem false_of_not_eq_self {a : Prop} (h : (Not a) = a) : False := by
by_cases a <;> simp_all
/-! Eq -/
theorem eq_eq_of_eq_true_left {a b : Prop} (h : a = True) : (a = b) = b := by simp [h]
theorem eq_eq_of_eq_true_right {a b : Prop} (h : b = True) : (a = b) = a := by simp [h]
end Lean.Grind

View File

@@ -41,7 +41,7 @@ attribute [grind_norm] not_true
attribute [grind_norm] not_false_eq_true
-- Implication as a clause
@[grind_norm] theorem imp_eq (p q : Prop) : (p q) = (¬ p q) := by
@[grind_norm] theorem imp_eq (p q : Prop) : (p q) = (¬ p q) := by
by_cases p <;> by_cases q <;> simp [*]
-- And

View File

@@ -0,0 +1,27 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.NotationExtra
namespace Lean.Parser
/-- A user-defined propagator for the `grind` tactic. -/
-- TODO: not implemented yet
syntax (docComment)? "grind_propagator " (Tactic.simpPre <|> Tactic.simpPost) ident " (" ident ")" " := " term : command
/-- A builtin propagator for the `grind` tactic. -/
syntax (docComment)? "builtin_grind_propagator " ident (Tactic.simpPre <|> Tactic.simpPost) ident " := " term : command
/-- Auxiliary attribute for builtin `grind` propagators. -/
syntax (name := grindPropagatorBuiltinAttr) "builtin_grind_propagator" (Tactic.simpPre <|> Tactic.simpPost) ident : attr
macro_rules
| `($[$doc?:docComment]? builtin_grind_propagator $propagatorName:ident $direction $op:ident := $body) => do
let propagatorType := `Lean.Meta.Grind.Propagator
`($[$doc?:docComment]? def $propagatorName:ident : $(mkIdent propagatorType) := $body
attribute [builtin_grind_propagator $direction $op] $propagatorName)
end Lean.Parser

View File

@@ -19,7 +19,15 @@ structure Config where
eager : Bool := false
deriving Inhabited, BEq
end Lean.Grind
namespace Lean.Parser.Tactic
/-!
`grind` tactic and related tactics.
-/
end Lean.Grind
-- TODO: configuration option, parameters
syntax (name := grind) "grind" : tactic
end Lean.Parser.Tactic

19
src/Init/Grind/Util.lean Normal file
View File

@@ -0,0 +1,19 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Core
namespace Lean.Grind
/-- A helper gadget for annotating nested proofs in goals. -/
def nestedProof (p : Prop) (h : p) : p := h
set_option pp.proofs true
theorem nestedProof_congr (p q : Prop) (h : p = q) (hp : p) (hq : q) : HEq (nestedProof p hp) (nestedProof q hq) := by
subst h; apply HEq.refl
end Lean.Grind

View File

@@ -18,6 +18,7 @@ inductive ExternEntry where
| inline (backend : Name) (pattern : String)
| standard (backend : Name) (fn : String)
| foreign (backend : Name) (fn : String)
deriving BEq, Hashable
/--
- `@[extern]`
@@ -36,7 +37,7 @@ inductive ExternEntry where
structure ExternAttrData where
arity? : Option Nat := none
entries : List ExternEntry
deriving Inhabited
deriving Inhabited, BEq, Hashable
-- def externEntry := leading_parser optional ident >> optional (nonReservedSymbol "inline ") >> strLit
-- @[builtin_attr_parser] def extern := leading_parser nonReservedSymbol "extern " >> optional numLit >> many externEntry

View File

@@ -7,6 +7,7 @@ prelude
import Init.Data.List.BasicAux
import Lean.Expr
import Lean.Meta.Instances
import Lean.Compiler.ExternAttr
import Lean.Compiler.InlineAttrs
import Lean.Compiler.Specialize
import Lean.Compiler.LCNF.Types
@@ -429,6 +430,80 @@ where
| .cases c => c.alts.forM fun alt => go alt.getCode
| .unreach .. | .return .. | .jmp .. => return ()
partial def Code.instantiateValueLevelParams (code : Code) (levelParams : List Name) (us : List Level) : Code :=
instCode code
where
instLevel (u : Level) :=
u.instantiateParams levelParams us
instExpr (e : Expr) :=
e.instantiateLevelParamsNoCache levelParams us
instParams (ps : Array Param) :=
ps.mapMono fun p => p.updateCore (instExpr p.type)
instAlt (alt : Alt) :=
match alt with
| .default k => alt.updateCode (instCode k)
| .alt _ ps k => alt.updateAlt! (instParams ps) (instCode k)
instArg (arg : Arg) : Arg :=
match arg with
| .type e => arg.updateType! (instExpr e)
| .fvar .. | .erased => arg
instLetValue (e : LetValue) : LetValue :=
match e with
| .const declName vs args => e.updateConst! declName (vs.mapMono instLevel) (args.mapMono instArg)
| .fvar fvarId args => e.updateFVar! fvarId (args.mapMono instArg)
| .proj .. | .value .. | .erased => e
instLetDecl (decl : LetDecl) :=
decl.updateCore (instExpr decl.type) (instLetValue decl.value)
instFunDecl (decl : FunDecl) :=
decl.updateCore (instExpr decl.type) (instParams decl.params) (instCode decl.value)
instCode (code : Code) :=
match code with
| .let decl k => code.updateLet! (instLetDecl decl) (instCode k)
| .jp decl k | .fun decl k => code.updateFun! (instFunDecl decl) (instCode k)
| .cases c => code.updateCases! (instExpr c.resultType) c.discr (c.alts.mapMono instAlt)
| .jmp fvarId args => code.updateJmp! fvarId (args.mapMono instArg)
| .return .. => code
| .unreach type => code.updateUnreach! (instExpr type)
inductive DeclValue where
| code (code : Code)
| extern (externAttrData : ExternAttrData)
deriving Inhabited, BEq
partial def DeclValue.size : DeclValue Nat
| .code c => c.size
| .extern .. => 0
def DeclValue.mapCode (f : Code Code) : DeclValue DeclValue :=
fun
| .code c => .code (f c)
| .extern e => .extern e
def DeclValue.mapCodeM [Monad m] (f : Code m Code) : DeclValue m DeclValue :=
fun v => do
match v with
| .code c => return .code ( f c)
| .extern .. => return v
def DeclValue.forCodeM [Monad m] (f : Code m Unit) : DeclValue m Unit :=
fun v => do
match v with
| .code c => f c
| .extern .. => return ()
def DeclValue.isCodeAndM [Monad m] (v : DeclValue) (f : Code m Bool) : m Bool :=
match v with
| .code c => f c
| .extern .. => pure false
/--
Declaration being processed by the Lean to Lean compiler passes.
-/
@@ -455,7 +530,7 @@ structure Decl where
The body of the declaration, usually changes as it progresses
through compiler passes.
-/
value : Code
value : DeclValue
/--
We set this flag to true during LCNF conversion. When we receive
a block of functions to be compiled, we set this flag to `true`
@@ -536,7 +611,9 @@ We use this function to decide whether we should inline a declaration tagged wit
`[inline_if_reduce]` or not.
-/
def Decl.isCasesOnParam? (decl : Decl) : Option Nat :=
go decl.value
match decl.value with
| .code c => go c
| .extern .. => none
where
go (code : Code) : Option Nat :=
match code with
@@ -550,49 +627,6 @@ def Decl.instantiateTypeLevelParams (decl : Decl) (us : List Level) : Expr :=
def Decl.instantiateParamsLevelParams (decl : Decl) (us : List Level) : Array Param :=
decl.params.mapMono fun param => param.updateCore (param.type.instantiateLevelParamsNoCache decl.levelParams us)
partial def Decl.instantiateValueLevelParams (decl : Decl) (us : List Level) : Code :=
instCode decl.value
where
instLevel (u : Level) :=
u.instantiateParams decl.levelParams us
instExpr (e : Expr) :=
e.instantiateLevelParamsNoCache decl.levelParams us
instParams (ps : Array Param) :=
ps.mapMono fun p => p.updateCore (instExpr p.type)
instAlt (alt : Alt) :=
match alt with
| .default k => alt.updateCode (instCode k)
| .alt _ ps k => alt.updateAlt! (instParams ps) (instCode k)
instArg (arg : Arg) : Arg :=
match arg with
| .type e => arg.updateType! (instExpr e)
| .fvar .. | .erased => arg
instLetValue (e : LetValue) : LetValue :=
match e with
| .const declName vs args => e.updateConst! declName (vs.mapMono instLevel) (args.mapMono instArg)
| .fvar fvarId args => e.updateFVar! fvarId (args.mapMono instArg)
| .proj .. | .value .. | .erased => e
instLetDecl (decl : LetDecl) :=
decl.updateCore (instExpr decl.type) (instLetValue decl.value)
instFunDecl (decl : FunDecl) :=
decl.updateCore (instExpr decl.type) (instParams decl.params) (instCode decl.value)
instCode (code : Code) :=
match code with
| .let decl k => code.updateLet! (instLetDecl decl) (instCode k)
| .jp decl k | .fun decl k => code.updateFun! (instFunDecl decl) (instCode k)
| .cases c => code.updateCases! (instExpr c.resultType) c.discr (c.alts.mapMono instAlt)
| .jmp fvarId args => code.updateJmp! fvarId (args.mapMono instArg)
| .return .. => code
| .unreach type => code.updateUnreach! (instExpr type)
/--
Return `true` if the arrow type contains an instance implicit argument.
-/
@@ -693,7 +727,7 @@ where
visit k
go : StateM NameSet Unit :=
decls.forM fun decl => visit decl.value
decls.forM (·.value.forCodeM visit)
def instantiateRangeArgs (e : Expr) (beginIdx endIdx : Nat) (args : Array Arg) : Expr :=
if !e.hasLooseBVars then

View File

@@ -123,7 +123,10 @@ def FunDeclCore.etaExpand (decl : FunDecl) : CompilerM FunDecl := do
decl.update decl.type params value
def Decl.etaExpand (decl : Decl) : CompilerM Decl := do
let some (params, value) etaExpandCore? decl.type decl.params decl.value | return decl
return { decl with params, value }
match decl.value with
| .code code =>
let some (params, newCode) etaExpandCore? decl.type decl.params code | return decl
return { decl with params, value := .code newCode}
| .extern .. => return decl
end Lean.Compiler.LCNF

View File

@@ -102,7 +102,7 @@ end CSE
Common sub-expression elimination
-/
def Decl.cse (decl : Decl) : CompilerM Decl := do
let value decl.value.cse
let value decl.value.mapCodeM (·.cse)
return { decl with value }
def cse (phase : Phase := .base) (occurrence := 0) : Pass :=

View File

@@ -261,7 +261,7 @@ def run (x : CheckM α) : CompilerM α :=
end Check
def Decl.check (decl : Decl) : CompilerM Unit := do
Check.run do Check.checkFunDeclCore decl.name decl.params decl.type decl.value
Check.run do decl.value.forCodeM (Check.checkFunDeclCore decl.name decl.params decl.type)
/--
Check whether every local declaration in the local context is used in one of given `decls`.
@@ -299,7 +299,7 @@ where
visitDecl (decl : Decl) : StateM FVarIdHashSet Unit := do
visitParams decl.params
visitCode decl.value
decl.value.forCodeM visitCode
visitDecls (decls : Array Decl) : StateM FVarIdHashSet Unit :=
decls.forM visitDecl

View File

@@ -148,7 +148,7 @@ def eraseCodeDecls (decls : Array CodeDecl) : CompilerM Unit := do
def eraseDecl (decl : Decl) : CompilerM Unit := do
eraseParams decl.params
eraseCode decl.value
decl.value.forCodeM eraseCode
abbrev Decl.erase (decl : Decl) : CompilerM Unit :=
eraseDecl decl

View File

@@ -38,6 +38,7 @@ end
instance : Hashable Code where
hash c := hashCode c
deriving instance Hashable for DeclValue
deriving instance Hashable for Decl
end Lean.Compiler.LCNF
end Lean.Compiler.LCNF

View File

@@ -95,6 +95,6 @@ def Code.elimDead (code : Code) : CompilerM Code :=
ElimDead.elimDead code |>.run' {}
def Decl.elimDead (decl : Decl) : CompilerM Decl := do
return { decl with value := ( decl.value.elimDead) }
return { decl with value := ( decl.value.mapCodeM Code.elimDead) }
end Lean.Compiler.LCNF

View File

@@ -513,7 +513,7 @@ def inferStep : InterpM Bool := do
let currentVal getFunVal idx
withReader (fun ctx => { ctx with currFnIdx := idx }) do
decl.params.forM fun p => updateVarAssignment p.fvarId .top
interpCode decl.value
decl.value.forCodeM interpCode
let newVal getFunVal idx
if currentVal != newVal then
return true
@@ -538,7 +538,7 @@ Use the information produced by the abstract interpreter to:
-/
partial def elimDead (assignment : Assignment) (decl : Decl) : CompilerM Decl := do
trace[Compiler.elimDeadBranches] s!"Eliminating {decl.name} with {repr (← assignment.toArray |>.mapM (fun (name, val) => do return (toString (← getBinderName name), val)))}"
return { decl with value := ( go decl.value) }
return { decl with value := ( decl.value.mapCodeM go) }
where
go (code : Code) : CompilerM Code := do
match code with

View File

@@ -141,8 +141,9 @@ partial def evalApp (declName : Name) (args : Array Arg) : FixParamM Unit := do
let key := (declName, values)
unless ( get).visited.contains key do
modify fun s => { s with visited := s.visited.insert key }
let assignment := mkAssignment decl values
withReader (fun ctx => { ctx with assignment }) <| evalCode decl.value
decl.value.forCodeM fun c =>
let assignment := mkAssignment decl values
withReader (fun ctx => { ctx with assignment }) <| evalCode c
end
@@ -169,8 +170,12 @@ def mkFixedParamsMap (decls : Array Decl) : NameMap (Array Bool) := Id.run do
let values := mkInitialValues decl.params.size
let assignment := mkAssignment decl values
let fixed := Array.mkArray decl.params.size true
match evalCode decl.value |>.run { main := decl, decls, assignment } |>.run { fixed } with
| .ok _ s | .error _ s => result := result.insert decl.name s.fixed
match decl.value with
| .code c =>
match evalCode c |>.run { main := decl, decls, assignment } |>.run { fixed } with
| .ok _ s | .error _ s => result := result.insert decl.name s.fixed
| .extern .. =>
result := result.insert decl.name fixed
return result
end Lean.Compiler.LCNF

View File

@@ -239,7 +239,7 @@ Iterate through `decl`, pushing local declarations that are only used in one
control flow arm into said arm in order to avoid useless computations.
-/
partial def floatLetIn (decl : Decl) : CompilerM Decl := do
let newValue go decl.value |>.run {}
let newValue decl.value.mapCodeM go |>.run {}
return { decl with value := newValue }
where
/--

View File

@@ -108,7 +108,7 @@ where
go (decl : Decl) : InternalizeM Decl := do
let type normExpr decl.type
let params decl.params.mapM internalizeParam
let value internalizeCode decl.value
let value decl.value.mapCodeM internalizeCode
return { decl with type, params, value }
/--

View File

@@ -133,7 +133,7 @@ this. This is because otherwise the calls to `myjp` in `f` and `g` would
produce out of scope join point jumps.
-/
partial def find (decl : Decl) : CompilerM FindState := do
let (_, candidates) go decl.value |>.run none |>.run {} |>.run' {}
let (_, candidates) decl.value.forCodeM go |>.run none |>.run {} |>.run' {}
return candidates
where
go : Code FindM Unit
@@ -178,7 +178,7 @@ and all calls to them with `jmp`s.
partial def replace (decl : Decl) (state : FindState) : CompilerM Decl := do
let mapper := fun acc cname _ => do return acc.insert cname ( mkFreshJpName)
let replaceCtx : ReplaceCtx state.candidates.foldM (init := .empty) mapper
let newValue go decl.value |>.run replaceCtx
let newValue decl.value.mapCodeM go |>.run replaceCtx
return { decl with value := newValue }
where
go (code : Code) : ReplaceM Code := do
@@ -389,7 +389,7 @@ position within the code so we can pull them out as far as possible, hopefully
enabling new inlining possibilities in the next simplifier run.
-/
partial def extend (decl : Decl) : CompilerM Decl := do
let newValue go decl.value |>.run {} |>.run' {} |>.run' {}
let newValue decl.value.mapCodeM go |>.run {} |>.run' {} |>.run' {}
let decl := { decl with value := newValue }
decl.pullFunDecls
where
@@ -510,8 +510,8 @@ After we have performed all of these optimizations we can take away the
code that has as little arguments as possible in the join points.
-/
partial def reduce (decl : Decl) : CompilerM Decl := do
let (_, analysis) goAnalyze decl.value |>.run {} |>.run {} |>.run' {}
let newValue goReduce decl.value |>.run analysis
let (_, analysis) decl.value.forCodeM goAnalyze |>.run {} |>.run {} |>.run' {}
let newValue decl.value.mapCodeM goReduce |>.run analysis
return { decl with value := newValue }
where
goAnalyzeFunDecl (fn : FunDecl) : ReduceAnalysisM Unit := do

View File

@@ -108,9 +108,10 @@ def mkAuxDecl (closure : Array Param) (decl : FunDecl) : LiftM LetDecl := do
where
go (nameNew : Name) (safe : Bool) (inlineAttr? : Option InlineAttributeKind) : InternalizeM Decl := do
let params := ( closure.mapM internalizeParam) ++ ( decl.params.mapM internalizeParam)
let value internalizeCode decl.value
let type value.inferType
let code internalizeCode decl.value
let type code.inferType
let type mkForallParams params type
let value := .code code
let decl := { name := nameNew, levelParams := [], params, type, value, safe, inlineAttr?, recursive := false : Decl }
return decl.setLevelParams
@@ -149,7 +150,7 @@ mutual
end
def main (decl : Decl) : LiftM Decl := do
let value withParams decl.params <| visitCode decl.value
let value withParams decl.params <| decl.value.mapCodeM visitCode
return { decl with value }
end LambdaLifting

View File

@@ -139,6 +139,10 @@ mutual
| .jmp _ args => visitArgs args
end
def visitDeclValue : DeclValue Visitor
| .code c => visitCode c
| .extern .. => id
end CollectLevelParams
open Lean.CollectLevelParams
@@ -149,7 +153,7 @@ Collect universe level parameters collecting in the type, parameters, and value,
set `decl.levelParams` with the resulting value.
-/
def Decl.setLevelParams (decl : Decl) : Decl :=
let levelParams := (visitCode decl.value visitParams decl.params visitType decl.type) {} |>.params.toList
let levelParams := (visitDeclValue decl.value visitParams decl.params visitType decl.type) {} |>.params.toList
{ decl with levelParams }
end Lean.Compiler.LCNF

View File

@@ -28,10 +28,10 @@ and `[specialize]` since they can be partially applied.
-/
def shouldGenerateCode (declName : Name) : CoreM Bool := do
if ( isCompIrrelevant |>.run') then return false
let env getEnv
if isExtern env declName then return true
let some info getDeclInfo? declName | return false
unless info.hasValue (allowOpaque := true) do return false
let env getEnv
if isExtern env declName then return false
if hasMacroInlineAttribute env declName then return false
if ( Meta.isMatcher declName) then return false
if isCasesOnRecursor env declName then return false

View File

@@ -105,6 +105,11 @@ mutual
return f!"⊥ : {← ppExpr type}"
else
return ""
partial def ppDeclValue (b : DeclValue) : M Format := do
match b with
| .code c => ppCode c
| .extern .. => return "extern"
end
def run (x : M α) : CompilerM α :=
@@ -121,7 +126,7 @@ def ppLetValue (e : LetValue) : CompilerM Format :=
def ppDecl (decl : Decl) : CompilerM Format :=
PP.run do
return f!"def {decl.name}{← PP.ppParams decl.params} : {← PP.ppExpr (← PP.getFunType decl.params decl.type)} :={indentD (← PP.ppCode decl.value)}"
return f!"def {decl.name}{← PP.ppParams decl.params} : {← PP.ppExpr (← PP.getFunType decl.params decl.type)} :={indentD (← PP.ppDeclValue decl.value)}"
def ppFunDecl (decl : FunDecl) : CompilerM Format :=
PP.run do

View File

@@ -57,7 +57,7 @@ where
| .cases (cases : CasesCore Code) => cases.alts.forM (go ·.getCode)
| .jmp .. | .return .. | .unreach .. => return ()
start (decls : Array Decl) : StateRefT (Array LetValue) CompilerM Unit :=
decls.forM (go ·.value)
decls.forM (·.value.forCodeM go)
partial def getJps : Probe Decl FunDecl := fun decls => do
let (_, res) start decls |>.run #[]
@@ -72,10 +72,10 @@ where
| .jmp .. | .return .. | .unreach .. => return ()
start (decls : Array Decl) : StateRefT (Array FunDecl) CompilerM Unit :=
decls.forM fun decl => go decl.value
decls.forM (·.value.forCodeM go)
partial def filterByLet (f : LetDecl CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let decl k => do if ( f decl) then return true else go k
@@ -84,7 +84,7 @@ where
| .jmp .. | .return .. | .unreach .. => return false
partial def filterByFun (f : FunDecl CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k | .jp _ k => go k
@@ -93,7 +93,7 @@ where
| .jmp .. | .return .. | .unreach .. => return false
partial def filterByJp (f : FunDecl CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k
@@ -103,7 +103,7 @@ where
| .jmp .. | .return .. | .unreach .. => return false
partial def filterByFunDecl (f : FunDecl CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k
@@ -112,7 +112,7 @@ where
| .jmp .. | .return .. | .unreach .. => return false
partial def filterByCases (f : Cases CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k
@@ -121,7 +121,7 @@ where
| .jmp .. | .return .. | .unreach .. => return false
partial def filterByJmp (f : FVarId Array Arg CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k
@@ -131,7 +131,7 @@ where
| .return .. | .unreach .. => return false
partial def filterByReturn (f : FVarId CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k
@@ -141,7 +141,7 @@ where
| .return var => f var
partial def filterByUnreach (f : Expr CompilerM Bool) : Probe Decl Decl :=
filter (fun decl => go decl.value)
filter (·.value.isCodeAndM go)
where
go : Code CompilerM Bool
| .let _ k => go k

View File

@@ -172,8 +172,8 @@ open PullFunDecls
Pull local function declarations and join points in the given declaration.
-/
def Decl.pullFunDecls (decl : Decl) : CompilerM Decl := do
let (value, ps) pull decl.value |>.run []
let value := attach ps.toArray value
let (value, ps) decl.value.mapCodeM pull |>.run []
let value := value.mapCode (attach ps.toArray)
return { decl with value }
def pullFunDecls : Pass :=

View File

@@ -96,8 +96,8 @@ open PullLetDecls
def Decl.pullLetDecls (decl : Decl) (isCandidateFn : LetDecl FVarIdSet CompilerM Bool) : CompilerM Decl := do
PullM.run (isCandidateFn := isCandidateFn) do
withParams decl.params do
let value pullDecls decl.value
let value attachToPull value
let value decl.value.mapCodeM pullDecls
let value value.mapCodeM attachToPull
return { decl with value }
def Decl.pullInstances (decl : Decl) : CompilerM Decl :=

View File

@@ -108,7 +108,7 @@ partial def visit (code : Code) : FindUsedM Unit := do
def collectUsedParams (decl : Decl) : CompilerM FVarIdSet := do
let params := decl.params.foldl (init := {}) fun s p => s.insert p.fvarId
let (_, { used, .. }) visit decl.value |>.run { decl, params } |>.run {}
let (_, { used, .. }) decl.value.forCodeM visit |>.run { decl, params } |>.run {}
return used
end FindUsed
@@ -146,37 +146,40 @@ end ReduceArity
open FindUsed ReduceArity Internalize
def Decl.reduceArity (decl : Decl) : CompilerM (Array Decl) := do
let used collectUsedParams decl
if used.size == decl.params.size then
return #[decl] -- Declarations uses all parameters
else
trace[Compiler.reduceArity] "{decl.name}, used params: {used.toList.map mkFVar}"
let mask := decl.params.map fun param => used.contains param.fvarId
let auxName := decl.name ++ `_redArg
let mkAuxDecl : CompilerM Decl := do
let params := decl.params.filter fun param => used.contains param.fvarId
let value reduce decl.value |>.run { declName := decl.name, auxDeclName := auxName, paramMask := mask }
let type value.inferType
let type mkForallParams params type
let auxDecl := { decl with name := auxName, levelParams := [], type, params, value }
auxDecl.saveMono
return auxDecl
let updateDecl : InternalizeM Decl := do
let params decl.params.mapM internalizeParam
let mut args := #[]
for used in mask, param in params do
if used then
args := args.push param.toArg
let letDecl mkAuxLetDecl (.const auxName [] args)
let value := .let letDecl (.return letDecl.fvarId)
let decl := { decl with params, value, inlineAttr? := some .inline, recursive := false }
decl.saveMono
return decl
let unusedParams := decl.params.filter fun param => !used.contains param.fvarId
let auxDecl mkAuxDecl
let decl updateDecl |>.run' {}
eraseParams unusedParams
return #[auxDecl, decl]
match decl.value with
| .code code =>
let used collectUsedParams decl
if used.size == decl.params.size then
return #[decl] -- Declarations uses all parameters
else
trace[Compiler.reduceArity] "{decl.name}, used params: {used.toList.map mkFVar}"
let mask := decl.params.map fun param => used.contains param.fvarId
let auxName := decl.name ++ `_redArg
let mkAuxDecl : CompilerM Decl := do
let params := decl.params.filter fun param => used.contains param.fvarId
let value decl.value.mapCodeM reduce |>.run { declName := decl.name, auxDeclName := auxName, paramMask := mask }
let type code.inferType
let type mkForallParams params type
let auxDecl := { decl with name := auxName, levelParams := [], type, params, value }
auxDecl.saveMono
return auxDecl
let updateDecl : InternalizeM Decl := do
let params decl.params.mapM internalizeParam
let mut args := #[]
for used in mask, param in params do
if used then
args := args.push param.toArg
let letDecl mkAuxLetDecl (.const auxName [] args)
let value := .code (.let letDecl (.return letDecl.fvarId))
let decl := { decl with params, value, inlineAttr? := some .inline, recursive := false }
decl.saveMono
return decl
let unusedParams := decl.params.filter fun param => !used.contains param.fvarId
let auxDecl mkAuxDecl
let decl updateDecl |>.run' {}
eraseParams unusedParams
return #[auxDecl, decl]
| .extern .. => return #[decl]
def reduceArity : Pass where
phase := .mono
@@ -187,4 +190,4 @@ def reduceArity : Pass where
builtin_initialize
registerTraceClass `Compiler.reduceArity (inherited := true)
end Lean.Compiler.LCNF
end Lean.Compiler.LCNF

View File

@@ -68,7 +68,7 @@ open ReduceJpArity
Try to reduce arity of join points
-/
def Decl.reduceJpArity (decl : Decl) : CompilerM Decl := do
let value reduce decl.value |>.run {}
let value decl.value.mapCodeM reduce |>.run {}
return { decl with value }
def reduceJpArity (phase := Phase.base) : Pass :=

View File

@@ -55,7 +55,7 @@ def Decl.applyRenaming (decl : Decl) (r : Renaming) : CompilerM Decl := do
return decl
else
let params decl.params.mapMonoM (·.applyRenaming r)
let value decl.value.applyRenaming r
let value decl.value.mapCodeM (·.applyRenaming r)
return { decl with params, value }
end Lean.Compiler.LCNF
end Lean.Compiler.LCNF

View File

@@ -22,19 +22,20 @@ namespace Lean.Compiler.LCNF
open Simp
def Decl.simp? (decl : Decl) : SimpM (Option Decl) := do
updateFunDeclInfo decl.value
let .code code := decl.value | return none
updateFunDeclInfo code
traceM `Compiler.simp.inline.info do return m!"{decl.name}:{Format.nest 2 (← (← get).funDeclInfoMap.format)}"
traceM `Compiler.simp.step do ppDecl decl
let value simp decl.value
let code simp code
let s get
let value value.applyRenaming s.binderRenaming
traceM `Compiler.simp.step.new do return m!"{decl.name} :=\n{← ppCode value}"
trace[Compiler.simp.stat] "{decl.name}, size: {value.size}, # visited: {s.visited}, # inline: {s.inline}, # inline local: {s.inlineLocal}"
if let some value simpJpCases? value then
let decl := { decl with value }
let code code.applyRenaming s.binderRenaming
traceM `Compiler.simp.step.new do return m!"{decl.name} :=\n{← ppCode code}"
trace[Compiler.simp.stat] "{decl.name}, size: {code.size}, # visited: {s.visited}, # inline: {s.inline}, # inline local: {s.inlineLocal}"
if let some code simpJpCases? code then
let decl := { decl with value := .code code }
decl.reduceJpArity
else if ( get).simplified then
return some { decl with value }
return some { decl with value := .code code }
else
return none

View File

@@ -43,6 +43,7 @@ def inlineCandidate? (e : LetValue) : SimpM (Option InlineCandidateInfo) := do
unless ( read).config.inlineDefs do
return none
let some decl getDecl? declName | return none
let .code code := decl.value | return none
let shouldInline : SimpM Bool := do
if !decl.inlineIfReduceAttr && decl.recursive then return false
if mustInline then return true
@@ -63,9 +64,8 @@ def inlineCandidate? (e : LetValue) : SimpM (Option InlineCandidateInfo) := do
if decl.alwaysInlineAttr then return true
-- TODO: check inlining quota
if decl.inlineAttr || decl.inlineIfReduceAttr then return true
unless decl.noinlineAttr do
if ( isSmall decl.value) then return true
return false
if decl.noinlineAttr then return false
isSmall code
unless ( shouldInline) do return none
/- check arity -/
let arity := decl.getArity
@@ -77,7 +77,7 @@ def inlineCandidate? (e : LetValue) : SimpM (Option InlineCandidateInfo) := do
let arg := args[paramIdx]!
unless ( arg.isConstructorApp) do return none
let params := decl.instantiateParamsLevelParams us
let value := decl.instantiateValueLevelParams us
let value := code.instantiateValueLevelParams decl.levelParams us
let type := decl.instantiateTypeLevelParams us
incInline
return some {

View File

@@ -69,11 +69,14 @@ where
visit fvarId projs
else
let some decl getDecl? declName | failure
guard (decl.getArity == args.size)
let params := decl.instantiateParamsLevelParams us
let code := decl.instantiateValueLevelParams us
let code betaReduce params code args (mustInline := true)
visitCode code projs
match decl.value with
| .code code =>
guard (decl.getArity == args.size)
let params := decl.instantiateParamsLevelParams us
let code := code.instantiateValueLevelParams decl.levelParams us
let code betaReduce params code args (mustInline := true)
visitCode code projs
| .extern .. => failure
visitCode (code : Code) (projs : List Nat) : OptionT (StateRefT (Array CodeDecl) SimpM) FVarId := do
match code with

View File

@@ -222,6 +222,7 @@ def mkSpecDecl (decl : Decl) (us : List Level) (argMask : Array (Option Arg)) (p
eraseDecl decl
where
go (decl : Decl) (nameNew : Name) : InternalizeM Decl := do
let .code code := decl.value | panic! "can only specialize decls with code"
let mut params params.mapM internalizeParam
let decls decls.mapM internalizeCodeDecl
for param in decl.params, arg in argMask do
@@ -235,11 +236,12 @@ where
for param in decl.params[argMask.size:] do
let param := { param with type := param.type.instantiateLevelParamsNoCache decl.levelParams us }
params := params.push ( internalizeParam param)
let value := decl.instantiateValueLevelParams us
let value internalizeCode value
let value := attachCodeDecls decls value
let type value.inferType
let code := code.instantiateValueLevelParams decl.levelParams us
let code internalizeCode code
let code := attachCodeDecls decls code
let type code.inferType
let type mkForallParams params type
let value := .code code
let safe := decl.safe
let recursive := decl.recursive
let decl := { name := nameNew, levelParams := levelParamsNew, params, type, value, safe, recursive, inlineAttr? := none : Decl }
@@ -268,6 +270,7 @@ mutual
let some paramsInfo getSpecParamInfo? declName | return none
unless ( shouldSpecialize paramsInfo args) do return none
let some decl getDecl? declName | return none
let .code _ := decl.value | return none
trace[Compiler.specialize.candidate] "{e.toExpr}, {paramsInfo}"
let (argMask, params, decls) Collector.collect paramsInfo args
let keyBody := .const declName us (argMask.filterMap id)
@@ -290,7 +293,7 @@ mutual
let specDecl specDecl.simp {}
let specDecl specDecl.simp { etaPoly := true, inlinePartial := true, implementedBy := true }
let value withReader (fun _ => { declName := specDecl.name }) do
withParams specDecl.params <| visitCode specDecl.value
withParams specDecl.params <| specDecl.value.mapCodeM visitCode
let specDecl := { specDecl with value }
modify fun s => { s with decls := s.decls.push specDecl }
return some (.const specDecl.name usNew argsNew)
@@ -325,7 +328,7 @@ def main (decl : Decl) : SpecializeM Decl := do
if ( decl.isTemplateLike) then
return decl
else
let value withParams decl.params <| visitCode decl.value
let value withParams decl.params <| decl.value.mapCodeM visitCode
return { decl with value }
end Specialize

View File

@@ -235,12 +235,17 @@ Assert that the pass under test produces `Decl`s that do not contain
`Expr.const constName` in their `Code.let` values anymore.
-/
def assertDoesNotContainConstAfter (constName : Name) (msg : String) : TestInstaller :=
assertForEachDeclAfterEachOccurrence (fun _ decl => !decl.value.containsConst constName) msg
assertForEachDeclAfterEachOccurrence
fun _ decl =>
match decl.value with
| .code c => !c.containsConst constName
| .extern .. => true
msg
def assertNoFun : TestInstaller :=
assertAfter do
for decl in ( getDecls) do
decl.value.forM fun
decl.value.forCodeM fun
| .fun .. => throwError "declaration `{decl.name}` contains a local function declaration"
| _ => return ()

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
prelude
import Lean.Meta.Transform
import Lean.Meta.Match.MatcherInfo
import Lean.Compiler.ExternAttr
import Lean.Compiler.ImplementedByAttr
import Lean.Compiler.LCNF.ToLCNF
@@ -96,31 +97,48 @@ The steps for this are roughly:
def toDecl (declName : Name) : CompilerM Decl := do
let declName := if let some name := isUnsafeRecName? declName then name else declName
let some info getDeclInfo? declName | throwError "declaration `{declName}` not found"
let some value := info.value? (allowOpaque := true) | throwError "declaration `{declName}` does not have a value"
let (type, value) Meta.MetaM.run' do
let type toLCNFType info.type
let value Meta.lambdaTelescope value fun xs body => do Meta.mkLambdaFVars xs ( Meta.etaExpand body)
let value replaceUnsafeRecNames value
let value macroInline value
/- Recall that some declarations tagged with `macro_inline` contain matchers. -/
let value inlineMatchers value
/- Recall that `inlineMatchers` may have exposed `ite`s and `dite`s which are tagged as `[macro_inline]`. -/
let value macroInline value
/-
Remark: we have disabled the following transformatbion, we will perform it at phase 2, after code specialization.
It prevents many optimizations (e.g., "cases-of-ctor").
-/
-- let value ← applyCasesOnImplementedBy value
return (type, value)
let value toLCNF value
let safe := !info.isPartial && !info.isUnsafe
let inlineAttr? := getInlineAttribute? ( getEnv) declName
let decl if let .fun decl (.return _) := value then
eraseFunDecl decl (recursive := false)
pure { name := declName, params := decl.params, type, value := decl.value, levelParams := info.levelParams, safe, inlineAttr? : Decl }
if let some externAttrData := getExternAttrData? ( getEnv) declName then
let paramsFromTypeBinders (expr : Expr) : CompilerM (Array Param) := do
let mut params := #[]
let mut currentExpr := expr
repeat
match currentExpr with
| .forallE binderName type body _ =>
let borrow := isMarkedBorrowed type
params := params.push ( mkParam binderName type borrow)
currentExpr := body
| _ => break
return params
let type Meta.MetaM.run' (toLCNFType info.type)
let params paramsFromTypeBinders type
return { name := declName, params, type, value := .extern externAttrData, levelParams := info.levelParams, safe, inlineAttr? }
else
pure { name := declName, params := #[], type, value, levelParams := info.levelParams, safe, inlineAttr? }
/- `toLCNF` may eta-reduce simple declarations. -/
decl.etaExpand
let some value := info.value? (allowOpaque := true) | throwError "declaration `{declName}` does not have a value"
let (type, value) Meta.MetaM.run' do
let type toLCNFType info.type
let value Meta.lambdaTelescope value fun xs body => do Meta.mkLambdaFVars xs ( Meta.etaExpand body)
let value replaceUnsafeRecNames value
let value macroInline value
/- Recall that some declarations tagged with `macro_inline` contain matchers. -/
let value inlineMatchers value
/- Recall that `inlineMatchers` may have exposed `ite`s and `dite`s which are tagged as `[macro_inline]`. -/
let value macroInline value
/-
Remark: we have disabled the following transformatbion, we will perform it at phase 2, after code specialization.
It prevents many optimizations (e.g., "cases-of-ctor").
-/
-- let value ← applyCasesOnImplementedBy value
return (type, value)
let code toLCNF value
let decl if let .fun decl (.return _) := code then
eraseFunDecl decl (recursive := false)
pure { name := declName, params := decl.params, type, value := .code decl.value, levelParams := info.levelParams, safe, inlineAttr? : Decl }
else
pure { name := declName, params := #[], type, value := .code code, levelParams := info.levelParams, safe, inlineAttr? }
/- `toLCNF` may eta-reduce simple declarations. -/
decl.etaExpand
end Lean.Compiler.LCNF

View File

@@ -110,7 +110,4 @@ def Code.toExpr (code : Code) (xs : Array FVarId := #[]) : Expr :=
def FunDeclCore.toExpr (decl : FunDecl) (xs : Array FVarId := #[]) : Expr :=
run' decl.toExprM xs
def Decl.toExpr (decl : Decl) : Expr :=
run do withParams decl.params do mkLambdaM decl.params ( decl.value.toExprM)
end Lean.Compiler.LCNF

View File

@@ -143,7 +143,7 @@ where
go : ToMonoM Decl := do
let type toMonoType decl.type
let params decl.params.mapM (·.toMono)
let value decl.value.toMono
let value decl.value.mapCodeM (·.toMono)
let decl := { decl with type, params, value, levelParams := [] }
decl.saveMono
return decl

View File

@@ -44,3 +44,4 @@ import Lean.Elab.Tactic.DiscrTreeKey
import Lean.Elab.Tactic.BVDecide
import Lean.Elab.Tactic.BoolToPropSimps
import Lean.Elab.Tactic.Classical
import Lean.Elab.Tactic.Grind

View File

@@ -82,7 +82,7 @@ instance : ToExpr Gate where
| .and => mkConst ``Gate.and
| .xor => mkConst ``Gate.xor
| .beq => mkConst ``Gate.beq
| .imp => mkConst ``Gate.imp
| .or => mkConst ``Gate.or
toTypeExpr := mkConst ``Gate
instance : ToExpr BVPred where

View File

@@ -91,7 +91,7 @@ where
| .and => ``Std.Tactic.BVDecide.Reflect.Bool.and_congr
| .xor => ``Std.Tactic.BVDecide.Reflect.Bool.xor_congr
| .beq => ``Std.Tactic.BVDecide.Reflect.Bool.beq_congr
| .imp => ``Std.Tactic.BVDecide.Reflect.Bool.imp_congr
| .or => ``Std.Tactic.BVDecide.Reflect.Bool.or_congr
/--
Construct the reified version of `Bool.not subExpr`.
@@ -136,7 +136,7 @@ def mkIte (discr lhs rhs : ReifiedBVLogical) (discrExpr lhsExpr rhsExpr : Expr)
lhsEvalExpr lhsProof?
rhsEvalExpr rhsProof? | return none
return mkApp9
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.ite_congr)
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.cond_congr)
discrExpr lhsExpr rhsExpr
discrEvalExpr lhsEvalExpr rhsEvalExpr
discrProof lhsProof rhsProof

View File

@@ -22,67 +22,70 @@ This function adds the two lemmas:
- `discrExpr = false → atomExpr = rhsExpr`
It assumes that `discrExpr`, `lhsExpr` and `rhsExpr` are the expressions corresponding to `discr`,
`lhs` and `rhs`. Furthermore it assumes that `atomExpr` is of the form
`if discrExpr = true then lhsExpr else rhsExpr`.
`bif discrExpr then lhsExpr else rhsExpr`.
-/
def addIfLemmas (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
def addCondLemmas (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : LemmaM Unit := do
let some trueLemma mkIfTrueLemma discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
let some trueLemma mkCondTrueLemma discr atom lhs discrExpr atomExpr lhsExpr rhsExpr | return ()
LemmaM.addLemma trueLemma
let some falseLemma mkIfFalseLemma discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
let some falseLemma mkCondFalseLemma discr atom rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
LemmaM.addLemma falseLemma
where
mkIfTrueLemma (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) :=
mkIfLemma true discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr
mkIfFalseLemma (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) :=
mkIfLemma false discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr
mkIfLemma (discrVal : Bool) (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
mkCondTrueLemma (discr : ReifiedBVLogical) (atom lhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) := do
let resExpr := if discrVal then lhsExpr else rhsExpr
let resValExpr := if discrVal then lhs else rhs
let lemmaName :=
if discrVal then
``Std.Tactic.BVDecide.Reflect.BitVec.if_true
else
``Std.Tactic.BVDecide.Reflect.BitVec.if_false
let discrValExpr := toExpr discrVal
let discrVal ReifiedBVLogical.mkBoolConst discrVal
let resExpr := lhsExpr
let resValExpr := lhs
let lemmaName := ``Std.Tactic.BVDecide.Reflect.BitVec.cond_true
let eqDiscrExpr mkAppM ``BEq.beq #[discrExpr, discrValExpr]
let eqDiscr ReifiedBVLogical.mkGate discr discrVal discrExpr discrValExpr .beq
let notDiscrExpr := mkApp (mkConst ``Bool.not) discrExpr
let notDiscr ReifiedBVLogical.mkNot discr discrExpr
let eqBVExpr mkAppM ``BEq.beq #[atomExpr, resExpr]
let some eqBVPred ReifiedBVPred.mkBinPred atom resValExpr atomExpr resExpr .eq | return none
let eqBV ReifiedBVLogical.ofPred eqBVPred
let imp ReifiedBVLogical.mkGate eqDiscr eqBV eqDiscrExpr eqBVExpr .imp
let imp ReifiedBVLogical.mkGate notDiscr eqBV notDiscrExpr eqBVExpr .or
let proof := do
let evalExpr ReifiedBVLogical.mkEvalExpr imp.expr
let congrProof := ( imp.evalsAtAtoms).getD (ReifiedBVLogical.mkRefl evalExpr)
let lemmaProof := mkApp4 (mkConst lemmaName) (toExpr lhs.width) discrExpr lhsExpr rhsExpr
let trueExpr := mkConst ``Bool.true
let eqDiscrTrueExpr mkEq eqDiscrExpr trueExpr
let eqBVExprTrueExpr mkEq eqBVExpr trueExpr
let impExpr mkArrow eqDiscrTrueExpr eqBVExprTrueExpr
-- construct a `Decidable` instance for the implication using forall_prop_decidable
let decEqDiscrTrue := mkApp2 (mkConst ``instDecidableEqBool) eqDiscrExpr trueExpr
let decEqBVExprTrue := mkApp2 (mkConst ``instDecidableEqBool) eqBVExpr trueExpr
let impDecidable := mkApp4 (mkConst ``forall_prop_decidable)
eqDiscrTrueExpr
(.lam .anonymous eqDiscrTrueExpr eqBVExprTrueExpr .default)
decEqDiscrTrue
(.lam .anonymous eqDiscrTrueExpr decEqBVExprTrue .default)
let decideImpExpr := mkApp2 (mkConst ``Decidable.decide) impExpr impDecidable
-- !discr || (atom == rhs)
let impExpr := mkApp2 (mkConst ``Bool.or) notDiscrExpr eqBVExpr
return mkApp4
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.lemma_congr)
decideImpExpr
impExpr
evalExpr
congrProof
lemmaProof
return some imp.bvExpr, proof, imp.expr
mkCondFalseLemma (discr : ReifiedBVLogical) (atom rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) := do
let resExpr := rhsExpr
let resValExpr := rhs
let lemmaName := ``Std.Tactic.BVDecide.Reflect.BitVec.cond_false
let eqBVExpr mkAppM ``BEq.beq #[atomExpr, resExpr]
let some eqBVPred ReifiedBVPred.mkBinPred atom resValExpr atomExpr resExpr .eq | return none
let eqBV ReifiedBVLogical.ofPred eqBVPred
let imp ReifiedBVLogical.mkGate discr eqBV discrExpr eqBVExpr .or
let proof := do
let evalExpr ReifiedBVLogical.mkEvalExpr imp.expr
let congrProof := ( imp.evalsAtAtoms).getD (ReifiedBVLogical.mkRefl evalExpr)
let lemmaProof := mkApp4 (mkConst lemmaName) (toExpr rhs.width) discrExpr lhsExpr rhsExpr
-- discr || (atom == rhs)
let impExpr := mkApp2 (mkConst ``Bool.or) discrExpr eqBVExpr
return mkApp4
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.lemma_congr)
impExpr
evalExpr
congrProof
lemmaProof

View File

@@ -220,15 +220,12 @@ where
.rotateRight
``BVUnOp.rotateRight
``Std.Tactic.BVDecide.Reflect.BitVec.rotateRight_congr
| ite _ discrExpr _ lhsExpr rhsExpr =>
let_expr Eq α discrExpr val := discrExpr | return none
let_expr Bool := α | return none
let_expr Bool.true := val | return none
| cond _ discrExpr lhsExpr rhsExpr =>
let some atom ReifiedBVExpr.bitVecAtom x true | return none
let some discr ReifiedBVLogical.of discrExpr | return none
let some lhs goOrAtom lhsExpr | return none
let some rhs goOrAtom rhsExpr | return none
addIfLemmas discr atom lhs rhs discrExpr x lhsExpr rhsExpr
addCondLemmas discr atom lhs rhs discrExpr x lhsExpr rhsExpr
return some atom
| _ => return none
@@ -392,10 +389,7 @@ where
| Bool => gateReflection lhsExpr rhsExpr .beq
| BitVec _ => goPred t
| _ => return none
| ite _ discrExpr _ lhsExpr rhsExpr =>
let_expr Eq α discrExpr val := discrExpr | return none
let_expr Bool := α | return none
let_expr Bool.true := val | return none
| cond _ discrExpr lhsExpr rhsExpr =>
let some discr goOrAtom discrExpr | return none
let some lhs goOrAtom lhsExpr | return none
let some rhs goOrAtom rhsExpr | return none

View File

@@ -28,6 +28,18 @@ namespace Frontend.Normalize
open Lean.Meta
open Std.Tactic.BVDecide.Normalize
builtin_simproc [bv_normalize] reduceCond (cond _ _ _) := fun e => do
let_expr f@cond α c tb eb := e | return .continue
let r Simp.simp c
if r.expr.cleanupAnnotations.isConstOf ``Bool.true then
let pr := mkApp (mkApp4 (mkConst ``Bool.cond_pos f.constLevels!) α c tb eb) ( r.getProof)
return .visit { expr := tb, proof? := pr }
else if r.expr.cleanupAnnotations.isConstOf ``Bool.false then
let pr := mkApp (mkApp4 (mkConst ``Bool.cond_neg f.constLevels!) α c tb eb) ( r.getProof)
return .visit { expr := eb, proof? := pr }
else
return .continue
builtin_simproc [bv_normalize] eqToBEq (((_ : Bool) = (_ : Bool))) := fun e => do
let_expr Eq _ lhs rhs := e | return .continue
match_expr rhs with
@@ -127,8 +139,6 @@ builtin_simproc [bv_normalize] bv_add_const' (((_ : BitVec _) + (_ : BitVec _))
else
return .continue
attribute [builtin_bv_normalize_proc] reduceIte
/-- Return a number `k` such that `2^k = n`. -/
private def Nat.log2Exact (n : Nat) : Option Nat := do
guard <| n 0

View File

@@ -0,0 +1,27 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Tactics
import Lean.Meta.Tactic.Grind
import Lean.Elab.Tactic.Basic
namespace Lean.Elab.Tactic
open Meta
def grind (mvarId : MVarId) (mainDeclName : Name) : MetaM Unit := do
let mvarIds Grind.main mvarId mainDeclName
unless mvarIds.isEmpty do
throwError "`grind` failed\n{goalsToMessageData mvarIds}"
@[builtin_tactic Lean.Parser.Tactic.grind] def evalApplyRfl : Tactic := fun stx => do
match stx with
| `(tactic| grind) =>
logWarningAt stx "The `grind` tactic is experimental and still under development. Avoid using it in production projects"
let declName := ( Term.getDeclName?).getD `_grind
withMainContext do liftMetaFinishingTactic (grind · declName)
| _ => throwUnsupportedSyntax
end Lean.Elab.Tactic

View File

@@ -579,12 +579,25 @@ private def checkAltsOfOptInductionAlts (optInductionAlts : Syntax) : TacticM Un
throwErrorAt alt "more than one wildcard alternative '| _ => ...' used"
found := true
def getInductiveValFromMajor (major : Expr) : TacticM InductiveVal :=
def getInductiveValFromMajor (induction : Bool) (major : Expr) : TacticM InductiveVal :=
liftMetaMAtMain fun mvarId => do
let majorType inferType major
let majorType whnf majorType
matchConstInduct majorType.getAppFn
(fun _ => Meta.throwTacticEx `induction mvarId m!"major premise type is not an inductive type {indentExpr majorType}")
(fun _ => do
let tacticName := if induction then `induction else `cases
let mut hint := m!"\n\nExplanation: the '{tacticName}' tactic is for constructor-based reasoning \
as well as for applying custom {tacticName} principles with a 'using' clause or a registered '@[{tacticName}_eliminator]' theorem. \
The above type neither is an inductive type nor has a registered theorem."
if majorType.isProp then
hint := m!"{hint}\n\n\
Consider using the 'by_cases' tactic, which does true/false reasoning for propositions."
else if majorType.isType then
hint := m!"{hint}\n\n\
Type universes are not inductive types, and type-constructor-based reasoning is not possible. \
This is a strong limitation. According to Lean's underlying theory, the only provable distinguishing \
feature of types is their cardinalities."
Meta.throwTacticEx tacticName mvarId m!"major premise type is not an inductive type{indentExpr majorType}{hint}")
(fun val _ => pure val)
/--
@@ -627,7 +640,7 @@ private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (inducti
return getElimInfo elimName
unless targets.size == 1 do
throwError "eliminator must be provided when multiple targets are used (use 'using <eliminator-name>'), and no default eliminator has been registered using attribute `[eliminator]`"
let indVal getInductiveValFromMajor targets[0]!
let indVal getInductiveValFromMajor induction targets[0]!
if induction && indVal.all.length != 1 then
throwError "'induction' tactic does not support mutually inductive types, the eliminator '{mkRecName indVal.name}' has multiple motives"
if induction && indVal.isNested then

View File

@@ -769,6 +769,11 @@ opaque quickLt (a : @& Expr) (b : @& Expr) : Bool
@[extern "lean_expr_lt"]
opaque lt (a : @& Expr) (b : @& Expr) : Bool
def quickComp (a b : Expr) : Ordering :=
if quickLt a b then .lt
else if quickLt b a then .gt
else .eq
/--
Return true iff `a` and `b` are alpha equivalent.
Binder annotations are ignored.
@@ -1643,6 +1648,23 @@ def isFalse (e : Expr) : Bool :=
def isTrue (e : Expr) : Bool :=
e.cleanupAnnotations.isConstOf ``True
/--
`getForallArity type` returns the arity of a `forall`-type. This function consumes nested annotations,
and performs pending beta reductions. It does **not** use whnf.
Examples:
- If `a` is `Nat`, `getForallArity a` returns `0`
- If `a` is `Nat → Bool`, `getForallArity a` returns `1`
-/
partial def getForallArity : Expr Nat
| .mdata _ b => getForallArity b
| .forallE _ _ b _ => getForallArity b + 1
| e =>
if e.isHeadBetaTarget then
getForallArity e.headBeta
else
let e' := e.cleanupAnnotations
if e != e' then getForallArity e' else 0
/--
Checks if an expression is a "natural number numeral in normal form",
i.e. of type `Nat`, and explicitly of the form `OfNat.ofNat n`

View File

@@ -176,6 +176,14 @@ def mkEqOfHEq (h : Expr) : MetaM Expr := do
| _ =>
throwAppBuilderException ``eq_of_heq m!"heterogeneous equality proof expected{indentExpr h}"
/-- Given `h : Eq a b`, returns a proof of `HEq a b`. -/
def mkHEqOfEq (h : Expr) : MetaM Expr := do
let hType infer h
let some (α, a, b) := hType.eq?
| throwAppBuilderException ``heq_of_eq m!"equality proof expected{indentExpr h}"
let u getLevel α
return mkApp4 (mkConst ``heq_of_eq [u]) α a b h
/--
If `e` is `@Eq.refl α a`, return `a`.
-/

View File

@@ -605,6 +605,9 @@ variable [MonadControlT MetaM n] [Monad n]
@[inline] def modifyCache (f : Cache Cache) : MetaM Unit :=
modify fun { mctx, cache, zetaDeltaFVarIds, postponed, diag } => { mctx, cache := f cache, zetaDeltaFVarIds, postponed, diag }
def resetCache : MetaM Unit :=
modifyCache fun _ => {}
@[inline] def modifyInferTypeCache (f : InferTypeCache InferTypeCache) : MetaM Unit :=
modifyCache fun ic, c1, c2, c3, c4, c5 => f ic, c1, c2, c3, c4, c5
@@ -1777,9 +1780,9 @@ private def withMVarContextImp (mvarId : MVarId) (x : MetaM α) : MetaM α := do
withLocalContextImp mvarDecl.lctx mvarDecl.localInstances x
/--
Execute `x` using the given metavariable `LocalContext` and `LocalInstances`.
The type class resolution cache is flushed when executing `x` if its `LocalInstances` are
different from the current ones. -/
Executes `x` using the given metavariable `LocalContext` and `LocalInstances`.
The type class resolution cache is flushed when executing `x` if its `LocalInstances` are
different from the current ones. -/
def _root_.Lean.MVarId.withContext (mvarId : MVarId) : n α n α :=
mapMetaM <| withMVarContextImp mvarId
@@ -1789,13 +1792,25 @@ private def withMCtxImp (mctx : MetavarContext) (x : MetaM α) : MetaM α := do
try x finally setMCtx mctx'
/--
`withMCtx mctx k` replaces the metavariable context and then executes `k`.
The metavariable context is restored after executing `k`.
`withMCtx mctx k` replaces the metavariable context and then executes `k`.
The metavariable context is restored after executing `k`.
This method is used to implement the type class resolution procedure. -/
This method is used to implement the type class resolution procedure. -/
def withMCtx (mctx : MetavarContext) : n α n α :=
mapMetaM <| withMCtxImp mctx
/--
`withoutModifyingMCtx k` executes `k` and then restores the metavariable context.
-/
def withoutModifyingMCtx : n α n α :=
mapMetaM fun x => do
let mctx getMCtx
try
x
finally
resetCache
setMCtx mctx
@[inline] private def approxDefEqImp (x : MetaM α) : MetaM α :=
withConfig (fun config => { config with foApprox := true, ctxApprox := true, quasiPatternApprox := true}) x

View File

@@ -167,7 +167,7 @@ def isDefEqStringLit (s t : Expr) : MetaM LBool := do
Remark: `n` may be 0. -/
def isEtaUnassignedMVar (e : Expr) : MetaM Bool := do
match e.etaExpanded? with
| some (Expr.mvar mvarId) =>
| some (.mvar mvarId) =>
if ( mvarId.isReadOnlyOrSyntheticOpaque) then
pure false
else if ( mvarId.isAssigned) then
@@ -361,9 +361,9 @@ private partial def isDefEqBindingAux (lctx : LocalContext) (fvars : Array Expr)
let fvars := fvars.push (mkFVar fvarId)
isDefEqBindingAux lctx fvars b₁ b₂ (ds₂.push d₂)
match e₁, e₂ with
| Expr.forallE n d₁ b₁ _, Expr.forallE _ d₂ b₂ _ => process n d₁ d₂ b₁ b₂
| Expr.lam n d₁ b₁ _, Expr.lam _ d₂ b₂ _ => process n d₁ d₂ b₁ b₂
| _, _ =>
| .forallE n d₁ b₁ _, .forallE _ d₂ b₂ _ => process n d₁ d₂ b₁ b₂
| .lam n d₁ b₁ _, .lam _ d₂ b₂ _ => process n d₁ d₂ b₁ b₂
| _, _ =>
withLCtx' lctx do
isDefEqBindingDomain fvars ds₂ do
Meta.isExprDefEqAux (e₁.instantiateRev fvars) (e₂.instantiateRev fvars)
@@ -1037,13 +1037,13 @@ def checkedAssignImpl (mvarId : MVarId) (val : Expr) : MetaM Bool := do
private def processAssignmentFOApproxAux (mvar : Expr) (args : Array Expr) (v : Expr) : MetaM Bool :=
match v with
| .mdata _ e => processAssignmentFOApproxAux mvar args e
| Expr.app f a =>
| .mdata _ e => processAssignmentFOApproxAux mvar args e
| .app f a =>
if args.isEmpty then
pure false
else
Meta.isExprDefEqAux args.back! a <&&> Meta.isExprDefEqAux (mkAppRange mvar 0 (args.size - 1) args) f
| _ => pure false
| _ => pure false
/--
Auxiliary method for applying first-order unification. It is an approximation.
@@ -1177,7 +1177,7 @@ private partial def processAssignment (mvarApp : Expr) (v : Expr) : MetaM Bool :
let arg simpAssignmentArg arg
let args := args.set i arg
match arg with
| Expr.fvar fvarId =>
| .fvar fvarId =>
if args[0:i].any fun prevArg => prevArg == arg then
useFOApprox args
else if mvarDecl.lctx.contains fvarId && !cfg.quasiPatternApprox then
@@ -1233,7 +1233,7 @@ private def processAssignment' (mvarApp : Expr) (v : Expr) : MetaM Bool := do
private def isDeltaCandidate? (t : Expr) : MetaM (Option ConstantInfo) := do
match t.getAppFn with
| Expr.const c _ =>
| .const c _ =>
match ( getUnfoldableConst? c) with
| r@(some info) => if info.hasValue then return r else return none
| _ => return none
@@ -1375,8 +1375,8 @@ private def unfoldBothDefEq (fn : Name) (t s : Expr) : MetaM LBool := do
private def sameHeadSymbol (t s : Expr) : Bool :=
match t.getAppFn, s.getAppFn with
| Expr.const c₁ _, Expr.const c₂ _ => c₁ == c₂
| _, _ => false
| .const c₁ _, .const c₂ _ => c₁ == c₂
| _, _ => false
/--
- If headSymbol (unfold t) == headSymbol s, then unfold t
@@ -1521,8 +1521,8 @@ private def isDefEqDelta (t s : Expr) : MetaM LBool := do
unfoldNonProjFnDefEq tInfo sInfo t s
private def isAssigned : Expr MetaM Bool
| Expr.mvar mvarId => mvarId.isAssigned
| _ => pure false
| .mvar mvarId => mvarId.isAssigned
| _ => pure false
private def expandDelayedAssigned? (t : Expr) : MetaM (Option Expr) := do
let tFn := t.getAppFn
@@ -1647,8 +1647,8 @@ private partial def isDefEqQuick (t s : Expr) : MetaM LBool :=
| .sort u, .sort v => toLBoolM <| isLevelDefEqAux u v
| .lam .., .lam .. => if t == s then pure LBool.true else toLBoolM <| isDefEqBinding t s
| .forallE .., .forallE .. => if t == s then pure LBool.true else toLBoolM <| isDefEqBinding t s
-- | Expr.mdata _ t _, s => isDefEqQuick t s
-- | t, Expr.mdata _ s _ => isDefEqQuick t s
-- | .mdata _ t _, s => isDefEqQuick t s
-- | t, .mdata _ s _ => isDefEqQuick t s
| .fvar fvarId₁, .fvar fvarId₂ => do
if fvarId₁ == fvarId₂ then
return .true

View File

@@ -12,6 +12,15 @@ import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Tactic.Grind.Cases
import Lean.Meta.Tactic.Grind.Injection
import Lean.Meta.Tactic.Grind.Core
import Lean.Meta.Tactic.Grind.Canon
import Lean.Meta.Tactic.Grind.MarkNestedProofs
import Lean.Meta.Tactic.Grind.Inv
import Lean.Meta.Tactic.Grind.Proof
import Lean.Meta.Tactic.Grind.Propagate
import Lean.Meta.Tactic.Grind.PP
import Lean.Meta.Tactic.Grind.Simp
import Lean.Meta.Tactic.Grind.Ctor
import Lean.Meta.Tactic.Grind.Parser
namespace Lean
@@ -21,5 +30,10 @@ builtin_initialize registerTraceClass `grind.issues
builtin_initialize registerTraceClass `grind.add
builtin_initialize registerTraceClass `grind.pre
builtin_initialize registerTraceClass `grind.debug
builtin_initialize registerTraceClass `grind.debug.proofs
builtin_initialize registerTraceClass `grind.simp
builtin_initialize registerTraceClass `grind.congr
builtin_initialize registerTraceClass `grind.proof
builtin_initialize registerTraceClass `grind.proof.detail
end Lean

View File

@@ -0,0 +1,157 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Util
import Lean.Meta.Basic
import Lean.Meta.FunInfo
import Lean.Util.FVarSubset
import Lean.Util.PtrSet
import Lean.Util.FVarSubset
namespace Lean.Meta.Grind
namespace Canon
/-!
A canonicalizer module for the `grind` tactic. The canonicalizer defined in `Meta/Canonicalizer.lean` is
not suitable for the `grind` tactic. It was designed for tactics such as `omega`, where the goal is
to detect when two structurally different atoms are definitionally equal.
The `grind` tactic, on the other hand, uses congruence closure. Moreover, types, type formers, proofs, and instances
are considered supporting elements and are not factored into congruence detection.
This module minimizes the number of `isDefEq` checks by comparing two terms `a` and `b` only if they instances,
types, or type formers and are the `i`-th arguments of two different `f`-applications. This approach is
sufficient for the congruence closure procedure used by the `grind` tactic.
To further optimize `isDefEq` checks, instances are compared using `TransparencyMode.instances`, which reduces
the number of constants that need to be unfolded. If diagnostics are enabled, instances are compared using
the default transparency mode too for sanity checking, and discrepancies are reported.
Types and type formers are always checked using default transparency.
Remark:
The canonicalizer minimizes issues with non-canonical instances and structurally different but definitionally equal types,
but it does not solve all problems. For example, consider a situation where we have `(a : BitVec n)`
and `(b : BitVec m)`, along with instances `inst1 n : Add (BitVec n)` and `inst2 m : Add (BitVec m)` where `inst1`
and `inst2` are structurally different. Now consider the terms `a + a` and `b + b`. After canonicalization, the two
additions will still use structurally different (and definitionally different) instances: `inst1 n` and `inst2 m`.
Furthermore, `grind` will not be able to infer that `HEq (a + a) (b + b)` even if we add the assumptions `n = m` and `HEq a b`.
-/
structure State where
argMap : PHashMap (Expr × Nat) (List Expr) := {}
canon : PHashMap Expr Expr := {}
deriving Inhabited
/--
Helper function for canonicalizing `e` occurring as the `i`th argument of an `f`-application.
`isInst` is true if `e` is an type class instance.
Recall that we use `TransparencyMode.instances` for checking whether two instances are definitionally equal or not.
Thus, if diagnostics are enabled, we also check them using `TransparencyMode.default`. If the result is different
we report to the user.
-/
def canonElemCore (f : Expr) (i : Nat) (e : Expr) (isInst : Bool) : StateT State MetaM Expr := do
let s get
if let some c := s.canon.find? e then
return c
let key := (f, i)
let cs := s.argMap.find? key |>.getD []
for c in cs do
if ( isDefEq e c) then
if c.fvarsSubset e then
-- It is not in general safe to replace `e` with `c` if `c` has more free variables than `e`.
modify fun s => { s with canon := s.canon.insert e c }
return c
if isInst then
if ( isDiagnosticsEnabled <&&> pure (c.fvarsSubset e) <&&> (withDefault <| isDefEq e c)) then
-- TODO: consider storing this information in some structure that can be browsed later.
trace[grind.issues] "the following `grind` static elements are definitionally equal with `default` transparency, but not with `instances` transparency{indentExpr e}\nand{indentExpr c}"
modify fun s => { s with canon := s.canon.insert e e, argMap := s.argMap.insert key (e::cs) }
return e
abbrev canonType (f : Expr) (i : Nat) (e : Expr) := withDefault <| canonElemCore f i e false
abbrev canonInst (f : Expr) (i : Nat) (e : Expr) := withReducibleAndInstances <| canonElemCore f i e true
/--
Return type for the `shouldCanon` function.
-/
private inductive ShouldCanonResult where
| /- Nested types (and type formers) are canonicalized. -/
canonType
| /- Nested instances are canonicalized. -/
canonInst
| /-
Term is not a proof, type (former), nor an instance.
Thus, it must be recursively visited by the canonizer.
-/
visit
deriving Inhabited
/--
See comments at `ShouldCanonResult`.
-/
def shouldCanon (pinfos : Array ParamInfo) (i : Nat) (arg : Expr) : MetaM ShouldCanonResult := do
if h : i < pinfos.size then
let pinfo := pinfos[i]
if pinfo.isInstImplicit then
return .canonInst
else if pinfo.isProp then
return .visit
if ( isTypeFormer arg) then
return .canonType
else
return .visit
unsafe def canonImpl (e : Expr) : StateT State MetaM Expr := do
visit e |>.run' mkPtrMap
where
visit (e : Expr) : StateRefT (PtrMap Expr Expr) (StateT State MetaM) Expr := do
match e with
| .bvar .. => unreachable!
-- Recall that `grind` treats `let`, `forall`, and `lambda` as atomic terms.
| .letE .. | .forallE .. | .lam ..
| .const .. | .lit .. | .mvar .. | .sort .. | .fvar ..
-- Recall that the `grind` preprocessor uses the `foldProjs` preprocessing step.
| .proj ..
-- Recall that the `grind` preprocessor uses the `eraseIrrelevantMData` preprocessing step.
| .mdata .. => return e
-- We only visit applications
| .app .. =>
-- Check whether it is cached
if let some r := ( get).find? e then
return r
e.withApp fun f args => do
let e' if f.isConstOf ``Lean.Grind.nestedProof && args.size == 2 then
-- We just canonize the proposition
let prop := args[0]!
let prop' visit prop
pure <| if ptrEq prop prop' then mkAppN f (args.set! 0 prop') else e
else
let pinfos := ( getFunInfo f).paramInfo
let mut modified := false
let mut args := args
for i in [:args.size] do
let arg := args[i]!
let arg' match ( shouldCanon pinfos i arg) with
| .canonType => canonType f i arg
| .canonInst => canonInst f i arg
| .visit => visit arg
unless ptrEq arg arg' do
args := args.set! i arg'
modified := true
pure <| if modified then mkAppN f args else e
modify fun s => s.insert e e'
return e'
/--
Canonicalizes nested types, type formers, and instances in `e`.
-/
def canon (e : Expr) : StateT State MetaM Expr :=
unsafe canonImpl e
end Canon
end Lean.Meta.Grind

View File

@@ -4,144 +4,15 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
import Init.Grind.Util
import Lean.Meta.LitValues
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Inv
import Lean.Meta.Tactic.Grind.PP
import Lean.Meta.Tactic.Grind.Ctor
import Lean.Meta.Tactic.Grind.Internalize
namespace Lean.Meta.Grind
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeRef (e : Expr) : GoalM Format := do
let some n getENode? e | return "_"
return f!"#{n.idx}"
/-- Returns expressions in the given expression equivalence class. -/
partial def getEqc (e : Expr) : GoalM (List Expr) :=
go e e []
where
go (first : Expr) (e : Expr) (acc : List Expr) : GoalM (List Expr) := do
let next getNext e
let acc := e :: acc
if isSameExpr first next then
return acc
else
go first next acc
/-- Returns all equivalence classes in the current goal. -/
partial def getEqcs : GoalM (List (List Expr)) := do
let mut r := []
for (_, node) in ( get).enodes do
if isSameExpr node.root node.self then
r := ( getEqc node.self) :: r
return r
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeDeclValue (e : Expr) : GoalM Format := do
if e.isApp && !( isLitValue e) then
e.withApp fun f args => do
let r if f.isConst then
ppExpr f
else
ppENodeRef f
let mut r := r
for arg in args do
r := r ++ " " ++ ( ppENodeRef arg)
return r
else
ppExpr e
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeDecl (e : Expr) : GoalM Format := do
let mut r := f!"{← ppENodeRef e} := {← ppENodeDeclValue e}"
let n getENode e
unless isSameExpr e n.root do
r := r ++ f!" ↦ {← ppENodeRef n.root}"
if n.interpreted then
r := r ++ ", [val]"
if n.ctor then
r := r ++ ", [ctor]"
return r
/-- Pretty print goal state for debugging purposes. -/
def ppState : GoalM Format := do
let mut r := f!"Goal:"
let nodes := ( get).enodes.toArray.map (·.2)
let nodes := nodes.qsort fun a b => a.idx < b.idx
for node in nodes do
r := r ++ "\n" ++ ( ppENodeDecl node.self)
let eqcs getEqcs
for eqc in eqcs do
if eqc.length > 1 then
r := r ++ "\n" ++ "{" ++ (Format.joinSep ( eqc.mapM ppENodeRef) ", ") ++ "}"
return r
/--
Returns `true` if `e` is `True`, `False`, or a literal value.
See `LitValues` for supported literals.
-/
def isInterpreted (e : Expr) : MetaM Bool := do
if e.isTrue || e.isFalse then return true
isLitValue e
/--
Creates an `ENode` for `e` if one does not already exist.
This method assumes `e` has been hashconsed.
-/
def mkENode (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
let ctor := ( isConstructorAppCore? e).isSome
let interpreted isInterpreted e
mkENodeCore e interpreted ctor generation
private def pushNewEqCore (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit :=
modify fun s => { s with newEqs := s.newEqs.push { lhs, rhs, proof, isHEq } }
@[inline] private def pushNewEq (lhs rhs proof : Expr) : GoalM Unit :=
pushNewEqCore lhs rhs proof (isHEq := false)
@[inline] private def pushNewHEq (lhs rhs proof : Expr) : GoalM Unit :=
pushNewEqCore lhs rhs proof (isHEq := true)
/--
Adds `e` to congruence table.
-/
def addCongrTable (_e : Expr) : GoalM Unit := do
-- TODO
return ()
partial def internalize (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
match e with
| .bvar .. => unreachable!
| .sort .. => return ()
| .fvar .. | .letE .. | .lam .. | .forallE .. =>
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .lit .. | .const .. =>
mkENode e generation
| .mvar ..
| .mdata ..
| .proj .. =>
trace[grind.issues] "unexpected term during internalization{indentExpr e}"
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .app .. =>
if ( isLitValue e) then
-- We do not want to internalize the components of a literal value.
mkENode e generation
else e.withApp fun f args => do
unless f.isConst do
internalize f generation
let info getFunInfo f
let shouldInternalize (i : Nat) : GoalM Bool := do
if h : i < info.paramInfo.size then
let pinfo := info.paramInfo[i]
if pinfo.binderInfo.isInstImplicit || pinfo.isProp then
return false
return true
for h : i in [: args.size] do
let arg := args[i]
if ( shouldInternalize i) then
unless ( isTypeFormer arg) do
internalize arg generation
mkENode e generation
addCongrTable e
/--
The fields `target?` and `proof?` in `e`'s `ENode` are encoding a transitivity proof
@@ -163,11 +34,40 @@ where
proof? := proofNew?
}
private def markAsInconsistent : GoalM Unit :=
modify fun s => { s with inconsistent := true }
/--
Remove `root` parents from the congruence table.
This is an auxiliary function performed while merging equivalence classes.
-/
private def removeParents (root : Expr) : GoalM ParentSet := do
let parents getParentsAndReset root
for parent in parents do
modify fun s => { s with congrTable := s.congrTable.erase { e := parent } }
return parents
def isInconsistent : GoalM Bool :=
return ( get).inconsistent
/--
Reinsert parents into the congruence table and detect new equalities.
This is an auxiliary function performed while merging equivalence classes.
-/
private def reinsertParents (parents : ParentSet) : GoalM Unit := do
for parent in parents do
addCongrTable parent
/-- Closes the goal when `True` and `False` are in the same equivalence class. -/
private def closeGoalWithTrueEqFalse : GoalM Unit := do
let mvarId := ( get).mvarId
unless ( mvarId.isAssigned) do
let trueEqFalse mkEqFalseProof ( getTrueExpr)
let falseProof mkEqMP trueEqFalse (mkConst ``True.intro)
closeGoal falseProof
/-- Closes the goal when `lhs` and `rhs` are both literal values and belong to the same equivalence class. -/
private def closeGoalWithValuesEq (lhs rhs : Expr) : GoalM Unit := do
let p mkEq lhs rhs
let hp mkEqProof lhs rhs
let d mkDecide p
let pEqFalse := mkApp3 (mkConst ``eq_false_of_decide) p d.appArg! (mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Bool) (mkConst ``false))
let falseProof mkEqMP pEqFalse hp
closeGoal falseProof
private partial def addEqStep (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit := do
trace[grind.eq] "{lhs} {if isHEq then "" else "="} {rhs}"
@@ -179,23 +79,33 @@ private partial def addEqStep (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit
return ()
let lhsRoot getENode lhsNode.root
let rhsRoot getENode rhsNode.root
let mut valueInconsistency := false
let mut trueEqFalse := false
if lhsRoot.interpreted && rhsRoot.interpreted then
if lhsNode.root.isTrue || rhsNode.root.isTrue then
markAsInconsistent
trueEqFalse := true
else
valueInconsistency := true
if (lhsRoot.interpreted && !rhsRoot.interpreted)
|| (lhsRoot.ctor && !rhsRoot.ctor)
|| (lhsRoot.size > rhsRoot.size && !rhsRoot.interpreted && !rhsRoot.ctor) then
go rhs lhs rhsNode lhsNode rhsRoot lhsRoot true
else
go lhs rhs lhsNode rhsNode lhsRoot rhsRoot false
if trueEqFalse then
closeGoalWithTrueEqFalse
unless ( isInconsistent) do
if lhsRoot.ctor && rhsRoot.ctor then
propagateCtor lhsRoot.self rhsRoot.self
unless ( isInconsistent) do
if valueInconsistency then
closeGoalWithValuesEq lhsRoot.self rhsRoot.self
trace[grind.debug] "after addEqStep, {← ppState}"
checkInvariants
where
go (lhs rhs : Expr) (lhsNode rhsNode lhsRoot rhsRoot : ENode) (flipped : Bool) : GoalM Unit := do
trace[grind.debug] "adding {← ppENodeRef lhs} ↦ {← ppENodeRef rhs}"
let mut valueInconsistency := false
if lhsRoot.interpreted && rhsRoot.interpreted then
if lhsNode.root.isTrue || rhsNode.root.isTrue then
markAsInconsistent
else
valueInconsistency := true
-- TODO: process valueInconsistency := true
/-
We have the following `target?/proof?`
`lhs -> ... -> lhsNode.root`
@@ -210,14 +120,12 @@ where
proof? := proof
flipped
}
-- TODO: Remove parents from congruence table
-- TODO: set propagateBool
updateRoots lhs rhsNode.root true -- TODO
let parents removeParents lhsRoot.self
updateRoots lhs rhsNode.root
trace[grind.debug] "{← ppENodeRef lhs} new root {← ppENodeRef rhsNode.root}, {← ppENodeRef (← getRoot lhs)}"
-- TODO: Reinsert parents into congruence table
setENode lhsNode.root { lhsRoot with
reinsertParents parents
setENode lhsNode.root { ( getENode lhsRoot.self) with -- We must retrieve `lhsRoot` since it was updated.
next := rhsRoot.next
root := rhsNode.root
}
setENode rhsNode.root { rhsRoot with
next := lhsRoot.next
@@ -225,22 +133,33 @@ where
hasLambdas := rhsRoot.hasLambdas || lhsRoot.hasLambdas
heqProofs := isHEq || rhsRoot.heqProofs || lhsRoot.heqProofs
}
-- TODO: copy parentst from lhsRoot parents to rhsRoot parents
copyParentsTo parents rhsNode.root
unless ( isInconsistent) do
for parent in parents do
propagateUp parent
updateRoots (lhs : Expr) (rootNew : Expr) (_propagateBool : Bool) : GoalM Unit := do
updateRoots (lhs : Expr) (rootNew : Expr) : GoalM Unit := do
let rec loop (e : Expr) : GoalM Unit := do
-- TODO: propagateBool
let n getENode e
setENode e { n with root := rootNew }
unless ( isInconsistent) do
propagateDown e
if isSameExpr lhs n.next then return ()
loop n.next
loop lhs
/-- Ensures collection of equations to be processed is empty. -/
def resetNewEqs : GoalM Unit :=
private def resetNewEqs : GoalM Unit :=
modify fun s => { s with newEqs := #[] }
partial def addEqCore (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit := do
/-- Pops and returns the next equality to be processed. -/
private def popNextEq? : GoalM (Option NewEq) := do
let r := ( get).newEqs.back?
if r.isSome then
modify fun s => { s with newEqs := s.newEqs.pop }
return r
private partial def addEqCore (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit := do
addEqStep lhs rhs proof isHEq
processTodo
where
@@ -248,7 +167,7 @@ where
if ( isInconsistent) then
resetNewEqs
return ()
let some { lhs, rhs, proof, isHEq } := ( get).newEqs.back? | return ()
let some { lhs, rhs, proof, isHEq } := ( popNextEq?) | return ()
addEqStep lhs rhs proof isHEq
processTodo
@@ -273,7 +192,7 @@ where
trace[grind.add] "isNeg: {isNeg}, {p}"
match_expr p with
| Eq _ lhs rhs => goEq p lhs rhs isNeg false
| HEq _ _ lhs rhs => goEq p lhs rhs isNeg true
| HEq _ lhs _ rhs => goEq p lhs rhs isNeg true
| _ =>
internalize p generation
if isNeg then

View File

@@ -0,0 +1,49 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
private partial def propagateInjEqs (eqs : Expr) (proof : Expr) : GoalM Unit := do
match_expr eqs with
| And left right =>
propagateInjEqs left (.proj ``And 0 proof)
propagateInjEqs right (.proj ``And 1 proof)
| Eq _ lhs rhs => pushEq lhs rhs proof
| HEq _ lhs _ rhs => pushHEq lhs rhs proof
| _ =>
trace[grind.issues] "unexpected injectivity theorem result type{indentExpr eqs}"
return ()
/--
Given constructors `a` and `b`, propagate equalities if they are the same,
and close goal if they are different.
-/
def propagateCtor (a b : Expr) : GoalM Unit := do
let aType whnfD ( inferType a)
let bType whnfD ( inferType b)
unless ( withDefault <| isDefEq aType bType) do
return ()
let ctor₁ := a.getAppFn
let ctor₂ := b.getAppFn
if ctor₁ == ctor₂ then
let .const ctorName _ := a.getAppFn | return ()
let injDeclName := Name.mkStr ctorName "inj"
unless ( getEnv).contains injDeclName do return ()
let info getConstInfo injDeclName
let n := info.type.getForallArity
let mask : Array (Option Expr) := mkArray n none
let mask := mask.set! (n-1) (some ( mkEqProof a b))
let injLemma mkAppOptM injDeclName mask
propagateInjEqs ( inferType injLemma) injLemma
else
let .const declName _ := aType.getAppFn | return ()
let noConfusionDeclName := Name.mkStr declName "noConfusion"
unless ( getEnv).contains noConfusionDeclName do return ()
closeGoal ( mkNoConfusion ( getFalseExpr) ( mkEqProof a b))
end Lean.Meta.Grind

View File

@@ -0,0 +1,68 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Util
import Lean.Meta.LitValues
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
/-- Adds `e` to congruence table. -/
def addCongrTable (e : Expr) : GoalM Unit := do
if let some { e := e' } := ( get).congrTable.find? { e } then
-- `f` and `g` must have the same type.
-- See paper: Congruence Closure in Intensional Type Theory
let f := e.getAppFn
let g := e'.getAppFn
unless isSameExpr f g do
unless ( hasSameType f g) do
trace[grind.issues] "found congruence between{indentExpr e}\nand{indentExpr e'}\nbut functions have different types"
return ()
trace[grind.congr] "{e} = {e'}"
pushEqHEq e e' congrPlaceholderProof
let node getENode e
setENode e { node with cgRoot := e' }
else
modify fun s => { s with congrTable := s.congrTable.insert { e } }
partial def internalize (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
match e with
| .bvar .. => unreachable!
| .sort .. => return ()
| .fvar .. | .letE .. | .lam .. | .forallE .. =>
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .lit .. | .const .. =>
mkENode e generation
| .mvar ..
| .mdata ..
| .proj .. =>
trace[grind.issues] "unexpected term during internalization{indentExpr e}"
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .app .. =>
if ( isLitValue e) then
-- We do not want to internalize the components of a literal value.
mkENode e generation
else e.withApp fun f args => do
if f.isConstOf ``Lean.Grind.nestedProof && args.size == 2 then
-- We only internalize the proposition. We can skip the proof because of
-- proof irrelevance
let c := args[0]!
internalize c generation
registerParent e c
else
unless f.isConst do
internalize f generation
registerParent e f
for h : i in [: args.size] do
let arg := args[i]
internalize arg generation
registerParent e arg
mkENode e generation
addCongrTable e
propagateUp e
end Lean.Meta.Grind

View File

@@ -0,0 +1,102 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Proof
namespace Lean.Meta.Grind
/-!
Debugging support code for checking basic invariants.
-/
private def checkEqc (root : ENode) : GoalM Unit := do
let mut size := 0
let mut curr := root.self
repeat
size := size + 1
-- The root of `curr` must be `root`
assert! isSameExpr ( getRoot curr) root.self
-- Check congruence root
if curr.isApp then
if let some { e } := ( get).congrTable.find? { e := curr } then
if ( hasSameType e.getAppFn curr.getAppFn) then
assert! isSameExpr e ( getENode curr).cgRoot
else
assert! isSameExpr curr ( getENode curr).cgRoot
-- If the equivalence class does not have HEq proofs, then the types must be definitionally equal.
unless root.heqProofs do
assert! ( hasSameType curr root.self)
-- Starting at `curr`, following the `target?` field leads to `root`.
let mut n := curr
repeat
if let some target getTarget? n then
n := target
else
break
assert! isSameExpr n root.self
-- Go to next element
curr getNext curr
if isSameExpr root.self curr then
break
-- The size of the equivalence class is correct.
assert! root.size == size
private def checkParents (e : Expr) : GoalM Unit := do
if ( isRoot e) then
for parent in ( getParents e) do
let mut found := false
let checkChild (child : Expr) : GoalM Bool := do
let some childRoot getRoot? child | return false
return isSameExpr childRoot e
-- There is an argument `arg` s.t. root of `arg` is `e`.
for arg in parent.getAppArgs do
if ( checkChild arg) then
found := true
break
unless found do
assert! ( checkChild parent.getAppFn)
else
-- All the parents are stored in the root of the equivalence class.
assert! ( getParents e).isEmpty
private def checkPtrEqImpliesStructEq : GoalM Unit := do
let nodes getENodes
for h₁ : i in [: nodes.size] do
let n₁ := nodes[i]
for h₂ : j in [i+1 : nodes.size] do
let n₂ := nodes[j]
-- We don't have multiple nodes for the same expression
assert! !isSameExpr n₁.self n₂.self
-- and the two expressions must not be structurally equal
assert! !Expr.equal n₁.self n₂.self
private def checkProofs : GoalM Unit := do
let eqcs getEqcs
for eqc in eqcs do
for a in eqc do
for b in eqc do
unless isSameExpr a b do
let p mkEqProof a b
trace[grind.debug.proofs] "{a} = {b}"
check p
trace[grind.debug.proofs] "checked: {← inferType p}"
/--
Checks basic invariants if `grind.debug` is enabled.
-/
def checkInvariants (expensive := false) : GoalM Unit := do
if grind.debug.get ( getOptions) then
for (_, node) in ( get).enodes do
checkParents node.self
if isSameExpr node.self node.root then
checkEqc node
if expensive then
checkPtrEqImpliesStructEq
if expensive && grind.debug.proofs.get ( getOptions) then
checkProofs
end Lean.Meta.Grind

View File

@@ -0,0 +1,59 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Util
import Lean.Util.PtrSet
import Lean.Meta.Basic
import Lean.Meta.InferType
namespace Lean.Meta.Grind
unsafe def markNestedProofsImpl (e : Expr) : MetaM Expr := do
visit e |>.run' mkPtrMap
where
visit (e : Expr) : StateRefT (PtrMap Expr Expr) MetaM Expr := do
if ( isProof e) then
if e.isAppOf ``Lean.Grind.nestedProof then
return e -- `e` is already marked
if let some r := ( get).find? e then
return r
let prop inferType e
let e' := mkApp2 (mkConst ``Lean.Grind.nestedProof) prop e
modify fun s => s.insert e e'
return e'
else match e with
| .bvar .. => unreachable!
-- See comments on `Canon.lean` for why we do not visit these cases.
| .letE .. | .forallE .. | .lam ..
| .const .. | .lit .. | .mvar .. | .sort .. | .fvar ..
| .proj ..
| .mdata .. => return e
-- We only visit applications
| .app .. =>
-- Check whether it is cached
if let some r := ( get).find? e then
return r
e.withApp fun f args => do
let mut modified := false
let mut args := args
for i in [:args.size] do
let arg := args[i]!
let arg' visit arg
unless ptrEq arg arg' do
args := args.set! i arg'
modified := true
let e' := if modified then mkAppN f args else e
modify fun s => s.insert e e'
return e'
/--
Wrap nested proofs `e` with `Lean.Grind.nestedProof`-applications.
Recall that the congruence closure module has special support for `Lean.Grind.nestedProof`.
-/
def markNestedProofs (e : Expr) : MetaM Expr :=
unsafe markNestedProofsImpl e
end Lean.Meta.Grind

View File

@@ -0,0 +1,59 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Util
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeRef (e : Expr) : GoalM Format := do
let some n getENode? e | return "_"
return f!"#{n.idx}"
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeDeclValue (e : Expr) : GoalM Format := do
if e.isApp && !( isLitValue e) then
e.withApp fun f args => do
let r if f.isConst then
ppExpr f
else
ppENodeRef f
let mut r := r
for arg in args do
r := r ++ " " ++ ( ppENodeRef arg)
return r
else
ppExpr e
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeDecl (e : Expr) : GoalM Format := do
let mut r := f!"{← ppENodeRef e} := {← ppENodeDeclValue e}"
let n getENode e
unless isSameExpr e n.root do
r := r ++ f!" ↦ {← ppENodeRef n.root}"
if n.interpreted then
r := r ++ ", [val]"
if n.ctor then
r := r ++ ", [ctor]"
if grind.debug.get ( getOptions) then
if let some target getTarget? e then
r := r ++ f!" ↝ {← ppENodeRef target}"
return r
/-- Pretty print goal state for debugging purposes. -/
def ppState : GoalM Format := do
let mut r := f!"Goal:"
let nodes getENodes
for node in nodes do
r := r ++ "\n" ++ ( ppENodeDecl node.self)
let eqcs getEqcs
for eqc in eqcs do
if eqc.length > 1 then
r := r ++ "\n" ++ "{" ++ (Format.joinSep ( eqc.mapM ppENodeRef) ", ") ++ "}"
return r
end Lean.Meta.Grind

View File

@@ -0,0 +1,15 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Parser.Command
namespace Lean.Parser.Command
/-!
Builtin parsers for `grind` related commands
-/
@[builtin_command_parser] def grindPattern := leading_parser
"grind_pattern " >> ident >> darrow >> sepBy1 termParser ","
end Lean.Parser.Command

View File

@@ -16,40 +16,20 @@ import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Tactic.Grind.Cases
import Lean.Meta.Tactic.Grind.Injection
import Lean.Meta.Tactic.Grind.Core
import Lean.Meta.Tactic.Grind.Simp
import Lean.Meta.Tactic.Grind.Run
namespace Lean.Meta.Grind
namespace Preprocessor
-- TODO: use congruence closure and decision procedures during pre-processing
-- TODO: implement `simp` discharger using preprocessor state
structure Context where
simp : Simp.Context
simprocs : Array Simp.Simprocs
deriving Inhabited
structure State where
simpStats : Simp.Stats := {}
goals : PArray Goal := {}
deriving Inhabited
abbrev PreM := ReaderT Context $ StateRefT State GrindM
abbrev PreM := StateRefT State GrindM
def PreM.run (x : PreM α) : GrindM α := do
let thms grindNormExt.getTheorems
let simprocs := #[( grindNormSimprocExt.getSimprocs)]
let simp Simp.mkContext
(config := { arith := true })
(simpTheorems := #[thms])
(congrTheorems := ( getSimpCongrTheorems))
x { simp, simprocs } |>.run' {}
def simp (_goal : Goal) (e : Expr) : PreM Simp.Result := do
-- TODO: use `goal` state in the simplifier
let simpStats := ( get).simpStats
let (r, simpStats) Meta.simp e ( read).simp ( read).simprocs (stats := simpStats)
modify fun s => { s with simpStats }
return r
x.run' {}
inductive IntroResult where
| done
@@ -69,21 +49,16 @@ def introNext (goal : Goal) : PreM IntroResult := do
let tag goal.mvarId.getTag
let q := target.bindingBody!
-- TODO: keep applying simp/eraseIrrelevantMData/canon/shareCommon until no progress
let r simp goal p
let p' := r.expr
let p' eraseIrrelevantMData p'
let p' foldProjs p'
let p' canon p'
let p' shareCommon p'
let r pre p
let fvarId mkFreshFVarId
let lctx := ( getLCtx).mkLocalDecl fvarId target.bindingName! p' target.bindingInfo!
let lctx := ( getLCtx).mkLocalDecl fvarId target.bindingName! r.expr target.bindingInfo!
let mvarNew mkFreshExprMVarAt lctx ( getLocalInstances) q .syntheticOpaque tag
let mvarIdNew := mvarNew.mvarId!
mvarIdNew.withContext do
let h mkLambdaFVars #[mkFVar fvarId] mvarNew
match r.proof? with
| some he =>
let hNew := mkAppN (mkConst ``Lean.Grind.intro_with_eq) #[p, p', q, he, h]
let hNew := mkAppN (mkConst ``Lean.Grind.intro_with_eq) #[p, r.expr, q, he, h]
goal.mvarId.assign hNew
return .newHyp fvarId { goal with mvarId := mvarIdNew }
| none =>
@@ -96,7 +71,7 @@ def introNext (goal : Goal) : PreM IntroResult := do
let localDecl fvarId.getDecl
if ( isProp localDecl.type) then
-- Add a non-dependent copy
let mvarId mvarId.assert localDecl.userName localDecl.type (mkFVar fvarId)
let mvarId mvarId.assert ( mkFreshUserName localDecl.userName) localDecl.type (mkFVar fvarId)
return .newDepHyp { goal with mvarId }
else
return .newLocal fvarId { goal with mvarId }
@@ -124,6 +99,8 @@ def applyInjection? (goal : Goal) (fvarId : FVarId) : MetaM (Option Goal) := do
return none
partial def loop (goal : Goal) : PreM Unit := do
if goal.inconsistent then
return ()
match ( introNext goal) with
| .done =>
if let some mvarId goal.mvarId.byContra? then
@@ -153,26 +130,43 @@ def ppGoals : PreM Format := do
return r
def preprocess (mvarId : MVarId) : PreM State := do
loop ( mkGoal mvarId)
if ( isTracingEnabledFor `grind.pre) then
trace[grind.pre] ( ppGoals)
get
end Preprocessor
open Preprocessor
partial def main (mvarId : MVarId) (mainDeclName : Name) : MetaM (List MVarId) := do
mvarId.ensureProp
-- TODO: abstract metavars
mvarId.ensureNoMVar
let mvarId mvarId.clearAuxDecls
let mvarId mvarId.revertAll
mvarId.ensureNoMVar
let mvarId mvarId.abstractNestedProofs mainDeclName
let mvarId mvarId.abstractNestedProofs ( getMainDeclName)
let mvarId mvarId.unfoldReducible
let mvarId mvarId.betaReduce
let s preprocess mvarId |>.run |>.run mainDeclName
return s.goals.toList.map (·.mvarId)
loop ( mkGoal mvarId)
if ( isTracingEnabledFor `grind.pre) then
trace[grind.pre] ( ppGoals)
for goal in ( get).goals do
discard <| GoalM.run' goal <| checkInvariants (expensive := true)
get
def preprocessAndProbe (mvarId : MVarId) (p : GoalM Unit) : PreM Unit := do
let s preprocess mvarId
s.goals.forM fun goal =>
discard <| GoalM.run' goal p
end Preprocessor
open Preprocessor
def preprocessAndProbe (mvarId : MVarId) (mainDeclName : Name) (p : GoalM Unit) : MetaM Unit :=
withoutModifyingMCtx do
Preprocessor.preprocessAndProbe mvarId p |>.run |>.run mainDeclName
def preprocess (mvarId : MVarId) (mainDeclName : Name) : MetaM Preprocessor.State :=
Preprocessor.preprocess mvarId |>.run |>.run mainDeclName
def main (mvarId : MVarId) (mainDeclName : Name) : MetaM (List MVarId) := do
let go : GrindM (List MVarId) := do
let s Preprocessor.preprocess mvarId |>.run
let goals := s.goals.toList.filter fun goal => !goal.inconsistent
return goals.map (·.mvarId)
go.run mainDeclName
end Lean.Meta.Grind

View File

@@ -0,0 +1,35 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.ProjFns
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Internalize
namespace Lean.Meta.Grind
/--
If `parent` is a projection-application `proj_i c`,
check whether the root of the equivalence class containing `c` is a constructor-application `ctor ... a_i ...`.
If so, internalize the term `proj_i (ctor ... a_i ...)` and add the equality `proj_i (ctor ... a_i ...) = a_i`.
-/
def propagateProjEq (parent : Expr) : GoalM Unit := do
let .const declName _ := parent.getAppFn | return ()
let some info getProjectionFnInfo? declName | return ()
unless info.numParams + 1 == parent.getAppNumArgs do return ()
let arg := parent.appArg!
let ctor getRoot arg
unless ctor.isAppOf info.ctorName do return ()
if isSameExpr arg ctor then
let idx := info.numParams + info.i
unless idx < ctor.getAppNumArgs do return ()
let v := ctor.getArg! idx
pushEq parent v ( mkEqRefl v)
else
let newProj := mkApp parent.appFn! ctor
let newProj shareCommon newProj
internalize newProj ( getGeneration parent)
end Lean.Meta.Grind

View File

@@ -0,0 +1,239 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Sorry -- TODO: remove
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
private def isEqProof (h : Expr) : MetaM Bool := do
return ( whnfD ( inferType h)).isAppOf ``Eq
private def flipProof (h : Expr) (flipped : Bool) (heq : Bool) : MetaM Expr := do
let mut h' := h
if ( pure heq <&&> isEqProof h') then
h' mkHEqOfEq h'
if flipped then
if heq then mkHEqSymm h' else mkEqSymm h'
else
return h'
private def mkRefl (a : Expr) (heq : Bool) : MetaM Expr :=
if heq then mkHEqRefl a else mkEqRefl a
private def mkTrans (h₁ h₂ : Expr) (heq : Bool) : MetaM Expr :=
if heq then
mkHEqTrans h₁ h₂
else
mkEqTrans h₁ h₂
private def mkTrans' (h₁ : Option Expr) (h₂ : Expr) (heq : Bool) : MetaM Expr := do
let some h₁ := h₁ | return h₂
mkTrans h₁ h₂ heq
/--
Given `h : HEq a b`, returns a proof `a = b` if `heq == false`.
Otherwise, it returns `h`.
-/
private def mkEqOfHEqIfNeeded (h : Expr) (heq : Bool) : MetaM Expr := do
if heq then return h else mkEqOfHEq h
/--
Given `lhs` and `rhs` that are in the same equivalence class,
find the common expression that are in the paths from `lhs` and `rhs` to
the root of their equivalence class.
Recall that this expression must exist since it is the root itself in the
worst case.
-/
private def findCommon (lhs rhs : Expr) : GoalM Expr := do
let mut visited : RBMap Nat Expr compare := {}
let mut it := lhs
-- Mark elements found following the path from `lhs` to the root.
repeat
let n getENode it
visited := visited.insert n.idx n.self
let some target := n.target? | break
it := target
-- Find the marked element from the path from `rhs` to the root.
it := rhs
repeat
let n getENode it
if let some common := visited.find? n.idx then
return common
let some target := n.target? | unreachable! --
it := target
unreachable!
/--
Returns `true` if we can construct a congruence proof for `lhs = rhs` using `congrArg`, `congrFun`, and `congr`.
`f` (`g`) is the function of the `lhs` (`rhs`) application. `numArgs` is the number of arguments.
-/
private partial def isCongrDefaultProofTarget (lhs rhs : Expr) (f g : Expr) (numArgs : Nat) : GoalM Bool := do
unless isSameExpr f g do return false
let info := ( getFunInfo f).paramInfo
let rec loop (lhs rhs : Expr) (i : Nat) : GoalM Bool := do
if lhs.isApp then
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
let i := i - 1
unless isSameExpr a₁ a₂ do
if h : i < info.size then
if info[i].hasFwdDeps then
-- Cannot use `congrArg` because there are forward dependencies
return false
else
return false -- Don't have information about argument
loop lhs.appFn! rhs.appFn! i
else
return true
loop lhs rhs numArgs
mutual
/--
Given `lhs` and `rhs` proof terms of the form `nestedProof p hp` and `nestedProof q hq`,
constructs a congruence proof for `HEq (nestedProof p hp) (nestedProof q hq)`.
`p` and `q` are in the same equivalence class.
-/
private partial def mkNestedProofCongr (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let p := lhs.appFn!.appArg!
let hp := lhs.appArg!
let q := rhs.appFn!.appArg!
let hq := rhs.appArg!
let h := mkApp5 (mkConst ``Lean.Grind.nestedProof_congr) p q ( mkEqProofCore p q false) hp hq
mkEqOfHEqIfNeeded h heq
/--
Constructs a congruence proof for `lhs` and `rhs` using `congr`, `congrFun`, and `congrArg`.
This function assumes `isCongrDefaultProofTarget` returned `true`.
-/
private partial def mkCongrDefaultProof (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let rec loop (lhs rhs : Expr) : GoalM (Option Expr) := do
if lhs.isApp then
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
if let some proof loop lhs.appFn! rhs.appFn! then
if isSameExpr a₁ a₂ then
mkCongrFun proof a₁
else
mkCongr proof ( mkEqProofCore a₁ a₂ false)
else if isSameExpr a₁ a₂ then
return none -- refl case
else
mkCongrArg lhs.appFn! ( mkEqProofCore a₁ a₂ false)
else
return none
let r := ( loop lhs rhs).get!
if heq then mkHEqOfEq r else return r
/-- Constructs a congruence proof for `lhs` and `rhs`. -/
private partial def mkCongrProof (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let f := lhs.getAppFn
let g := rhs.getAppFn
let numArgs := lhs.getAppNumArgs
assert! rhs.getAppNumArgs == numArgs
if f.isConstOf ``Lean.Grind.nestedProof && g.isConstOf ``Lean.Grind.nestedProof && numArgs == 2 then
mkNestedProofCongr lhs rhs heq
else if ( isCongrDefaultProofTarget lhs rhs f g numArgs) then
mkCongrDefaultProof lhs rhs heq
else
let thm mkHCongrWithArity f numArgs
assert! thm.argKinds.size == numArgs
let rec loop (lhs rhs : Expr) (i : Nat) : GoalM Expr := do
let i := i - 1
if lhs.isApp then
let proof loop lhs.appFn! rhs.appFn! i
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
let k := thm.argKinds[i]!
return mkApp3 proof a₁ a₂ ( mkEqProofCore a₁ a₂ (k matches .heq))
else
return thm.proof
let proof loop lhs rhs numArgs
if isSameExpr f g then
mkEqOfHEqIfNeeded proof heq
else
/-
`lhs` is of the form `f a_1 ... a_n`
`rhs` is of the form `g b_1 ... b_n`
`proof : HEq (f a_1 ... a_n) (f b_1 ... b_n)`
We construct a proof for `HEq (f a_1 ... a_n) (g b_1 ... b_n)` using `Eq.ndrec`
-/
let motive withLocalDeclD ( mkFreshUserName `x) ( inferType f) fun x => do
mkLambdaFVars #[x] ( mkHEq lhs (mkAppN x rhs.getAppArgs))
let fEq mkEqProofCore f g false
let proof mkEqNDRec motive proof fEq
mkEqOfHEqIfNeeded proof heq
private partial def realizeEqProof (lhs rhs : Expr) (h : Expr) (flipped : Bool) (heq : Bool) : GoalM Expr := do
let h if h == congrPlaceholderProof then
mkCongrProof lhs rhs heq
else
flipProof h flipped heq
/-- Given `acc : lhs₀ = lhs`, returns a proof of `lhs₀ = common`. -/
private partial def mkProofTo (lhs : Expr) (common : Expr) (acc : Option Expr) (heq : Bool) : GoalM (Option Expr) := do
if isSameExpr lhs common then
return acc
let n getENode lhs
let some target := n.target? | unreachable!
let some h := n.proof? | unreachable!
let h realizeEqProof lhs target h n.flipped heq
-- h : lhs = target
let acc mkTrans' acc h heq
mkProofTo target common (some acc) heq
/-- Given `lhsEqCommon : lhs = common`, returns a proof for `lhs = rhs`. -/
private partial def mkProofFrom (rhs : Expr) (common : Expr) (lhsEqCommon? : Option Expr) (heq : Bool) : GoalM (Option Expr) := do
if isSameExpr rhs common then
return lhsEqCommon?
let n getENode rhs
let some target := n.target? | unreachable!
let some h := n.proof? | unreachable!
let h realizeEqProof target rhs h (!n.flipped) heq
-- `h : target = rhs`
let h' mkProofFrom target common lhsEqCommon? heq
-- `h' : lhs = target`
mkTrans' h' h heq
private partial def mkEqProofCore (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
if isSameExpr lhs rhs then
return ( mkRefl lhs heq)
let n₁ getENode lhs
let n₂ getENode rhs
assert! isSameExpr n₁.root n₂.root
let common findCommon lhs rhs
let lhsEqCommon? mkProofTo lhs common none heq
let some lhsEqRhs mkProofFrom rhs common lhsEqCommon? heq | unreachable!
return lhsEqRhs
end
/--
Returns a proof that `a = b` (or `HEq a b`).
It assumes `a` and `b` are in the same equivalence class.
-/
@[export lean_grind_mk_eq_proof]
def mkEqProofImpl (a b : Expr) : GoalM Expr := do
let p go
trace[grind.proof.detail] "{p}"
return p
where
go : GoalM Expr := do
let n getRootENode a
if !n.heqProofs then
trace[grind.proof] "{a} = {b}"
mkEqProofCore a b (heq := false)
else
if ( hasSameType a b) then
trace[grind.proof] "{a} = {b}"
mkEqOfHEq ( mkEqProofCore a b (heq := true))
else
trace[grind.proof] "{a} ≡ {b}"
mkEqProofCore a b (heq := true)
def mkHEqProof (a b : Expr) : GoalM Expr :=
mkEqProofCore a b (heq := true)
end Lean.Meta.Grind

View File

@@ -0,0 +1,145 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind
import Lean.Meta.Tactic.Grind.Proof
import Lean.Meta.Tactic.Grind.PropagatorAttr
namespace Lean.Meta.Grind
/--
Propagates equalities for a conjunction `a ∧ b` based on the truth values
of its components `a` and `b`. This function checks the truth value of `a` and `b`,
and propagates the following equalities:
- If `a = True`, propagates `(a ∧ b) = b`.
- If `b = True`, propagates `(a ∧ b) = a`.
- If `a = False`, propagates `(a ∧ b) = False`.
- If `b = False`, propagates `(a ∧ b) = False`.
-/
builtin_grind_propagator propagateAndUp And := fun e => do
let_expr And a b := e | return ()
if ( isEqTrue a) then
-- a = True → (a ∧ b) = b
pushEq e b <| mkApp3 (mkConst ``Lean.Grind.and_eq_of_eq_true_left) a b ( mkEqTrueProof a)
else if ( isEqTrue b) then
-- b = True → (a ∧ b) = a
pushEq e a <| mkApp3 (mkConst ``Lean.Grind.and_eq_of_eq_true_right) a b ( mkEqTrueProof b)
else if ( isEqFalse a) then
-- a = False → (a ∧ b) = False
pushEqFalse e <| mkApp3 (mkConst ``Lean.Grind.and_eq_of_eq_false_left) a b ( mkEqFalseProof a)
else if ( isEqFalse b) then
-- b = False → (a ∧ b) = False
pushEqFalse e <| mkApp3 (mkConst ``Lean.Grind.and_eq_of_eq_false_right) a b ( mkEqFalseProof b)
/--
Propagates truth values downwards for a conjunction `a ∧ b` when the
expression itself is known to be `True`.
-/
builtin_grind_propagator propagateAndDown And := fun e => do
if ( isEqTrue e) then
let_expr And a b := e | return ()
let h mkEqTrueProof e
pushEqTrue a <| mkApp3 (mkConst ``Lean.Grind.eq_true_of_and_eq_true_left) a b h
pushEqTrue b <| mkApp3 (mkConst ``Lean.Grind.eq_true_of_and_eq_true_right) a b h
/--
Propagates equalities for a disjunction `a b` based on the truth values
of its components `a` and `b`. This function checks the truth value of `a` and `b`,
and propagates the following equalities:
- If `a = False`, propagates `(a b) = b`.
- If `b = False`, propagates `(a b) = a`.
- If `a = True`, propagates `(a b) = True`.
- If `b = True`, propagates `(a b) = True`.
-/
builtin_grind_propagator propagateOrUp Or := fun e => do
let_expr Or a b := e | return ()
if ( isEqFalse a) then
-- a = False → (a b) = b
pushEq e b <| mkApp3 (mkConst ``Lean.Grind.or_eq_of_eq_false_left) a b ( mkEqFalseProof a)
else if ( isEqFalse b) then
-- b = False → (a b) = a
pushEq e a <| mkApp3 (mkConst ``Lean.Grind.or_eq_of_eq_false_right) a b ( mkEqFalseProof b)
else if ( isEqTrue a) then
-- a = True → (a b) = True
pushEqTrue e <| mkApp3 (mkConst ``Lean.Grind.or_eq_of_eq_true_left) a b ( mkEqTrueProof a)
else if ( isEqTrue b) then
-- b = True → (a ∧ b) = True
pushEqTrue e <| mkApp3 (mkConst ``Lean.Grind.or_eq_of_eq_true_right) a b ( mkEqTrueProof b)
/--
Propagates truth values downwards for a disjuction `a b` when the
expression itself is known to be `False`.
-/
builtin_grind_propagator propagateOrDown Or := fun e => do
if ( isEqFalse e) then
let_expr Or a b := e | return ()
let h mkEqFalseProof e
pushEqFalse a <| mkApp3 (mkConst ``Lean.Grind.eq_false_of_or_eq_false_left) a b h
pushEqFalse b <| mkApp3 (mkConst ``Lean.Grind.eq_false_of_or_eq_false_right) a b h
/--
Propagates equalities for a negation `Not a` based on the truth value of `a`.
This function checks the truth value of `a` and propagates the following equalities:
- If `a = False`, propagates `(Not a) = True`.
- If `a = True`, propagates `(Not a) = False`.
-/
builtin_grind_propagator propagateNotUp Not := fun e => do
let_expr Not a := e | return ()
if ( isEqFalse a) then
-- a = False → (Not a) = True
pushEqTrue e <| mkApp2 (mkConst ``Lean.Grind.not_eq_of_eq_false) a ( mkEqFalseProof a)
else if ( isEqTrue a) then
-- a = True → (Not a) = False
pushEqFalse e <| mkApp2 (mkConst ``Lean.Grind.not_eq_of_eq_true) a ( mkEqTrueProof a)
/--
Propagates truth values downwards for a negation expression `Not a` based on the truth value of `Not a`.
This function performs the following:
- If `(Not a) = False`, propagates `a = True`.
- If `(Not a) = True`, propagates `a = False`.
-/
builtin_grind_propagator propagateNotDown Not := fun e => do
let_expr Not a := e | return ()
if ( isEqFalse e) then
pushEqTrue a <| mkApp2 (mkConst ``Lean.Grind.eq_true_of_not_eq_false) a ( mkEqFalseProof e)
else if ( isEqTrue e) then
pushEqFalse a <| mkApp2 (mkConst ``Lean.Grind.eq_false_of_not_eq_true) a ( mkEqTrueProof e)
else if ( isEqv e a) then
closeGoal <| mkApp2 (mkConst ``Lean.Grind.false_of_not_eq_self) a ( mkEqProof e a)
/-- Propagates `Eq` upwards -/
builtin_grind_propagator propagateEqUp Eq := fun e => do
let_expr Eq _ a b := e | return ()
if ( isEqTrue a) then
pushEq e b <| mkApp3 (mkConst ``Lean.Grind.eq_eq_of_eq_true_left) a b ( mkEqTrueProof a)
else if ( isEqTrue b) then
pushEq e a <| mkApp3 (mkConst ``Lean.Grind.eq_eq_of_eq_true_right) a b ( mkEqTrueProof b)
else if ( isEqv a b) then
pushEqTrue e <| mkApp2 (mkConst ``eq_true) e ( mkEqProof a b)
/-- Propagates `Eq` downwards -/
builtin_grind_propagator propagateEqDown Eq := fun e => do
if ( isEqTrue e) then
let_expr Eq _ a b := e | return ()
pushEq a b <| mkApp2 (mkConst ``of_eq_true) e ( mkEqTrueProof e)
/-- Propagates `HEq` downwards -/
builtin_grind_propagator propagateHEqDown HEq := fun e => do
if ( isEqTrue e) then
let_expr HEq _ a _ b := e | return ()
pushHEq a b <| mkApp2 (mkConst ``of_eq_true) e ( mkEqTrueProof e)
/-- Propagates `HEq` upwards -/
builtin_grind_propagator propagateHEqUp HEq := fun e => do
let_expr HEq _ a _ b := e | return ()
if ( isEqv a b) then
pushEqTrue e <| mkApp2 (mkConst ``eq_true) e ( mkHEqProof a b)
end Lean.Meta.Grind

View File

@@ -0,0 +1,61 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind
import Lean.Meta.Tactic.Grind.Proof
namespace Lean.Meta.Grind
/-- Builtin propagators. -/
structure BuiltinPropagators where
up : Std.HashMap Name Propagator := {}
down : Std.HashMap Name Propagator := {}
deriving Inhabited
builtin_initialize builtinPropagatorsRef : IO.Ref BuiltinPropagators IO.mkRef {}
private def registerBuiltinPropagatorCore (declName : Name) (up : Bool) (proc : Propagator) : IO Unit := do
unless ( initializing) do
throw (IO.userError s!"invalid builtin `grind` propagator declaration, it can only be registered during initialization")
if up then
if ( builtinPropagatorsRef.get).up.contains declName then
throw (IO.userError s!"invalid builtin `grind` upward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up := up.insert declName proc, down }
else
if ( builtinPropagatorsRef.get).down.contains declName then
throw (IO.userError s!"invalid builtin `grind` downward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up, down := down.insert declName proc }
def registerBuiltinUpwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName true proc
def registerBuiltinDownwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName false proc
private def addBuiltin (propagatorName : Name) (stx : Syntax) : AttrM Unit := do
let go : MetaM Unit := do
let up := stx[1].getKind == ``Lean.Parser.Tactic.simpPost
let addDeclName := if up then
``registerBuiltinUpwardPropagator
else
``registerBuiltinDownwardPropagator
let declName resolveGlobalConstNoOverload stx[2]
let val := mkAppN (mkConst addDeclName) #[toExpr declName, mkConst propagatorName]
let initDeclName mkFreshUserName (propagatorName ++ `declare)
declareBuiltin initDeclName val
go.run' {}
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `grindPropagatorBuiltinAttr
descr := "Builtin `grind` propagator procedure"
applicationTime := AttributeApplicationTime.afterCompilation
erase := fun _ => throwError "Not implemented yet, [-builtin_simproc]"
add := fun declName stx _ => addBuiltin declName stx
}
end Lean.Meta.Grind

View File

@@ -0,0 +1,53 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Lemmas
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.PropagatorAttr
import Lean.Meta.Tactic.Grind.Proj
namespace Lean.Meta.Grind
def mkMethods : CoreM Methods := do
let builtinPropagators builtinPropagatorsRef.get
return {
propagateUp := fun e => do
let .const declName _ := e.getAppFn | return ()
propagateProjEq e
if let some prop := builtinPropagators.up[declName]? then
prop e
propagateDown := fun e => do
let .const declName _ := e.getAppFn | return ()
if let some prop := builtinPropagators.down[declName]? then
prop e
}
def GrindM.run (x : GrindM α) (mainDeclName : Name) : MetaM α := do
let scState := ShareCommon.State.mk _
let (falseExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``False)
let (trueExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``True)
let thms grindNormExt.getTheorems
let simprocs := #[( grindNormSimprocExt.getSimprocs)]
let simp Simp.mkContext
(config := { arith := true })
(simpTheorems := #[thms])
(congrTheorems := ( getSimpCongrTheorems))
x ( mkMethods).toMethodsRef { mainDeclName, simprocs, simp } |>.run' { scState, trueExpr, falseExpr }
@[inline] def GoalM.run (goal : Goal) (x : GoalM α) : GrindM (α × Goal) :=
goal.mvarId.withContext do StateRefT'.run x goal
@[inline] def GoalM.run' (goal : Goal) (x : GoalM Unit) : GrindM Goal :=
goal.mvarId.withContext do StateRefT'.run' (x *> get) goal
def mkGoal (mvarId : MVarId) : GrindM Goal := do
let trueExpr getTrueExpr
let falseExpr getFalseExpr
GoalM.run' { mvarId } do
mkENodeCore falseExpr (interpreted := true) (ctor := false) (generation := 0)
mkENodeCore trueExpr (interpreted := true) (ctor := false) (generation := 0)
end Lean.Meta.Grind

View File

@@ -0,0 +1,41 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Simp.Main
import Lean.Meta.Tactic.Grind.Util
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.MarkNestedProofs
namespace Lean.Meta.Grind
-- TODO: use congruence closure and decision procedures during pre-processing
-- TODO: implement `simp` discharger using preprocessor state
/-- Simplifies the given expression using the `grind` simprocs and normalization theorems. -/
def simp (e : Expr) : GrindM Simp.Result := do
let simpStats := ( get).simpStats
let (r, simpStats) Meta.simp e ( readThe Context).simp ( readThe Context).simprocs (stats := simpStats)
modify fun s => { s with simpStats }
return r
/--
Simplifies `e` using `grind` normalization theorems and simprocs,
and then applies several other preprocessing steps.
-/
def pre (e : Expr) : GrindM Simp.Result := do
let r simp e
let e' := r.expr
let e' markNestedProofs e'
let e' unfoldReducible e'
let e' eraseIrrelevantMData e'
let e' foldProjs e'
let e' normalizeLevels e'
let e' canon e'
let e' shareCommon e'
trace[grind.simp] "{e}\n===>\n{e'}"
return { r with expr := e' }
end Lean.Meta.Grind

View File

@@ -8,8 +8,10 @@ import Lean.Util.ShareCommon
import Lean.Meta.Basic
import Lean.Meta.CongrTheorems
import Lean.Meta.AbstractNestedProofs
import Lean.Meta.Canonicalizer
import Lean.Meta.Tactic.Simp.Types
import Lean.Meta.Tactic.Util
import Lean.Meta.Tactic.Grind.Canon
import Lean.Meta.Tactic.Grind.Attr
namespace Lean.Meta.Grind
@@ -18,12 +20,36 @@ namespace Lean.Meta.Grind
-- inserted into the E-graph
unsafe ptrEq a b
structure Context where
mainDeclName : Name
/-- We use this auxiliary constant to mark delayed congruence proofs. -/
def congrPlaceholderProof := mkConst (Name.mkSimple "[congruence]")
/--
Key for the congruence theorem cache.
Returns `true` if `e` is `True`, `False`, or a literal value.
See `LitValues` for supported literals.
-/
def isInterpreted (e : Expr) : MetaM Bool := do
if e.isTrue || e.isFalse then return true
isLitValue e
register_builtin_option grind.debug : Bool := {
defValue := false
group := "debug"
descr := "check invariants after updates"
}
register_builtin_option grind.debug.proofs : Bool := {
defValue := false
group := "debug"
descr := "check proofs between the elements of all equivalence classes"
}
/-- Context for `GrindM` monad. -/
structure Context where
simp : Simp.Context
simprocs : Array Simp.Simprocs
mainDeclName : Name
/-- Key for the congruence theorem cache. -/
structure CongrTheoremCacheKey where
f : Expr
numArgs : Nat
@@ -36,8 +62,9 @@ instance : BEq CongrTheoremCacheKey where
instance : Hashable CongrTheoremCacheKey where
hash a := mixHash (unsafe ptrAddrUnsafe a.f).toUInt64 (hash a.numArgs)
/-- State for the `GrindM` monad. -/
structure State where
canon : Canonicalizer.State := {}
canon : Canon.State := {}
/-- `ShareCommon` (aka `Hashconsing`) state. -/
scState : ShareCommon.State.{0} ShareCommon.objectFactory := ShareCommon.State.mk _
/-- Next index for creating auxiliary theorems. -/
@@ -48,29 +75,36 @@ structure State where
Remark: we currently do not reuse congruence theorems
-/
congrThms : PHashMap CongrTheoremCacheKey CongrTheorem := {}
simpStats : Simp.Stats := {}
trueExpr : Expr
falseExpr : Expr
abbrev GrindM := ReaderT Context $ StateRefT State MetaM
private opaque MethodsRefPointed : NonemptyType.{0}
private def MethodsRef : Type := MethodsRefPointed.type
instance : Nonempty MethodsRef := MethodsRefPointed.property
def GrindM.run (x : GrindM α) (mainDeclName : Name) : MetaM α := do
let scState := ShareCommon.State.mk _
let (falseExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``False)
let (trueExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``True)
x { mainDeclName } |>.run' { scState, trueExpr, falseExpr }
abbrev GrindM := ReaderT MethodsRef $ ReaderT Context $ StateRefT State MetaM
/-- Returns the internalized `True` constant. -/
def getTrueExpr : GrindM Expr := do
return ( get).trueExpr
/-- Returns the internalized `False` constant. -/
def getFalseExpr : GrindM Expr := do
return ( get).falseExpr
def getMainDeclName : GrindM Name :=
return ( readThe Context).mainDeclName
@[inline] def getMethodsRef : GrindM MethodsRef :=
read
/--
Abtracts nested proofs in `e`. This is a preprocessing step performed before internalization.
-/
def abstractNestedProofs (e : Expr) : GrindM Expr := do
let nextIdx := ( get).nextThmIdx
let (e, s') AbstractNestedProofs.visit e |>.run { baseName := ( read).mainDeclName } |>.run |>.run { nextIdx }
let (e, s') AbstractNestedProofs.visit e |>.run { baseName := ( getMainDeclName) } |>.run |>.run { nextIdx }
modify fun s => { s with nextThmIdx := s'.nextIdx }
return e
@@ -79,30 +113,18 @@ Applies hash-consing to `e`. Recall that all expressions in a `grind` goal have
been hash-consing. We perform this step before we internalize expressions.
-/
def shareCommon (e : Expr) : GrindM Expr := do
modifyGet fun { canon, scState, nextThmIdx, congrThms, trueExpr, falseExpr } =>
modifyGet fun { canon, scState, nextThmIdx, congrThms, trueExpr, falseExpr, simpStats } =>
let (e, scState) := ShareCommon.State.shareCommon scState e
(e, { canon, scState, nextThmIdx, congrThms, trueExpr, falseExpr })
(e, { canon, scState, nextThmIdx, congrThms, trueExpr, falseExpr, simpStats })
/--
Applies the canonicalizer to all subterms of `e`.
Canonicalizes nested types, type formers, and instances in `e`.
-/
-- TODO: the current canonicalizer is not a good solution for `grind`.
-- The problem is that two different applications `@f inst_1 a` and `@f inst_2 b`
-- may still have syntaticaally different instances. Thus, if we learn that `a = b`,
-- congruence closure will fail to see that the two applications are congruent.
def canon (e : Expr) : GrindM Expr := do
let canonS modifyGet fun s => (s.canon, { s with canon := {} })
let (e, canonS) Canonicalizer.CanonM.run (canonRec e) (s := canonS)
let (e, canonS) Canon.canon e |>.run canonS
modify fun s => { s with canon := canonS }
return e
where
canonRec (e : Expr) : CanonM Expr := do
let post (e : Expr) : CanonM TransformStep := do
if e.isApp then
return .done ( Meta.canon e)
else
return .done e
transform e post
/--
Creates a congruence theorem for a `f`-applications with `numArgs` arguments.
@@ -169,9 +191,73 @@ structure NewEq where
proof : Expr
isHEq : Bool
abbrev ENodes := PHashMap USize ENode
structure CongrKey (enodes : ENodes) where
e : Expr
private abbrev toENodeKey (e : Expr) : USize :=
unsafe ptrAddrUnsafe e
private def hashRoot (enodes : ENodes) (e : Expr) : UInt64 :=
if let some node := enodes.find? (toENodeKey e) then
toENodeKey node.root |>.toUInt64
else
13
private def hasSameRoot (enodes : ENodes) (a b : Expr) : Bool := Id.run do
let ka := toENodeKey a
let kb := toENodeKey b
if ka == kb then
return true
else
let some n1 := enodes.find? ka | return false
let some n2 := enodes.find? kb | return false
toENodeKey n1.root == toENodeKey n2.root
def congrHash (enodes : ENodes) (e : Expr) : UInt64 :=
if e.isAppOfArity ``Lean.Grind.nestedProof 2 then
-- We only hash the proposition
hashRoot enodes (e.getArg! 0)
else
go e 17
where
go (e : Expr) (r : UInt64) : UInt64 :=
match e with
| .app f a => go f (mixHash r (hashRoot enodes a))
| _ => mixHash r (hashRoot enodes e)
partial def isCongruent (enodes : ENodes) (a b : Expr) : Bool :=
if a.isAppOfArity ``Lean.Grind.nestedProof 2 && b.isAppOfArity ``Lean.Grind.nestedProof 2 then
hasSameRoot enodes (a.getArg! 0) (b.getArg! 0)
else
go a b
where
go (a b : Expr) : Bool :=
if a.isApp && b.isApp then
hasSameRoot enodes a.appArg! b.appArg! && go a.appFn! b.appFn!
else
-- Remark: we do not check whether the types of the functions are equal here
-- because we are not in the `MetaM` monad.
hasSameRoot enodes a b
instance : Hashable (CongrKey enodes) where
hash k := congrHash enodes k.e
instance : BEq (CongrKey enodes) where
beq k1 k2 := isCongruent enodes k1.e k2.e
abbrev CongrTable (enodes : ENodes) := PHashSet (CongrKey enodes)
-- Remark: we cannot use pointer addresses here because we have to traverse the tree.
abbrev ParentSet := RBTree Expr Expr.quickComp
abbrev ParentMap := PHashMap USize ParentSet
structure Goal where
mvarId : MVarId
enodes : PHashMap USize ENode := {}
enodes : ENodes := {}
parents : ParentMap := {}
congrTable : CongrTable enodes := {}
/-- Equations to be processed. -/
newEqs : Array NewEq := #[]
/-- `inconsistent := true` if `ENode`s for `True` and `False` are in the same equivalence class. -/
@@ -187,11 +273,15 @@ def Goal.admit (goal : Goal) : MetaM Unit :=
abbrev GoalM := StateRefT Goal GrindM
@[inline] def GoalM.run (goal : Goal) (x : GoalM α) : GrindM (α × Goal) :=
goal.mvarId.withContext do StateRefT'.run x goal
abbrev Propagator := Expr GoalM Unit
@[inline] def GoalM.run' (goal : Goal) (x : GoalM Unit) : GrindM Goal :=
goal.mvarId.withContext do StateRefT'.run' (x *> get) goal
/-- Returns `true` if `e` is the internalized `True` expression. -/
def isTrueExpr (e : Expr) : GrindM Bool :=
return isSameExpr e ( getTrueExpr)
/-- Returns `true` if `e` is the internalized `False` expression. -/
def isFalseExpr (e : Expr) : GrindM Bool :=
return isSameExpr e ( getFalseExpr)
/--
Returns `some n` if `e` has already been "internalized" into the
@@ -202,13 +292,51 @@ def getENode? (e : Expr) : GoalM (Option ENode) :=
/-- Returns node associated with `e`. It assumes `e` has already been internalized. -/
def getENode (e : Expr) : GoalM ENode := do
let some n := ( get).enodes.find? (unsafe ptrAddrUnsafe e) | unreachable!
let some n := ( get).enodes.find? (unsafe ptrAddrUnsafe e)
| throwError "internal `grind` error, term has not been internalized{indentExpr e}"
return n
/-- Returns the generation of the given term. Is assumes it has been internalized -/
def getGeneration (e : Expr) : GoalM Nat :=
return ( getENode e).generation
/-- Returns `true` if `e` is in the equivalence class of `True`. -/
def isEqTrue (e : Expr) : GoalM Bool := do
let n getENode e
return isSameExpr n.root ( getTrueExpr)
/-- Returns `true` if `e` is in the equivalence class of `False`. -/
def isEqFalse (e : Expr) : GoalM Bool := do
let n getENode e
return isSameExpr n.root ( getFalseExpr)
/-- Returns `true` if `a` and `b` are in the same equivalence class. -/
def isEqv (a b : Expr) : GoalM Bool := do
if isSameExpr a b then
return true
else
let na getENode a
let nb getENode b
return isSameExpr na.root nb.root
/-- Returns `true` if the root of its equivalence class. -/
def isRoot (e : Expr) : GoalM Bool := do
let some n getENode? e | return false -- `e` has not been internalized. Panic instead?
return isSameExpr n.root e
/-- Returns the root element in the equivalence class of `e` IF `e` has been internalized. -/
def getRoot? (e : Expr) : GoalM (Option Expr) := do
let some n getENode? e | return none
return some n.root
/-- Returns the root element in the equivalence class of `e`. -/
def getRoot (e : Expr) : GoalM Expr :=
return ( getENode e).root
/-- Returns the root enode in the equivalence class of `e`. -/
def getRootENode (e : Expr) : GoalM ENode := do
getENode ( getRoot e)
/-- Returns the next element in the equivalence class of `e`. -/
def getNext (e : Expr) : GoalM Expr :=
return ( getENode e).next
@@ -217,8 +345,86 @@ def getNext (e : Expr) : GoalM Expr :=
def alreadyInternalized (e : Expr) : GoalM Bool :=
return ( get).enodes.contains (unsafe ptrAddrUnsafe e)
def getTarget? (e : Expr) : GoalM (Option Expr) := do
let some n getENode? e | return none
return n.target?
/--
If `isHEq` is `false`, it pushes `lhs = rhs` with `proof` to `newEqs`.
Otherwise, it pushes `HEq lhs rhs`.
-/
def pushEqCore (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit :=
modify fun s => { s with newEqs := s.newEqs.push { lhs, rhs, proof, isHEq } }
/-- Return `true` if `a` and `b` have the same type. -/
def hasSameType (a b : Expr) : MetaM Bool :=
withDefault do isDefEq ( inferType a) ( inferType b)
@[inline] def pushEqHEq (lhs rhs proof : Expr) : GoalM Unit := do
if ( hasSameType lhs rhs) then
pushEqCore lhs rhs proof (isHEq := false)
else
pushEqCore lhs rhs proof (isHEq := true)
/-- Pushes `lhs = rhs` with `proof` to `newEqs`. -/
@[inline] def pushEq (lhs rhs proof : Expr) : GoalM Unit :=
pushEqCore lhs rhs proof (isHEq := false)
/-- Pushes `HEq lhs rhs` with `proof` to `newEqs`. -/
@[inline] def pushHEq (lhs rhs proof : Expr) : GoalM Unit :=
pushEqCore lhs rhs proof (isHEq := true)
/-- Pushes `a = True` with `proof` to `newEqs`. -/
def pushEqTrue (a proof : Expr) : GoalM Unit := do
pushEq a ( getTrueExpr) proof
/-- Pushes `a = False` with `proof` to `newEqs`. -/
def pushEqFalse (a proof : Expr) : GoalM Unit := do
pushEq a ( getFalseExpr) proof
/--
Records that `parent` is a parent of `child`. This function actually stores the
information in the root (aka canonical representative) of `child`.
-/
def registerParent (parent : Expr) (child : Expr) : GoalM Unit := do
let some childRoot getRoot? child | return ()
let key := toENodeKey childRoot
let parents := if let some parents := ( get).parents.find? key then parents else {}
modify fun s => { s with parents := s.parents.insert key (parents.insert parent) }
/--
Returns the set of expressions `e` is a child of, or an expression in
`e`s equivalence class is a child of.
The information is only up to date if `e` is the root (aka canonical representative) of the equivalence class.
-/
def getParents (e : Expr) : GoalM ParentSet := do
let some parents := ( get).parents.find? (toENodeKey e) | return {}
return parents
/--
Similar to `getParents`, but also removes the entry `e ↦ parents` from the parent map.
-/
def getParentsAndReset (e : Expr) : GoalM ParentSet := do
let parents getParents e
modify fun s => { s with parents := s.parents.erase (toENodeKey e) }
return parents
/--
Copy `parents` to the parents of `root`.
`root` must be the root of its equivalence class.
-/
def copyParentsTo (parents : ParentSet) (root : Expr) : GoalM Unit := do
let key := toENodeKey root
let mut curr := if let some parents := ( get).parents.find? key then parents else {}
for parent in parents do
curr := curr.insert parent
modify fun s => { s with parents := s.parents.insert key curr }
def setENode (e : Expr) (n : ENode) : GoalM Unit :=
modify fun s => { s with enodes := s.enodes.insert (unsafe ptrAddrUnsafe e) n }
modify fun s => { s with
enodes := s.enodes.insert (unsafe ptrAddrUnsafe e) n
congrTable := unsafe unsafeCast s.congrTable
}
def mkENodeCore (e : Expr) (interpreted ctor : Bool) (generation : Nat) : GoalM Unit := do
setENode e {
@@ -232,11 +438,123 @@ def mkENodeCore (e : Expr) (interpreted ctor : Bool) (generation : Nat) : GoalM
}
modify fun s => { s with nextIdx := s.nextIdx + 1 }
def mkGoal (mvarId : MVarId) : GrindM Goal := do
let trueExpr getTrueExpr
let falseExpr getFalseExpr
GoalM.run' { mvarId } do
mkENodeCore falseExpr (interpreted := true) (ctor := false) (generation := 0)
mkENodeCore trueExpr (interpreted := true) (ctor := false) (generation := 0)
/--
Creates an `ENode` for `e` if one does not already exist.
This method assumes `e` has been hashconsed.
-/
def mkENode (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
let ctor := ( isConstructorAppCore? e).isSome
let interpreted isInterpreted e
mkENodeCore e interpreted ctor generation
/-- Return `true` if the goal is inconsistent. -/
def isInconsistent : GoalM Bool :=
return ( get).inconsistent
/--
Returns a proof that `a = b` (or `HEq a b`).
It assumes `a` and `b` are in the same equivalence class.
-/
-- Forward definition
@[extern "lean_grind_mk_eq_proof"]
opaque mkEqProof (a b : Expr) : GoalM Expr
/--
Returns a proof that `a = True`.
It assumes `a` and `True` are in the same equivalence class.
-/
def mkEqTrueProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getTrueExpr)
/--
Returns a proof that `a = False`.
It assumes `a` and `False` are in the same equivalence class.
-/
def mkEqFalseProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getFalseExpr)
/-- Marks current goal as inconsistent without assigning `mvarId`. -/
def markAsInconsistent : GoalM Unit := do
modify fun s => { s with inconsistent := true }
/--
Closes the current goal using the given proof of `False` and
marks it as inconsistent if it is not already marked so.
-/
def closeGoal (falseProof : Expr) : GoalM Unit := do
markAsInconsistent
let mvarId := ( get).mvarId
unless ( mvarId.isAssigned) do
let target mvarId.getType
if target.isFalse then
mvarId.assign falseProof
else
mvarId.assign ( mkFalseElim target falseProof)
/-- Returns all enodes in the goal -/
def getENodes : GoalM (Array ENode) := do
-- We must sort because we are using pointer addresses as keys in `enodes`
let nodes := ( get).enodes.toArray.map (·.2)
return nodes.qsort fun a b => a.idx < b.idx
def forEachENode (f : ENode GoalM Unit) : GoalM Unit := do
let nodes getENodes
for n in nodes do
f n
def filterENodes (p : ENode GoalM Bool) : GoalM (Array ENode) := do
let ref IO.mkRef #[]
forEachENode fun n => do
if ( p n) then
ref.modify (·.push n)
ref.get
def forEachEqc (f : ENode GoalM Unit) : GoalM Unit := do
let nodes getENodes
for n in nodes do
if isSameExpr n.self n.root then
f n
structure Methods where
propagateUp : Propagator := fun _ => return ()
propagateDown : Propagator := fun _ => return ()
deriving Inhabited
def Methods.toMethodsRef (m : Methods) : MethodsRef :=
unsafe unsafeCast m
private def MethodsRef.toMethods (m : MethodsRef) : Methods :=
unsafe unsafeCast m
@[inline] def getMethods : GrindM Methods :=
return ( getMethodsRef).toMethods
def propagateUp (e : Expr) : GoalM Unit := do
( getMethods).propagateUp e
def propagateDown (e : Expr) : GoalM Unit := do
( getMethods).propagateDown e
/-- Returns expressions in the given expression equivalence class. -/
partial def getEqc (e : Expr) : GoalM (List Expr) :=
go e e []
where
go (first : Expr) (e : Expr) (acc : List Expr) : GoalM (List Expr) := do
let next getNext e
let acc := e :: acc
if isSameExpr first next then
return acc
else
go first next acc
/-- Returns all equivalence classes in the current goal. -/
partial def getEqcs : GoalM (List (List Expr)) := do
let mut r := []
let nodes getENodes
for node in nodes do
if isSameExpr node.root node.self then
r := ( getEqc node.self) :: r
return r
end Lean.Meta.Grind

View File

@@ -124,4 +124,15 @@ def foldProjs (e : Expr) : MetaM Expr := do
return .done e
Meta.transform e (post := post)
/--
Normalizes universe levels in constants and sorts.
-/
def normalizeLevels (e : Expr) : CoreM Expr := do
let pre (e : Expr) := do
match e with
| .sort u => return .done <| e.updateSort! u.normalize
| .const _ us => return .done <| e.updateConst! (us.map Level.normalize)
| _ => return .continue
Core.transform e (pre := pre)
end Lean.Meta.Grind

View File

@@ -49,17 +49,13 @@ If they do, they must disable the following `simprocs`.
-/
builtin_dsimproc [simp, seval] reduceNeg ((- _ : Int)) := fun e => do
unless e.isAppOfArity ``Neg.neg 3 do return .continue
let arg := e.appArg!
let_expr Neg.neg _ _ arg e | return .continue
if arg.isAppOfArity ``OfNat.ofNat 3 then
-- We return .done to ensure `Neg.neg` is not unfolded even when `ground := true`.
return .done e
else
let some v fromExpr? arg | return .continue
if v < 0 then
return .done <| toExpr (- v)
else
return .done <| toExpr v
return .done <| toExpr (- v)
/-- Return `.done` for positive Int values. We don't want to unfold in the symbolic evaluator. -/
builtin_dsimproc [seval] isPosValue ((OfNat.ofNat _ : Int)) := fun e => do

View File

@@ -47,6 +47,17 @@ def isOfScientificLit (e : Expr) : Bool :=
def isCharLit (e : Expr) : Bool :=
e.isAppOfArity ``Char.ofNat 1 && e.appArg!.isRawNatLit
/--
Unfold definition even if it is not marked as `@[reducible]`.
Remark: We never unfold irreducible definitions. Mathlib relies on that in the implementation of the
command `irreducible_def`.
-/
private def unfoldDefinitionAny? (e : Expr) : MetaM (Option Expr) := do
if let .const declName _ := e.getAppFn then
if ( isIrreducible declName) then
return none
unfoldDefinition? e (ignoreTransparency := true)
private def reduceProjFn? (e : Expr) : SimpM (Option Expr) := do
matchConst e.getAppFn (fun _ => pure none) fun cinfo _ => do
match ( getProjectionFnInfo? cinfo.name) with
@@ -83,7 +94,7 @@ private def reduceProjFn? (e : Expr) : SimpM (Option Expr) := do
let major := e.getArg! projInfo.numParams
unless ( isConstructorApp major) do
return none
reduceProjCont? ( withDefault <| unfoldDefinition? e)
reduceProjCont? ( unfoldDefinitionAny? e)
else
-- `structure` projections
reduceProjCont? ( unfoldDefinition? e)
@@ -133,7 +144,7 @@ private def unfold? (e : Expr) : SimpM (Option Expr) := do
if cfg.unfoldPartialApp -- If we are unfolding partial applications, ignore issue #2042
-- When smart unfolding is enabled, and `f` supports it, we don't need to worry about issue #2042
|| (smartUnfolding.get options && ( getEnv).contains (mkSmartUnfoldingNameFor fName)) then
withDefault <| unfoldDefinition? e
unfoldDefinitionAny? e
else
-- `We are not unfolding partial applications, and `fName` does not have smart unfolding support.
-- Thus, we must check whether the arity of the function >= number of arguments.
@@ -142,16 +153,16 @@ private def unfold? (e : Expr) : SimpM (Option Expr) := do
let arity := value.getNumHeadLambdas
-- Partially applied function, return `none`. See issue #2042
if arity > e.getAppNumArgs then return none
withDefault <| unfoldDefinition? e
unfoldDefinitionAny? e
if ( isProjectionFn fName) then
return none -- should be reduced by `reduceProjFn?`
else if ctx.config.autoUnfold then
if ctx.simpTheorems.isErased (.decl fName) then
return none
else if hasSmartUnfoldingDecl ( getEnv) fName then
withDefault <| unfoldDefinition? e
unfoldDefinitionAny? e
else if ( isMatchDef fName) then
let some value withDefault <| unfoldDefinition? e | return none
let some value unfoldDefinitionAny? e | return none
let .reduced value withSimpMetaConfig <| reduceMatcher? value | return none
return some value
else

View File

@@ -64,10 +64,38 @@ def isAuxDef (constName : Name) : MetaM Bool := do
let env getEnv
return isAuxRecursor env constName || isNoConfusion env constName
@[inline] private def matchConstAux {α} (e : Expr) (failK : Unit MetaM α) (k : ConstantInfo List Level MetaM α) : MetaM α := do
let .const name lvls := e
/--
Retrieves `ConstInfo` for `declName`.
Remark: if `ignoreTransparency = false`, then `getUnfoldableConst?` is used.
For example, if `ignoreTransparency = false` and `transparencyMode = .reducible` and `declName` is not reducible,
then the result is `none`.
-/
private def getConstInfo? (declName : Name) (ignoreTransparency : Bool) : MetaM (Option ConstantInfo) := do
if ignoreTransparency then
return ( getEnv).find? declName
else
getUnfoldableConst? declName
/--
Similar to `getConstInfo?` but using `getUnfoldableConstNoEx?`.
-/
private def getConstInfoNoEx? (declName : Name) (ignoreTransparency : Bool) : MetaM (Option ConstantInfo) := do
if ignoreTransparency then
return ( getEnv).find? declName
else
getUnfoldableConstNoEx? declName
/--
If `e` is of the form `Expr.const declName us`, executes `k info us` if
- `declName` is in the `Environment` and (is unfoldable or `ignoreTransparency = true`)
- `info` is the `ConstantInfo` associated with `declName`.
Otherwise executes `failK`.
-/
@[inline] private def matchConstAux {α} (e : Expr) (failK : Unit MetaM α) (k : ConstantInfo List Level MetaM α) (ignoreTransparency := false) : MetaM α := do
let .const declName lvls := e
| failK ()
let (some cinfo) getUnfoldableConst? name
let some cinfo getConstInfo? declName ignoreTransparency
| failK ()
k cinfo lvls
@@ -713,11 +741,14 @@ mutual
else
unfoldProjInst? e
/-- Unfold definition using "smart unfolding" if possible. -/
partial def unfoldDefinition? (e : Expr) : MetaM (Option Expr) :=
/--
Unfold definition using "smart unfolding" if possible.
If `ignoreTransparency = true`, then the definition is unfolded even if the transparency setting does not allow it.
-/
partial def unfoldDefinition? (e : Expr) (ignoreTransparency := false) : MetaM (Option Expr) :=
match e with
| .app f _ =>
matchConstAux f.getAppFn (fun _ => unfoldProjInstWhenInstances? e) fun fInfo fLvls => do
matchConstAux (ignoreTransparency := ignoreTransparency) f.getAppFn (fun _ => unfoldProjInstWhenInstances? e) fun fInfo fLvls => do
if fInfo.levelParams.length != fLvls.length then
return none
else
@@ -756,7 +787,8 @@ mutual
Remark 2: the match expression reduces reduces to `cons a xs` when the discriminants are `⟨0, h⟩` and `xs`.
Remark 3: this check is unnecessary in most cases, but we don't need dependent elimination to trigger the issue fixed by this extra check. Here is another example that triggers the issue fixed by this check.
Remark 3: this check is unnecessary in most cases, but we don't need dependent elimination to trigger the issue
fixed by this extra check. Here is another example that triggers the issue fixed by this check.
```
def f : Nat → Nat → Nat
| 0, y => y
@@ -788,7 +820,7 @@ mutual
else
unfoldDefault ()
| .const declName lvls => do
let some cinfo getUnfoldableConstNoEx? declName | pure none
let some cinfo getConstInfoNoEx? declName ignoreTransparency | pure none
-- check smart unfolding only after `getUnfoldableConstNoEx?` because smart unfoldings have a
-- significant chance of not existing and `Environment.contains` misses are more costly
if smartUnfolding.get ( getOptions) && ( getEnv).contains (mkSmartUnfoldingNameFor declName) then

View File

@@ -34,3 +34,4 @@ import Lean.Util.SearchPath
import Lean.Util.SafeExponentiation
import Lean.Util.NumObjs
import Lean.Util.NumApps
import Lean.Util.FVarSubset

View File

@@ -26,14 +26,14 @@ mutual
else main e { s with visitedExpr := s.visitedExpr.insert e }
partial def main : Expr Visitor
| Expr.proj _ _ e => visit e
| Expr.forallE _ d b _ => visit b visit d
| Expr.lam _ d b _ => visit b visit d
| Expr.letE _ t v b _ => visit b visit v visit t
| Expr.app f a => visit a visit f
| Expr.mdata _ b => visit b
| Expr.fvar fvarId => fun s => s.add fvarId
| _ => id
| .proj _ _ e => visit e
| .forallE _ d b _ => visit b visit d
| .lam _ d b _ => visit b visit d
| .letE _ t v b _ => visit b visit v visit t
| .app f a => visit a visit f
| .mdata _ b => visit b
| .fvar fvarId => fun s => s.add fvarId
| _ => id
end
end CollectFVars

View File

@@ -0,0 +1,25 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Util.CollectFVars
import Lean.Util.FindExpr
namespace Lean.Expr
/-- Returns true if the free variables in `a` are subset of the free variables in `b`. -/
def fvarsSubset (a b : Expr) : Bool :=
if !a.hasFVar then
true -- Empty set is subset of anything
else if !b.hasFVar then
false -- Empty set is not a superset of a nonempty set.
else
let s := collectFVars {} b
Option.isNone <| a.findExt? fun e =>
if !e.hasFVar then .done
else if e.isFVar && !s.fvarSet.contains e.fvarId! then .found
else .visit
end Lean.Expr

View File

@@ -14,25 +14,24 @@ opaque findImpl? (p : @& (Expr → Bool)) (e : @& Expr) : Option Expr
@[inline] def find? (p : Expr Bool) (e : Expr) : Option Expr := findImpl? p e
/-- Return true if `e` occurs in `t` -/
/-- Returns true if `e` occurs in `t` -/
def occurs (e : Expr) (t : Expr) : Bool :=
(t.find? fun s => s == e).isSome
/--
Return type for `findExt?` function argument.
Returns type for `findExt?` function argument.
-/
inductive FindStep where
/-- Found desired subterm -/ | found
/-- Search subterms -/ | visit
/-- Do not search subterms -/ | done
| /-- Found desired subterm -/ found
| /-- Search subterms -/ visit
| /-- Do not search subterms -/ done
@[extern "lean_find_ext_expr"]
opaque findExtImpl? (p : @& (Expr FindStep)) (e : @& Expr) : Option Expr
/--
Similar to `find?`, but `p` can return `FindStep.done` to interrupt the search on subterms.
Remark: Differently from `find?`, we do not invoke `p` for partial applications of an application. -/
Similar to `find?`, but `p` can return `FindStep.done` to interrupt the search on subterms.
Remark: Differently from `find?`, we do not invoke `p` for partial applications of an application. -/
@[inline] def findExt? (p : Expr FindStep) (e : Expr) : Option Expr := findExtImpl? p e
end Expr
end Lean
end Lean.Expr

View File

@@ -20,13 +20,13 @@ def visit (g : Expr → m Bool) (e : Expr) : MonadCacheT Expr Unit m Unit :=
checkCache e fun _ => do
if ( g e) then
match e with
| Expr.forallE _ d b _ => do visit g d; visit g b
| Expr.lam _ d b _ => do visit g d; visit g b
| Expr.letE _ t v b _ => do visit g t; visit g v; visit g b
| Expr.app f a => do visit g f; visit g a
| Expr.mdata _ b => visit g b
| Expr.proj _ _ b => visit g b
| _ => pure ()
| .forallE _ d b _ => do visit g d; visit g b
| .lam _ d b _ => do visit g d; visit g b
| .letE _ t v b _ => do visit g t; visit g v; visit g b
| .app f a => do visit g f; visit g a
| .mdata _ b => visit g b
| .proj _ _ b => visit g b
| _ => pure ()
end ForEachExpr

View File

@@ -293,8 +293,15 @@ The semantics for `BVExpr`.
-/
def eval (assign : Assignment) : BVExpr w BitVec w
| .var idx =>
let bv := assign.get idx
bv.truncate w
let packedBv := assign.get idx
/-
This formulation improves performance, as in a well formed expression the condition always holds
so there is no need for the more involved `BitVec.truncate` logic.
-/
if h : packedBv.w = w then
h packedBv.bv
else
packedBv.bv.truncate w
| .const val => val
| .zeroExtend v expr => BitVec.zeroExtend v (eval assign expr)
| .extract start len expr => BitVec.extractLsb' start len (eval assign expr)
@@ -308,8 +315,13 @@ def eval (assign : Assignment) : BVExpr w → BitVec w
| .arithShiftRight lhs rhs => BitVec.sshiftRight' (eval assign lhs) (eval assign rhs)
@[simp]
theorem eval_var : eval assign ((.var idx) : BVExpr w) = (assign.get idx).bv.truncate _ := by
rfl
theorem eval_var : eval assign ((.var idx) : BVExpr w) = (assign.get idx).bv.truncate w := by
rw [eval]
split
· next h =>
subst h
simp
· rfl
@[simp]
theorem eval_const : eval assign (.const val) = val := by rfl
@@ -454,7 +466,7 @@ def eval (assign : BVExpr.Assignment) (expr : BVLogicalExpr) : Bool :=
@[simp] theorem eval_not : eval assign (.not x) = !eval assign x := rfl
@[simp] theorem eval_gate : eval assign (.gate g x y) = g.eval (eval assign x) (eval assign y) := rfl
@[simp] theorem eval_ite :
eval assign (.ite d l r) = if (eval assign d) then (eval assign l) else (eval assign r) := rfl
eval assign (.ite d l r) = bif (eval assign d) then (eval assign l) else (eval assign r) := rfl
def Sat (x : BVLogicalExpr) (assign : BVExpr.Assignment) : Prop := eval assign x = true

View File

@@ -18,7 +18,7 @@ inductive Gate
| and
| xor
| beq
| imp
| or
namespace Gate
@@ -26,13 +26,13 @@ def toString : Gate → String
| and => "&&"
| xor => "^^"
| beq => "=="
| imp => "->"
| or => "||"
def eval : Gate Bool Bool Bool
| and => (· && ·)
| xor => (· ^^ ·)
| beq => (· == ·)
| imp => (· ·)
| or => (· || ·)
end Gate
@@ -59,13 +59,13 @@ def eval (a : α → Bool) : BoolExpr α → Bool
| .const b => b
| .not x => !eval a x
| .gate g x y => g.eval (eval a x) (eval a y)
| .ite d l r => if d.eval a then l.eval a else r.eval a
| .ite d l r => bif d.eval a then l.eval a else r.eval a
@[simp] theorem eval_literal : eval a (.literal l) = a l := rfl
@[simp] theorem eval_const : eval a (.const b) = b := rfl
@[simp] theorem eval_not : eval a (.not x) = !eval a x := rfl
@[simp] theorem eval_gate : eval a (.gate g x y) = g.eval (eval a x) (eval a y) := rfl
@[simp] theorem eval_ite : eval a (.ite d l r) = if d.eval a then l.eval a else r.eval a := rfl
@[simp] theorem eval_ite : eval a (.ite d l r) = bif d.eval a then l.eval a else r.eval a := rfl
def Sat (a : α Bool) (x : BoolExpr α) : Prop := eval a x = true
def Unsat (x : BoolExpr α) : Prop := f, eval f x = false

View File

@@ -75,9 +75,9 @@ where
let ret := aig.mkBEqCached input
have := LawfulOperator.le_size (f := mkBEqCached) aig input
ret, by dsimp only [ret] at lextend rextend ; omega
| .imp =>
let ret := aig.mkImpCached input
have := LawfulOperator.le_size (f := mkImpCached) aig input
| .or =>
let ret := aig.mkOrCached input
have := LawfulOperator.le_size (f := mkOrCached) aig input
ret, by dsimp only [ret] at lextend rextend ; omega
namespace ofBoolExprCached
@@ -127,9 +127,9 @@ theorem go_decl_eq (idx) (aig : AIG β) (h : idx < aig.decls.size) (hbounds) :
| beq =>
simp only [go]
rw [AIG.LawfulOperator.decl_eq (f := mkBEqCached), rih, lih]
| imp =>
| or =>
simp only [go]
rw [AIG.LawfulOperator.decl_eq (f := mkImpCached), rih, lih]
rw [AIG.LawfulOperator.decl_eq (f := mkOrCached), rih, lih]
theorem go_isPrefix_aig {aig : AIG β} :
IsPrefix aig.decls (go aig expr atomHandler).val.aig.decls := by

View File

@@ -118,7 +118,6 @@ theorem BitVec.srem_umod (x y : BitVec w) :
rw [BitVec.srem_eq]
cases x.msb <;> cases y.msb <;> simp
attribute [bv_normalize] Bool.cond_eq_if
attribute [bv_normalize] BitVec.abs_eq
attribute [bv_normalize] BitVec.twoPow_eq

View File

@@ -41,7 +41,14 @@ attribute [bv_normalize] Bool.not_not
attribute [bv_normalize] Bool.and_self_left
attribute [bv_normalize] Bool.and_self_right
attribute [bv_normalize] eq_self
attribute [bv_normalize] ite_self
attribute [bv_normalize] Bool.cond_self
attribute [bv_normalize] cond_false
attribute [bv_normalize] cond_true
attribute [bv_normalize] Bool.cond_not
@[bv_normalize]
theorem if_eq_cond {b : Bool} {x y : α} : (if b = true then x else y) = (bif b then x else y) := by
rw [cond_eq_if]
@[bv_normalize]
theorem Bool.not_xor : (a b : Bool), !(a ^^ b) = (a == b) := by decide

View File

@@ -13,8 +13,6 @@ This module contains the `Prop` simplifying part of the `bv_normalize` simp set.
namespace Std.Tactic.BVDecide
namespace Frontend.Normalize
attribute [bv_normalize] ite_true
attribute [bv_normalize] ite_false
attribute [bv_normalize] dite_true
attribute [bv_normalize] dite_false
attribute [bv_normalize] and_true

View File

@@ -123,12 +123,12 @@ theorem umod_congr (lhs rhs lhs' rhs' : BitVec w) (h1 : lhs' = lhs) (h2 : rhs' =
(lhs' % rhs') = (lhs % rhs) := by
simp[*]
theorem if_true (discr : Bool) (lhs rhs : BitVec w) :
decide ((discr == true) = true ((if discr = true then lhs else rhs) == lhs) = true) = true := by
theorem cond_true (discr : Bool) (lhs rhs : BitVec w) :
(!discr || ((bif discr then lhs else rhs) == lhs)) = true := by
cases discr <;> simp
theorem if_false (discr : Bool) (lhs rhs : BitVec w) :
decide ((discr == false) = true ((if discr = true then lhs else rhs) == rhs) = true) = true := by
theorem cond_false (discr : Bool) (lhs rhs : BitVec w) :
(discr || ((bif discr then lhs else rhs) == rhs)) = true := by
cases discr <;> simp
end BitVec
@@ -150,13 +150,13 @@ theorem beq_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs)
(lhs' == rhs') = (lhs == rhs) := by
simp[*]
theorem imp_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs) :
(decide (lhs' rhs')) = (decide (lhs rhs)) := by
theorem or_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs) :
(lhs' || rhs') = (lhs || rhs) := by
simp[*]
theorem ite_congr (discr lhs rhs discr' lhs' rhs' : Bool) (h1 : discr' = discr) (h2 : lhs' = lhs)
theorem cond_congr (discr lhs rhs discr' lhs' rhs' : Bool) (h1 : discr' = discr) (h2 : lhs' = lhs)
(h3 : rhs' = rhs) :
(if discr' = true then lhs' else rhs') = (if discr = true then lhs else rhs) := by
(bif discr' = true then lhs' else rhs') = (bif discr = true then lhs else rhs) := by
simp[*]
theorem false_of_eq_true_of_eq_false (h₁ : x = true) (h₂ : x = false) : False := by

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
stage0/stdlib/Init/Data/Array/Lex/Basic.c generated Normal file

Binary file not shown.

BIN
stage0/stdlib/Init/Data/Array/Lex/Lemmas.c generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
stage0/stdlib/Init/Data/List/Lex.c generated Normal file

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More