crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.47k stars 1.62k forks source link

Load-time dynamic linking on Windows #11575

Open HertzDevil opened 2 years ago

HertzDevil commented 2 years ago

We already have two use cases for load-time dynamic linking on Windows: distributing the LLVM libraries, and linking to MPIR without license issues. (Run-time dynamic linking would be equivalent to porting Crystal::Loader to Windows.) #11573 shows that this is indeed possible, but we are still far from supporting it in the compiler itself. Here are some thoughts:

HertzDevil commented 2 years ago

It looks like LLVM's CMakeLists.txt doesn't support BUILD_SHARED_LIBS on MSVC: https://llvm.org/docs/CMake.html#llvm-related-variables

It does have a LLVM_BUILD_LLVM_C_DYLIB variable which produces Release\lib\LLVM-C.lib and Release\bin\LLVM-C.dll. I do not know whether this DLL is sufficient for our LibLLVM. I am even less sure whether llvm_ext.obj would work.

HertzDevil commented 2 years ago

Related: #9278

HertzDevil commented 1 year ago

13436 will provide us a way to implement our own RPATH-style custom DLL searching, by calling LoadLibraryExA multiple times with different absolute paths, instead of once with an unqualified name. That would be good for crystal run and similar because it means we don't have to "install" the DLLs into a fixed location, and even Crystal itself doesn't need to be in %PATH%.

dumpbin /exports LLVM-C.dll shows that it provides everything in LibLLVM already except for the unused bindings in #13438. The problem is llvm_ext.cc; it depends on LLVM's C++ interfaces, so it must link against LLVM statically, which means a Crystal compiler distributed with just LLVM-C.dll cannot be used to rebuild Crystal. Removing the DIBuilder stuffs is easy, the rest not so much.

The top question is still figuring out how to distribute the static libraries and the DLL import libraries side-by-side. Generating import libraries from the DLLs themselves and placing them in the cache directory is probably not a viable option.

HertzDevil commented 1 year ago

Idea: if --static is provided, Crystal will manually look up foo-static.lib followed by foo.lib, otherwise Crystal tries foo-dynamic.lib and then foo.lib. This will solve the CRYSTAL_LIBRARY_PATH issue, but it still requires all libraries under --static to link against the static CRT (/MT), because that remains controlled by the same flag:

https://github.com/crystal-lang/crystal/blob/98c091ee9dd5a0e75b3dd47b642bd170b4c84a39/src/lib_c.cr#L1-L4

In the future this would become {{ flag?(:static) ? "libucrt" : "ucrt" }}. Ditto for the CRT startup library.

After #13436 we will perform an actual library search anyway, because we need to open the library files to search for the DLL imports. Two consequences are that we don't even need to pass /LIBPATH to cl.exe at all, and that we could produce a compiler error prior to linking if the library cannot be found.

HertzDevil commented 2 months ago

We have fully switched to dynamic linking and I think we could consider this issue finished once the following TODO is also addressed:

https://github.com/crystal-lang/crystal/blob/cdd9ccf460641ee17a603c67ff9d23aa1199f14f/src/compiler/crystal/loader/msvc.cr#L158-L161