/min-crt-poc

Trimmed-down MinGW DLL entry point in Rust

Primary LanguageRust

MinGW compilers come in multiple flavours, "mingw", "ucrt" and "clang"/"llvm". This asks for the question how many Rust targets there should be. While both MinGW GCC and LLVM toolchains use the same C run-times, they are utilizing different exception handling mechanisms. This makes it impossible to produce a "universal" non-MSVC Windows target that would work with both GCC and Clang. But what about "mingw" vs. "ucrt"? The suggested approach aims to facilitate run-time-neutral <arch>-pc-windows-gnu target. In more practical terms, after executing rustup target add <arch>-pc-windows-gnu users will be free to choose between msvcrt and ucrt by simply adjusting their PATH environment variable(**).

The key component is dllmain.rs, a trimmed-down MinGW CRT initialization module, that should allow linking std-<release-id>.dll without reference to a specific run-time, legacy msvcrt.dll or Universal CRT.

While compiling with -C prefer-dynamic might not be that popular, the fact that the std-<release-id>.dll can be linked without reference to a specific run-time strongly suggests that the static libstd-<release-id>.rlib and Rust-generated code in general don't actually have the dependency either. Or rather it's not strong enough(*) to prevent the choice of the run-time to be postponed till the application link time. This is the main proposition.

The rest of the crate is a test-bed PoC around the dllmain.rs. You're more than likely going to have to run cargo run ... twice to test. This is because application code gets linked before the library is available. If you know how to arrange the link invocations in specific order, do tell:-) Either way, once confirmed to execute, double-check that poc.dll doesn't have imports from your CRT. The test registers a pre-main subroutine, an equivalent to __attribute__((constructor)) in C, spawns a rayon thread and prints the value set by the "constructor."

Even though the primary target is MinGW, the dllmain.rs and PoC work even with MSVC.

(*) Rust compiler depends on memcpy/move/set/cmp and strlen being externally available. These are interchangeable between all known run-times.

(**) Caveat lector. By default Rust compiler drivers invokes x86_64-w64-mingw32-gcc to link the final binary. The catch is that some MinGW clang packages were spotted to provide x86_64-w64-mingw32-gcc, presumably to smooth the switch. Since it's the C compiler driver that is responsible for linking the exception handling library, it would be a problem if the exception flavours chosen by Rust and first-on-the-PATH C compiler mismatch. In the context one can make a case for a sanity check for a matching C compiler. Most notably MinGW GCC target driver could verify that the library search path in x86_64-w64-mingw32-gcc -print-search-dirs output has corresponding exception handling library, libgcc_eh.a.