-
Notifications
You must be signed in to change notification settings - Fork 19
Conversation
05fd2f7
to
ce3472f
Compare
Fun facts: on Linux, linking against a library as in |
Thinking about this again, this could be an issue if the server and Lake are run from different directories because of a non-standard |
@Kha On a more general note, it doesn't really make sense allow precompiling modules for olean packages (i.e.,, |
Would that extend to the respective facets? Meaning, building the oleans facet would not use precompilation even if explicitly opted in by the user? I would find that counterintuitive. Again, precompilation to me is an internal optimization independent of what is requested as the actual build result. |
I don't see it this way. Precompilation it not simply an optimization, it has significant effects on how a Lean module is interpreted --
The package facet is intended to indicate exactly what outputs are to be built for modules. The |
What's the solution then? Do we need a new facet just for precompilation? |
Will I need a different syntax for compiling a single module from mathlib4 than from other projects? I'm afraid this seems pointlessly user-hostile to me. |
Adding a module facet for a module's precompiled library would be a nice future addition (so that it can be built explicitly through the CLI). However, rolling it into the olean & c facet, as you have already done, is sufficient for now. Also, with this change #46, the more logical split for package (library) facets would be a pure module build (i.e., just the cross-platform module olean and ilean files) vs native module build (i.e., cross-platform olean/ilean plus native c/o/so) to replace the current olean/olean&c split.
No, all these changes are primarily internal, they don't significantly effect the API/UI. Even the additional facet would just provide finer control. It wouldn't make standard compilation any different. |
let mut env := #[("LEAN_PATH", some oleanPath.toString)] | ||
if !precompiledPath.isEmpty && System.Platform.isWindows then | ||
let path := (← IO.getEnv "PATH").getD "" | ||
-- set up search path for locating dependent DLLs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably this shouldn't be just precompiledPath
, but soPath
and also include DLL directories from dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by soPath
? I haven't looked into package dependencies yet, true. Frankly I'm fine for now if it works on mathlib4 😄 (esp. if it works well enough so that we can benchmark and detect important bottlenecks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kha I'm actually a little confused about how this works, why do you even need to change the PATH
? Why is passing the full path to --load-dynlib
not sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The full path works, but it's that library's dependencies that are searched by name in PATH. We only pass the direct dependencies' libraries to lean
, which ensures the cmdline length is reasonably bounded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kha Why would command line length be a concern? If passing the entire dependency closure works, that seems like a better solution to me than altering PATH
. Furthermore, from the code in recBuildModulePrecompTargetWithLocalImports
, it looks like that such an approach already necessary on Windows, so keeping it consistent across platforms seems like another benefit.
Note: I am not asking you change the PR, I just want to verify that such a solution is feasible for when I implement this feature myself (hopefully) this week,
EDIT: Using the transitive closure would also solve the problem for package dependencies as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Command line lengths are always a concern for practically unbounded input. For the linker we can always use response files, which we have yet to invent for Lean. But it will probably take a while for dependency trees to get large enough for this to blow up, so passing the entire dependency tree sounds good to me.
I don't understand the "consistent across platforms" comment, other platforms should not unnecessarily be burdened with Windows limitations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kha Maybe this is due to some experience I lack, but I still don't see why lengths are a problem. Is there some major performance or security issue with lengths that go even into the multiples of MBs range? It seems like that still wouldn't be a major burden to memory or parse time (though I can see why one might not want to log it).
For the cross-platform code, sure, special casing an operating system when there is clear advantage to for doing so makes sense. However, if multiple approaches are reasonable and one is cross-platform, I would go with it. I think simpler code is a virtue and, if it doesn't have a significant cost, should be preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I understand the confusion! Command line lengths are unfortunately quite limited in practice: https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553. But I think we should fall under the 32K case, so we should be good for a good while. Still, people (especially people with unusual build setups like in Nixpkgs and GHC that create an unusual number of dependent libraries) do run into these limits on all platforms from time to time, which is why they invented response file to hide their humongous command-line arguments in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kha Thanks! I learn something new everyday. I am somewhat surprised that such a limitation hasn't been lifted yet (either on Windows or Unix), but I imagine there are complexities there I do not realize.
We should fall under 32K for now. However, if we assume an average of 50-80 bytes per argument, that only allows somewhere between 400-640 modules to be loaded by Lean or linked by the linker, so we will likely hit that cap rather soon. I can easily see mathlib surpassing it relatively quickly (and definitely by the time the port is finished). Thus, response files for CLIs (both Lean and the linker) seem like an important feature to consider soon.
From a mathlib point of view, it's important that precompilation can be selectively enabled for only some of the modules. That is, produce *.so for everything in Mathlib/Tactic, but for nothing else. (EDIT: would that even work? Should we throw an error if a file has precompilation enabled but it's dependencies don't?) |
@gebner Mathlib tactics can depend on other non-tactic parts of the library, though, right? So those dependencies would also need their native components built. As such, it seems like it would just be reasonable to build native components for everything for the time being. |
Ok. Just to be clear, does that mean we should keep |
@Kha |
You're not describing a complete solution. Should the |
@Kha Sorry for not explaining this well enough. Here is summary of what I was suggesting:
|
An alternative solution would be to float the In that case there would be a separate In fact, I think the PR as it stands needs to do some checking in |
Hmm, perhaps we need to take a step back. I wanted to go with a straightforward It Just Works solution (... modulo dependencies) for now, but if that is not acceptable, I'd rather rewrite the code like I believe it will have to be in the near future anyway, as alluded to in the initial message. Specifically, I posit:
If we accept both of these, the implication to me is that there should be a single target for recursively building modules that decides whether to build .c and .pre.so files on a per-module basis. Specifically,
Thoughts? Also happy to try and resolve this in a call instead if it helps. |
I agree largely with what you said here, but I feel like we have different mental models of this change (and the related concepts in Lake) and it is leading us to talk past one another. A call seems like a good idea, as it is quicker and easier to have the back-and-forth necessary for a discussion like this in a synchronous call than this asynchronous discussion thread. |
Great. Let's arrange a time & date on Zulip? Anyone else want to join? |
I haven't read the whole discussion in depth. Some general notes: I think it's okay to merge this implementation now, even if it is still "prototype" quality. It would be certainly be useful to try it out on mathlib4. The only hard requirement is that package depending on mathlib4 continue to work (i.e., precompilation needs to work across package dependencies, I didn't try out whether that works already). More fine-grained control of what modules are compiled is not an immediate requirement now. It will only become important when mathlib gets more math content; say late spring / early summer. Regarding large refactorings: I still think it would be very useful to base lake on (a Lean version of) shake, because that makes it much easier to add custom build targets and dependencies of lean files. If I understood you correctly on zulip, such a shake port is already on your todo-list. It might make sense to move that up before rewriting things twice.
I'll gladly join unless you want to keep the meeting small. |
I redid my measurements on mathlib4 after the above commit, which should help with incremental compilation. They are: full build w/ So the C compilation overhead really is the largest by far, but we should assume it is mostly avoided when recompiling. Linking & loading are each around 5% overhead. |
One final performance observation for now: according to |
Ok, understandable. I probably would have merged it by myself by now if the need for the feature would have manifested, but it seems we're still not quite there yet :) . |
@Kha I was looking through the PR as I plan on implementing a similar approach this week and I had a question. Have the |
@Kha I have finally finished my version of precompiled modules and was thinking of using the test you had written in this PR. However, I am not exactly sure what it tests. What difference in behavior should one be looking for from a working version of the example test vs a non-working version? |
Well if the test fails, we know something went wrong :) . But we could do something simple with symbols like @[export foo]
def fooImpl := 42
@[extern "foo"]
opaque foo : Nat import Foo
#eval foo |
The last part doesn't seem to be the case currently. If I enable precompilation for Aesop (a prime example for a library-level precompilation use case btw) and then import it into my project which does not have precompilation enabled, no Aesop dynlibs are loaded. |
a feature of #47 I had hetherto missed
a feature of leanprover/lake#47 I had hetherto missed
This is the more immediately important part of leanprover/lean4#949. It was less complicated in the end than I feared, though I went for the most direct implementation for now. It might be a good idea to refactor the three recursive module builders into a single one.
This should probably have a test, but it will have to wait until leanprover/lean4#959 is released.