Skip to content

Commit 7a0e025

Browse files
kim-emParcly-TaxelRuben-VandeVeldeEmilieUthaiwateric-wieser
committed
feat: instant library_search (#3404)
Well, not quite instant, but very much faster. This works by creating an extra file in the `build` directory containing a cache of the `library_search` discrimination tree. * If you modify a file, then open another file, `library_search` will not see the modified declarations. * However after a `lake exe cache get` or a `lake env lean Extras/LibrarySearch.lean`, you will see the modifications. * In particular CI will automatically do the work of building this cache. * This will increase the size of a `lake exe cache get` download (by about 20mb). To accommodate the cache file, we create a new `build/extra/` directory, that can store `.olean` format cache files. Our `cache` executable now manages files in this directory too. Co-authored-by: Scott Morrison <scott.morrison@gmail.com> Co-authored-by: Jeremy Tan Jie Rui <e0191785@u.nus.edu> Co-authored-by: Pol_tta <65514131+Ruben-VandeVelde@users.noreply.github.com> Co-authored-by: EmilieUthaiwat <emiliepathum@gmail.com> Co-authored-by: Eric Wieser <wieser.eric@gmail.com> Co-authored-by: Yaël Dillies <yael.dillies@gmail.com> Co-authored-by: Jireh Loreaux <loreaujy@gmail.com> Co-authored-by: Gabriel Ebner <gebner@gebner.org> Co-authored-by: Violeta Hernández <vi.hdz.p@gmail.com>
1 parent 5865020 commit 7a0e025

File tree

13 files changed

+159
-31
lines changed

13 files changed

+159
-31
lines changed

.github/workflows/bors.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ jobs:
9595
- name: build mathlib
9696
run: env LEAN_ABORT_ON_PANIC=1 lake build
9797

98+
- name: build library_search cache
99+
run: lake build MathlibExtras
100+
98101
- name: upload cache
99102
if: always()
100103
run: |

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ jobs:
101101
- name: build mathlib
102102
run: env LEAN_ABORT_ON_PANIC=1 lake build
103103

104+
- name: build library_search cache
105+
run: lake build MathlibExtras
106+
104107
- name: upload cache
105108
if: always()
106109
run: |

.github/workflows/build.yml.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ jobs:
8181
- name: build mathlib
8282
run: env LEAN_ABORT_ON_PANIC=1 lake build
8383

84+
- name: build library_search cache
85+
run: lake build MathlibExtras
86+
8487
- name: upload cache
8588
if: always()
8689
run: |

.github/workflows/build_fork.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ jobs:
9999
- name: build mathlib
100100
run: env LEAN_ABORT_ON_PANIC=1 lake build
101101

102+
- name: build library_search cache
103+
run: lake build MathlibExtras
104+
102105
- name: upload cache
103106
if: always()
104107
run: |

Cache/Hashing.lean

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,11 @@ partial def getFileHash (filePath : FilePath) : HashM $ Option UInt64 := do
102102
cache := stt.cache.insert filePath (some fileHash)
103103
depsMap := stt.depsMap.insert filePath fileImports })
104104

105+
/-- Files to start hashing from. -/
106+
def roots : Array FilePath := #["Mathlib.lean", "MathlibExtras.lean"]
107+
105108
/-- Main API to retrieve the hashes of the Lean files -/
106109
def getHashMemo : IO HashMemo :=
107-
return (← StateT.run (getFileHash ⟨"Mathlib.lean") default).2
110+
return (← StateT.run (roots.mapM getFileHash) default).2
108111

109112
end Cache.Hashing

Cache/IO.lean

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ def getPackageDirs : IO PackageDirs := return .ofList [
7070
("Mathlib", if ← isMathlibRoot then "." else mathlibDepPath),
7171
("Aesop", LAKEPACKAGESDIR / "aesop"),
7272
("Std", LAKEPACKAGESDIR / "std"),
73-
("Qq", LAKEPACKAGESDIR / "Qq")
73+
("Qq", LAKEPACKAGESDIR / "Qq"),
74+
("MathlibExtras", ".")
7475
]
7576

7677
initialize pkgDirs : PackageDirs ← getPackageDirs
@@ -146,18 +147,23 @@ end HashMap
146147
def mkDir (path : FilePath) : IO Unit := do
147148
if !(← path.pathExists) then IO.FS.createDirAll path
148149

149-
/-- Given a path to a Lean file, concatenates the paths to its build files -/
150-
def mkBuildPaths (path : FilePath) : IO $ Array FilePath := do
150+
/--
151+
Given a path to a Lean file, concatenates the paths to its build files.
152+
Each build file also has a `Bool` indicating whether that file is required for caching to proceed.
153+
-/
154+
def mkBuildPaths (path : FilePath) : IO $ Array (FilePath × Bool) := do
151155
let packageDir ← getPackageDir path
152156
return #[
153-
packageDir / LIBDIR / path.withExtension "olean",
154-
packageDir / LIBDIR / path.withExtension "ilean",
155-
packageDir / LIBDIR / path.withExtension "trace",
156-
packageDir / IRDIR / path.withExtension "c"]
157-
158-
def allExist (paths : Array FilePath) : IO Bool := do
159-
for path in paths do
160-
if !(← path.pathExists) then return false
157+
(packageDir / LIBDIR / path.withExtension "olean", true),
158+
(packageDir / LIBDIR / path.withExtension "ilean", true),
159+
(packageDir / LIBDIR / path.withExtension "trace", true),
160+
(packageDir / IRDIR / path.withExtension "c", true),
161+
(packageDir / LIBDIR / path.withExtension "extra", false)]
162+
163+
/-- Check that all required build files exist. -/
164+
def allExist (paths : Array (FilePath × Bool)) : IO Bool := do
165+
for (path, required) in paths do
166+
if required && !(← path.pathExists) then return false
161167
pure true
162168

163169
/-- Compresses build files into the local cache and returns an array with the compressed files -/
@@ -172,7 +178,7 @@ def packCache (hashMap : HashMap) (overwrite : Bool) : IO $ Array String := do
172178
if ← allExist buildPaths then
173179
if (overwrite || !(← zipPath.pathExists)) then
174180
discard $ runCmd "tar" $ #["-I", "gzip -9", "-cf", zipPath.toString] ++
175-
(buildPaths.map toString)
181+
((← buildPaths.filterM (·.1.pathExists)) |>.map (·.1.toString))
176182
acc := acc.push zip
177183
return acc
178184

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,7 @@ import Mathlib.Util.Export
20232023
import Mathlib.Util.IncludeStr
20242024
import Mathlib.Util.LongNames
20252025
import Mathlib.Util.MemoFix
2026+
import Mathlib.Util.Pickle
20262027
import Mathlib.Util.Syntax
20272028
import Mathlib.Util.SynthesizeUsing
20282029
import Mathlib.Util.Tactic

Mathlib/Tactic/LibrarySearch.lean

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Gabriel Ebner, Scott Morrison
55
-/
66
import Mathlib.Tactic.TryThis
7+
import Mathlib.Util.Pickle
78
import Mathlib.Lean.Expr.Basic
89
import Mathlib.Lean.Meta.DiscrTree
910
import Mathlib.Tactic.Cache
@@ -46,23 +47,58 @@ inductive DeclMod
4647
| none | symm | mp | mpr
4748
deriving DecidableEq
4849

49-
initialize librarySearchLemmas : DeclCache (DiscrTree (Name × DeclMod) true) ←
50-
DeclCache.mk "librarySearch: init cache" {} fun name constInfo lemmas => do
51-
if constInfo.isUnsafe then return lemmas
52-
if ← name.isBlackListed then return lemmas
53-
withNewMCtxDepth do withReducible do
54-
let (_, _, type) ← forallMetaTelescopeReducing constInfo.type
55-
let keys ← DiscrTree.mkPath type
56-
let lemmas := lemmas.insertIfSpecific keys (name, .none)
57-
match type.getAppFnArgs with
58-
| (``Eq, #[_, lhs, rhs]) => do
59-
let keys_symm ← DiscrTree.mkPath (← mkEq rhs lhs)
60-
pure (lemmas.insertIfSpecific keys_symm (name, .symm))
61-
| (``Iff, #[lhs, rhs]) => do
62-
let keys_mp ← DiscrTree.mkPath rhs
63-
let keys_mpr ← DiscrTree.mkPath lhs
64-
pure <| (lemmas.insertIfSpecific keys_mp (name, .mp)).insertIfSpecific keys_mpr (name, .mpr)
65-
| _ => pure lemmas
50+
/-- Insert a lemma into the discrimination tree. -/
51+
def addLemma (name : Name) (constInfo : ConstantInfo)
52+
(lemmas : DiscrTree (Name × DeclMod) true) : MetaM (DiscrTree (Name × DeclMod) true) := do
53+
if constInfo.isUnsafe then return lemmas
54+
if ← name.isBlackListed then return lemmas
55+
withNewMCtxDepth do withReducible do
56+
let (_, _, type) ← forallMetaTelescopeReducing constInfo.type
57+
let keys ← DiscrTree.mkPath type
58+
let lemmas := lemmas.insertIfSpecific keys (name, .none)
59+
match type.getAppFnArgs with
60+
| (``Eq, #[_, lhs, rhs]) => do
61+
let keys_symm ← DiscrTree.mkPath (← mkEq rhs lhs)
62+
pure (lemmas.insertIfSpecific keys_symm (name, .symm))
63+
| (``Iff, #[lhs, rhs]) => do
64+
let keys_mp ← DiscrTree.mkPath rhs
65+
let keys_mpr ← DiscrTree.mkPath lhs
66+
pure <| (lemmas.insertIfSpecific keys_mp (name, .mp)).insertIfSpecific keys_mpr (name, .mpr)
67+
| _ => pure lemmas
68+
69+
/-- Construct the discrimination tree of all lemmas. -/
70+
def buildDiscrTree : IO (DeclCache (DiscrTree (Name × DeclMod) true)) :=
71+
DeclCache.mk "librarySearch: init cache" {} addLemma
72+
73+
open System (FilePath)
74+
75+
def cachePath : IO FilePath :=
76+
try
77+
return (← findOLean `MathlibExtras.LibrarySearch).withExtension "extra"
78+
catch _ =>
79+
return "build" / "lib" / "MathlibExtras" / "LibrarySearch.extra"
80+
81+
/--
82+
A structure that holds the cached discrimination tree,
83+
and possibly a pointer to a memory region, if we unpickled the tree from disk.
84+
-/
85+
structure CachedData where
86+
pointer? : Option CompactedRegion
87+
cache : DeclCache (DiscrTree (Name × DeclMod) true)
88+
deriving Nonempty
89+
90+
initialize cachedData : CachedData ← unsafe do
91+
let path ← cachePath
92+
if (← path.pathExists) then
93+
let (d, r) ← unpickle (DiscrTree (Name × DeclMod) true) path
94+
return ⟨r, (← Cache.mk (pure d), addLemma)⟩
95+
else
96+
return ⟨none, ← buildDiscrTree⟩
97+
98+
/--
99+
Retrieve the current current of lemmas.
100+
-/
101+
def librarySearchLemmas : DeclCache (DiscrTree (Name × DeclMod) true) := cachedData.cache
66102

67103
/-- Shortcut for calling `solveByElim`. -/
68104
def solveByElim (goals : List MVarId) (required : List Expr) (depth) := do

Mathlib/Util/Pickle.lean

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/-
2+
Copyright (c) 2023 Mario Carneiro. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Mario Carneiro
5+
-/
6+
import Std.Util.TermUnsafe
7+
8+
/-!
9+
# Pickling and unpickling objects
10+
11+
By abusing `saveModuleData` and `readModuleData` we can pickle and unpickle objects to disk.
12+
-/
13+
14+
open Lean System
15+
16+
/--
17+
Save an object to disk.
18+
If you need to write multiple objects from within a single declaration,
19+
you will need to provide a unique `key` for each.
20+
-/
21+
def pickle {α : Type} (path : FilePath) (x : α) (key : Name := by exact decl_name%) : IO Unit :=
22+
saveModuleData path key (unsafe unsafeCast x)
23+
24+
/--
25+
Load an object from disk.
26+
Note: The returned `CompactedRegion` can be used to free the memory behind the value
27+
of type `α`, using `CompactedRegion.free` (which is only safe once all references to the `α` are
28+
released). Ignoring the `CompactedRegion` results in the data being leaked.
29+
Use `withUnpickle` to call `CompactedRegion.free` automatically.
30+
31+
This function is unsafe because the data being loaded may not actually have type `α`, and this
32+
may cause crashes or other bad behavior.
33+
-/
34+
unsafe def unpickle (α : Type) (path : FilePath) : IO (α × CompactedRegion) := do
35+
let (x, region) ← readModuleData path
36+
pure (unsafeCast x, region)
37+
38+
/-- Load an object from disk and run some continuation on it, freeing memory afterwards. -/
39+
unsafe def withUnpickle [Monad m] [MonadLiftT IO m] {α β : Type}
40+
(path : FilePath) (f : α → m β) : m β := do
41+
let (x, region) ← unpickle α path
42+
let r ← f x
43+
region.free
44+
pure r

MathlibExtras.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import MathlibExtras.LibrarySearch

0 commit comments

Comments
 (0)