Zulip Chat Archive
Stream: lean4
Topic: Precompile modules on Windows
Tomas Skrivan (Dec 02 2024 at 07:43):
I'm trying to build SciLean on Windows but I'm having issues with precompileModules
option and I'm getting linker errors undefined symbol ...
.
Here is mwe
lakefile.lean
import Lake
open Lake DSL System
package foo
lean_lib Foo where
roots := #[`Foo]
precompileModules := true
@[default_target]
lean_exe foo where
root := `Main
Foo/Basic.lean
@[extern "hello_impl"]
opaque hello (name : String) : String
Foo/Impl.lean
@[export hello_impl]
def helloImpl (name : String) := s!"hello {name}"
Foo.lean
import Foo.Basic
import Foo.Impl
Main.lean
import Foo
def main : IO Unit :=
IO.println s!"Hello, {hello "John"}!"
Running lake build
produces error
Γ£û [8/16] Building Foo.Basic:dynlib
trace: .> c:\Users\skriv\.elan\toolchains\leanprover--lean4---stable\bin\leanc.exe -shared -o .\.\.lake\build\lib\Foo-Basic-1.dll .\.\.lake\build\ir\Foo\Basic.c.o.export -L.\.\.lake\build\lib
info: stderr:
ld.lld: error: undefined symbol: hello_impl
>>> referenced by .\.\.lake\build\ir\Foo\Basic.c.o.export:(l_hello___boxed)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: external command 'c:\Users\skriv\.elan\toolchains\leanprover--lean4---stable\bin\leanc.exe' exited with code 1
Some required builds logged failures:
- Foo.Basic:dynlib
error: build failed
Looks like that the call to leanc
is missing -lFoo-Impl-1
. So manually calling
c:\Users\skriv\.elan\toolchains\leanprover--lean4---stable\bin\leanc.exe -shared -o .\.\.lake\build\lib\Foo-Basic-1.dll .\.\.lake\build\ir\Foo\Basic.c.o.export -L.\.\.lake\build\lib -lFoo-Impl-1
does not fail anymore and produces desired Foo-Basic-1.dll
.
Now we have Foo-Basic-1.dll
lets try resume build and call lake build
again. It fails with
Γ£û [10/16] Building Foo
trace: .> LEAN_PATH=.\.\.lake\build\lib PATH c:\Users\skriv\.elan\toolchains\leanprover--lean4---stable\bin\lean.exe .\.\.\.\Foo.lean -R .\.\.\. -o .\.\.lake\build\lib\Foo.olean -i .\.\.lake\build\lib\Foo.ilean -c .\.\.lake\build\ir\Foo.c --load-dynlib=.\.\.lake\build\lib\Foo-Basic-1.dll --load-dynlib=.\.\.lake\build\lib\Foo-Impl-1.dll --json
info: stderr:
libc++abi: terminating due to uncaught exception of type lean::exception: error loading library .\.\.lake\build\lib\Foo-Basic-1.dll: 126
error: Lean exited with code 3221226505
Some required builds logged failures:
- Foo
error: build failed
I have not been successful in modifying this command to make it succeed.
Is this a bug in lake
? @Mac Malone Or what am I doing wrong?
Sebastian Ullrich (Dec 02 2024 at 07:47):
Oh wow, we definitely didn't design it with such extern/export cross-edges in mind
Tomas Skrivan (Dec 02 2024 at 07:49):
Uhh what do you mean? I'm just recreating what is done with simp
/simpImp
, whnf
, isDefEq
etc.
Tomas Skrivan (Dec 02 2024 at 07:50):
Maybe I'm not doing it right but it works on Linux. (I didn't test this particular mwe on Linux as I'm on Windows right now but in SciLean I'm doing something identical)
Tomas Skrivan (Dec 02 2024 at 07:57):
How am I supposed to write a recursive tactic like simp
using extern/export and use it in my project? I need `precompileModules, right?
The above mwe compiled fine if I remove precompileModules
but then I can't #eval main
in Main.lean
as I get server error
libc++abi: terminating due to uncaught exception of type lean::exception: Could not find native implementation of external declaration 'hello' (symbols 'l_hello___boxed' or 'l_hello').
Sebastian Ullrich (Dec 02 2024 at 09:04):
Just because core does it doesn't mean it's the best solution for user code. If it's infeasible to contain the recursion in one mutual
, you could for example store the function for recursive use in an IO.Ref
, kind of the userspace equivalent of export+extern
Tomas Skrivan (Dec 02 2024 at 09:33):
I see, I will keep that in mind. However right now I have a fork of simp and redesigning to that would probably be quite some work.
Sebastian Ullrich (Dec 02 2024 at 09:42):
The IO.Ref should be a simple change
Tomas Skrivan (Dec 02 2024 at 10:10):
Do I understand it correctly that I it should look something like this:
Init.lean
initialize factorialRef : IO.Ref (Nat → IO Nat) ← IO.mkRef (fun x => return x)
def factorial (n : Nat) : IO Nat := do
let ref ← factorialRef.get
return (← ref n)
Impl.lean
def factorialImpl (n : Nat) : IO Nat := do
match n with
| 0 => return 1
| n + 1 => return (n+1)* (← factorial n)
initialize factorialRef.set factorialImpl
#eval factorial 4
Tomas Skrivan (Dec 02 2024 at 10:12):
In that case the change should be easy, thanks for the help!
Tomas Skrivan (Dec 02 2024 at 10:41):
Great, fixing SciLean was just six lines and now it compiles on Windows.
Sebastian Ullrich (Dec 02 2024 at 13:00):
FYI, what probably happened here is that Linux allows undefined symbols in shared libraries as long as they are defined at load time, and Windows doesn't. So this is a correct cross-platform solution.
Yakov Pechersky (Dec 02 2024 at 15:20):
This is also the case for Macs, as I've learned recently. Where the solution is -Wl,-undefined,dynamic_lookup
: https://blog.tim-smith.us/2015/09/python-extension-modules-os-x/
Last updated: May 02 2025 at 03:31 UTC