Skip to content

Commit c155094

Browse files
tydeuclaude
andauthored
feat: lake: precompileLibrary & precompileImports (#15015)
This PR adds two new configuration options that are subsets of `precompileModules`: `precompileLibrary` for `lean_lib` targets and `precompileImports` for all Lean configurations (e.g., settable on `package`, `lean_lib`, or `lean_exe`). `precompileImports` compiles a module's imports but not the module itself. `precompileLibrary` compiles the whole library for importers, but the library's modules do not compile their own imports during elaboration. Closes #2757 with the finer-grain `precompileLibrary` instead of the more general `precompilePackage`. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 5549307 commit c155094

15 files changed

Lines changed: 120 additions & 19 deletions

File tree

src/lake/Lake/Build/Module.lean

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,8 @@ def computePrecompileImportsAux
111111
(fileName : String) (imports : Array Module)
112112
: FetchM (Job (Array Module)) := do
113113
collectImportsAux fileName imports fun imp =>
114-
if imp.shouldPrecompile then
115-
(true, ·) <$> imp.transImports.fetch
116-
else
117-
(false, ·) <$> imp.precompileImports.fetch
114+
-- `imp.shouldPrecompile` implies `imp.lib.shouldPrecompile`
115+
(imp.lib.shouldPrecompile, ·) <$> imp.precompileImports.fetch
118116

119117
/-- Recursively compute a module's precompiled imports. -/
120118
def Module.recComputePrecompileImports (mod : Module) : FetchM (Job (Array Module)) := ensureJob do
@@ -130,15 +128,20 @@ Modules from the same library are loaded individually, while modules
130128
from other libraries are loaded as part of the whole library.
131129
-/
132130
def Module.fetchImportLibs
133-
(self : Module) (imps : Array Module) (compileSelf : Bool)
131+
(self : Module) (imps : Array Module)
132+
(precompileModules : Bool) (precompileImports : Bool)
134133
: FetchM (Array (Job Dynlib)) := do
135134
let (_, jobs) ← imps.foldlM (init := (({} : NameSet), #[])) fun (libs, jobs) imp => do
136135
if libs.contains imp.lib.name then
137136
return (libs, jobs)
138-
else if compileSelf && self.lib.name = imp.lib.name then
139-
let job ← imp.dynlib.fetch
140-
return (libs, jobs.push job)
141-
else if compileSelf || imp.shouldPrecompile then
137+
else if self.lib.name = imp.lib.name then
138+
-- The library as a whole cannot be loaded here, as it includes the module itself.
139+
if precompileModules then
140+
let job ← imp.dynlib.fetch
141+
return (libs, jobs.push job)
142+
else
143+
return (libs, jobs)
144+
else if precompileImports || imp.lib.shouldPrecompile then
142145
let jobs ← jobs.push <$> imp.lib.shared.fetch
143146
return (libs.insert imp.lib.name, jobs)
144147
else
@@ -155,7 +158,7 @@ def fetchImportLibs
155158
let (_, jobs) ← mods.foldlM (init := (({} : NameSet), #[])) fun (libs, jobs) imp => do
156159
if libs.contains imp.lib.name then
157160
return (libs, jobs)
158-
else if imp.shouldPrecompile then
161+
else if imp.lib.shouldPrecompile then
159162
let jobs ← jobs.push <$> imp.lib.shared.fetch
160163
return (libs.insert imp.lib.name, jobs)
161164
else
@@ -577,14 +580,14 @@ def Module.recFetchPreSetup (mod : Module) : FetchM (Job ModulePreSetup) := ensu
577580
Remark: It should be possible to avoid transitive imports here when the module
578581
itself is precompiled, but they are currently kept to preserve the "bad import" errors.
579582
-/
580-
let precompileImports ← if mod.shouldPrecompile then
583+
let precompileImports ← if mod.shouldPrecompileImports then
581584
mod.transImports.fetch else mod.precompileImports.fetch
582585
let precompileImports ← precompileImports.await
583586
let impLibsJob ← Job.collectArray (traceCaption := "import dynlibs") <$>
584-
mod.fetchImportLibs precompileImports mod.shouldPrecompile
587+
mod.fetchImportLibs precompileImports mod.shouldPrecompile mod.shouldPrecompileImports
585588

586589
let externLibsJob ← Job.collectArray (traceCaption := "package external libraries") <$>
587-
if mod.shouldPrecompile then mod.pkg.externLibs.mapM (·.dynlib.fetch) else pure #[]
590+
if mod.shouldPrecompileImports then mod.pkg.externLibs.mapM (·.dynlib.fetch) else pure #[]
588591
let dynlibsJob ← mod.dynlibs.fetchIn mod.pkg "module dynlibs"
589592
let pluginsJob ← mod.plugins.fetchIn mod.pkg "module plugins"
590593

@@ -1360,7 +1363,7 @@ def Module.recBuildDynlib (mod : Module) : FetchM (Job Dynlib) :=
13601363
-- Fetch dependencies' dynlibs
13611364
let libJobs ← id do
13621365
let imps ← (← mod.imports.fetch).await
1363-
let libJobs ← mod.fetchImportLibs imps true
1366+
let libJobs ← mod.fetchImportLibs imps true true
13641367
let libJobs ← mod.lib.moreLinkLibs.foldlM
13651368
(·.push <$> ·.fetchIn mod.pkg) libJobs
13661369
let libJobs ← mod.pkg.externLibs.foldlM
@@ -1439,14 +1442,14 @@ def setupEditedModule
14391442
let impInfoJob ← fetchImportInfo fileName mod.pkg.keyName mod.name header
14401443
(allowNonModules := mod.allowNonModules)
14411444
let precompileImports ←
1442-
if mod.shouldPrecompile then
1445+
if mod.shouldPrecompileImports then
14431446
(← computeTransImportsAux fileName localImports).await
14441447
else
14451448
(← computePrecompileImportsAux fileName localImports).await
14461449
let impLibsJob ← Job.collectArray (traceCaption := "import dynlibs") <$>
1447-
mod.fetchImportLibs precompileImports mod.shouldPrecompile
1450+
mod.fetchImportLibs precompileImports mod.shouldPrecompile mod.shouldPrecompileImports
14481451
let externLibsJob ← Job.collectArray (traceCaption := "package external libraries") <$>
1449-
if mod.shouldPrecompile then mod.pkg.externLibs.mapM (·.dynlib.fetch) else pure #[]
1452+
if mod.shouldPrecompileImports then mod.pkg.externLibs.mapM (·.dynlib.fetch) else pure #[]
14501453
let dynlibsJob ← mod.dynlibs.fetchIn mod.pkg "module dynlibs"
14511454
let pluginsJob ← mod.plugins.fetchIn mod.pkg "module plugins"
14521455
extraDepJob.bindM (sync := true) fun _ => do

src/lake/Lake/Config/LeanConfig.lean

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ public configuration LeanConfig where
254254
and Lake will not catch it. Defaults to `none`.
255255
-/
256256
platformIndependent : Option Bool := none
257+
/--
258+
Whether to compile the imports of modules into native shared libraries that
259+
are loaded when the module is built. This speeds up evaluation of metaprograms
260+
and enables the interpreter to run functions marked `@[extern]`.
261+
262+
Defaults to `false`.
263+
-/
264+
precompileImports : Bool := false
257265
/-
258266
An array of dynamic library targets to load during the elaboration
259267
of a module (via `lean --load-dynlib`).

src/lake/Lake/Config/LeanLib.lean

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ Is true if either the package or the library have `precompileModules` set.
104104
@[inline] public def precompileModules (self : LeanLib) : Bool :=
105105
self.pkg.precompileModules || self.config.precompileModules
106106

107+
/--
108+
Whether to precompile the imports of the library's modules.
109+
Is true if either the package or the library have `precompileImports` set,
110+
or if the library's modules are precompiled.
111+
-/
112+
@[inline] public def precompileImports (self : LeanLib) : Bool :=
113+
self.precompileModules || self.pkg.precompileImports || self.config.precompileImports
114+
115+
/--
116+
Whether to precompile the library for importers.
117+
Is true if the library has `precompileLibrary` set or its modules are precompiled.
118+
-/
119+
@[inline] public def shouldPrecompile (self : LeanLib) : Bool :=
120+
self.precompileModules || self.config.precompileLibrary
121+
107122
/--
108123
Whether to the library's Lean code is platform-independent.
109124
Returns the library's `platformIndependent` configuration if non-`none`.

src/lake/Lake/Config/LeanLibConfig.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,23 @@ public configuration LeanLibConfig (name : Name) extends LeanConfig where
7272
-/
7373
extraDepTargets : Array Name := #[]
7474

75+
/--
76+
Whether to compile the library into a native shared library that is loaded
77+
whenever one of its modules is imported by a module outside the library.
78+
This speeds up evaluation of metaprograms and enables the interpreter to run
79+
functions marked `@[extern]`.
80+
81+
Defaults to `false`.
82+
-/
83+
precompileLibrary : Bool := false
84+
7585
/--
7686
Whether to compile each of the library's modules into a native shared library
7787
that is loaded whenever the module is imported. This speeds up evaluation of
7888
metaprograms and enables the interpreter to run functions marked `@[extern]`.
7989
90+
This implies `precompileImports` and `precompileLibrary`.
91+
8092
Defaults to `false`.
8193
-/
8294
precompileModules : Bool := false

src/lake/Lake/Config/Module.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ public def leanIncludeDir? (self : Module) : Option FilePath :=
217217
@[inline] public def platformIndependent (self : Module) : Option Bool :=
218218
self.lib.platformIndependent
219219

220+
@[inline] public def shouldPrecompileImports (self : Module) : Bool :=
221+
self.lib.precompileImports
222+
220223
@[inline] public def shouldPrecompile (self : Module) : Bool :=
221224
self.lib.precompileModules
222225

src/lake/Lake/Config/Package.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ public def id? (self : Package) : Option PkgId :=
276276
@[inline] public def precompileModules (self : Package) : Bool :=
277277
self.config.precompileModules
278278

279+
/-- The package's `precompileImports` configuration. -/
280+
@[inline] public def precompileImports (self : Package) : Bool :=
281+
self.config.precompileImports
282+
279283
/-- The package's `moreGlobalServerArgs` configuration. -/
280284
@[inline] public def moreGlobalServerArgs (self : Package) : Array String :=
281285
self.config.moreGlobalServerArgs

src/lake/schemas/lakefile-toml-schema.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@
132132
"type": "boolean",
133133
"description": "Asserts whether Lake should assume Lean modules are platform-independent.\n\n* If `false`, Lake will add `System.Platform.target` to the module traces within the code unit (e.g., package or library). This will force Lean code to be re-elaborated on different platforms.\n\n* If `true`, Lake will exclude platform-dependent elements (e.g., precompiled modules, external libraries) from a module's trace, preventing re-elaboration on different platforms. Note that this will not effect modules outside the code unit in question. For example, a platform-independent package which depends on a platform-dependent library will still be platform-dependent.\n\n* If not set, Lake will construct traces as natural. That is, it will include platform-dependent artifacts in the trace if they module depends on them, but otherwise not force modules to be platform-dependent.\n\nThere is no check for correctness here, so a configuration can lie and Lake will not catch it."
134134
},
135+
"precompileImports": {
136+
"type": "boolean",
137+
"default": false,
138+
"description": "Whether to compile the imports of modules into native shared libraries that are loaded when the module is built. This speeds up evaluation of metaprograms and enables the interpreter to run functions marked `@[extern]`."
139+
},
135140
"dynlibs": {
136141
"type": "array",
137142
"items": {
@@ -217,10 +222,15 @@
217222
"default": [],
218223
"description": "Build target identifiers of targets to build before the library's modules.\n\nFormat: `<target>[:<facet>]*`, where:\n- `<target>` is of the form `@<package>`, `@<package>/<targetName>`, +<module>` or `@<package>/+<module>`,\n- `<package>` may be empty to reference the default package,\n- `<facet>` is the name of a target facet."
219224
},
225+
"precompileLibrary": {
226+
"type": "boolean",
227+
"default": false,
228+
"description": "Whether to compile the library into a native shared library that is loaded whenever one of its modules is imported by a module outside the library. This speeds up evaluation of metaprograms and enables the interpreter to run functions marked `@[extern]`."
229+
},
220230
"precompileModules": {
221231
"type": "boolean",
222232
"default": false,
223-
"description": "Whether to compile each of the library's modules into a native shared library that is loaded whenever the module is imported. This speeds up evaluation of metaprograms and enables the interpreter to run functions marked `@[extern]`."
233+
"description": "Whether to compile each of the library's modules into a native shared library that is loaded whenever the module is imported. This speeds up evaluation of metaprograms and enables the interpreter to run functions marked `@[extern]`.\n\nThis implies `precompileImports` and `precompileLibrary`."
224234
},
225235
"defaultFacets": {
226236
"type": "array",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import Lib.Base
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import LibDep
2+
3+
builtin_initialize libGreetingRef : IO.Ref String ← IO.mkRef libGreeting
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def libGreeting := "Hello"

0 commit comments

Comments
 (0)