llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.77k stars 11.9k forks source link

Static libc++ on Windows has problems with missing exception related symbols. #84490

Open tru opened 7 months ago

tru commented 7 months ago

On Windows if you build libc++ as a static .lib you will get this error on a simple helloworld program:

I build the program like this:

clang-cl.exe /Fohello.obj /c hello.cpp /clang:-nostdinc++ \
  /clang:-cxx-isystembuild/include/c++/v1 \
  /clang:-cxx-isystembuild/include/x86_64-pc-windows-msvc/c++/v1 \
  /D_CRT_STDIO_ISO_WIDE_SPECIFIERS /EHsc
lld-link.exe /out:hello.exe /debug hello.obj /libpath:build/lib/x86_64-pc-windows-msvc
Error output from lld-link ``` lld-link: error: undefined symbol: void __cdecl __ExceptionPtrCreate(void *) >>> referenced by libc++.lib(exception.cpp.obj):(public: __cdecl std::exception_ptr::exception_ptr(void)) >>> referenced by libc++.lib(exception.cpp.obj):(public: __cdecl std::exception_ptr::exception_ptr(std::nullptr_t)) >>> referenced by libc++.lib(exception.cpp.obj):(public: class std::exception_ptr & __cdecl std::exception_ptr::operator=(std::nullptr_t)) >>> referenced 4 more times lld-link: error: undefined symbol: void __cdecl __ExceptionPtrCopy(void *, void const *) >>> referenced by libc++.lib(exception.cpp.obj):(public: __cdecl std::exception_ptr::exception_ptr(class std::exception_ptr const &)) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrAssign(void *, void const *) >>> referenced by libc++.lib(exception.cpp.obj):(public: class std::exception_ptr & __cdecl std::exception_ptr::operator=(class std::exception_ptr const &)) >>> referenced by libc++.lib(exception.cpp.obj):(public: class std::exception_ptr & __cdecl std::exception_ptr::operator=(std::nullptr_t)) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrDestroy(void *) >>> referenced by libc++.lib(exception.cpp.obj):(public: class std::exception_ptr & __cdecl std::exception_ptr::operator=(std::nullptr_t)) >>> referenced by libc++.lib(exception.cpp.obj):(public: __cdecl std::exception_ptr::~exception_ptr(void)) >>> referenced by libc++.lib(exception.cpp.obj):(int `class __copy_exception_ptr::exception_ptr __cdecl std::__copy_exception_ptr(void *, void const *)'::`1'::dtor$4) >>> referenced 4 more times lld-link: error: undefined symbol: bool __cdecl __ExceptionPtrToBool(void const *) >>> referenced by libc++.lib(exception.cpp.obj):(public: bool __cdecl std::exception_ptr::operator bool(void) const) lld-link: error: undefined symbol: bool __cdecl __ExceptionPtrCompare(void const *, void const *) >>> referenced by libc++.lib(exception.cpp.obj):(bool __cdecl std::operator==(class std::exception_ptr const &, class std::exception_ptr const &)) >>> referenced by libc++.lib(exception.cpp.obj):(public: void __cdecl std::nested_exception::rethrow_nested(void) const) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrSwap(void *, void *) >>> referenced by libc++.lib(exception.cpp.obj):(void __cdecl std::swap(class std::exception_ptr &, class std::exception_ptr &)) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrCopyException(void *, void const *, void const *) >>> referenced by libc++.lib(exception.cpp.obj):(class std::exception_ptr __cdecl std::__copy_exception_ptr(void *, void const *)) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrCurrentException(void *) >>> referenced by libc++.lib(exception.cpp.obj):(class std::exception_ptr __cdecl std::current_exception(void)) >>> referenced by libc++.lib(exception.cpp.obj):(public: __cdecl std::nested_exception::nested_exception(void)) lld-link: error: undefined symbol: void __cdecl __ExceptionPtrRethrow(void const *) >>> referenced by libc++.lib(exception.cpp.obj):(void __cdecl std::rethrow_exception(class std::exception_ptr)) ```

This is something we discussed at EuroLLVM a year ago @zmodem @petrhosek @mstorsjo - but I couldn't find a issue for it so I wanted to track it to get a final fix in for this.

I was able to do some stubs for this in my own application:

void __cdecl __ExceptionPtrCreate(void *) { }
void __cdecl __ExceptionPtrCopy(void *, void const *) { }
void __cdecl __ExceptionPtrDestroy(void *) { }
bool __cdecl __ExceptionPtrToBool(void const *) { return false; }
bool __cdecl __ExceptionPtrCompare(void const *, void const *) { return false; }
void __cdecl __ExceptionPtrSwap(void *, void *) { }
void __cdecl __ExceptionPtrCopyException(void *, void const *, void const *) { }
void __cdecl __ExceptionPtrCurrentException(void *) { }
void __cdecl __ExceptionPtrRethrow(void const *) { }
void __cdecl __ExceptionPtrAssign(void *, void const *) { }

Which made the program compile and run (without using exceptions of course).

But the question is how do we solve this long term. I think @petrhosek was talking about getting the source from Microsoft in order to put it into libc++, but that it had stalled and we couldn't get to a good conclusion.

Any input on this would help since we are making a push to see if we can replace MSVC STL with libc++ in some of our applications.

tru commented 7 months ago

Upstream seems to have implementations of these functions here: https://github.com/microsoft/STL/blob/main/stl/src/excptptr.cpp#L416

Can we just import these functions into libc++? Are the licenses compatible these days? Also I wonder if this is the right fix or if it could create problems with exception ABI.

ldionne commented 7 months ago

I don't know much about Windows, but don't you need to link against their ABI library for this to work? Or is their ABI library basically merged into their main C++ Runtime library so you'd have to link against all of it? And if so, why can't you link against that (you basically won't use most of the symbols in their library except the symbols from their ABI library).

mstorsjo commented 7 months ago

I don't know much about Windows, but don't you need to link against their ABI library for this to work? Or is their ABI library basically merged into their main C++ Runtime library so you'd have to link against all of it? And if so, why can't you link against that (you basically won't use most of the symbols in their library except the symbols from their ABI library).

Yep, pretty much this. Libc++ in MSVC environments currently relies on basic C++ ABI stuff from vcruntime and MS STL. For static linking, this requires pulling in those libraries. Due to the usual issues with transitive linking and static libraries, users would have to specify those libraries additionally.

(In the end, one ends up linking against the whole MS STL, but only including the relevant C++ ABI bits.)

But I guess it could be possible to add relevant #pragmas into the static libc++ object files, that would make it automatically pull in the right libraries as well? That way this bit would work automatically, regardless of whether we rely on MS STL/vcruntime or not.

tru commented 7 months ago

I guess it would be fine to link to the STL since the namespace of the C++ functions would be different to not cause clashes. But yeah ideally it would work out of the box if that's the behavior we want.

I still would like to hear what @petrhosek has to say since I know he spent some time investigating this as well.

ldionne commented 7 months ago

Can we use the usual combination of libc++ / libc++abi / libunwind on Windows? If so, that should be the default configuration the library comes with, and then you should be able to build the library to make it compatible with the system library by specifying a CMake cache.

mstorsjo commented 7 months ago

Can we use the usual combination of libc++ / libc++abi / libunwind on Windows? If so, that should be the default configuration the library comes with, and then you should be able to build the library to make it compatible with the system library by specifying a CMake cache.

We can, and do, use them on MinGW, which uses the Itanium C++ ABI.

MSVC mode uses an entirely different C++ ABI, and AFAIK all that libunwind and libcxxabi provides, is specific to the Itanium ABI. So, it clearly would be nice to not rely on them, but we don't have anything right now to provide the MSVC C++ ABI, AFAIK.

tru commented 7 months ago

I have done some experiments here while chatting with @mstorsjo https://github.com/tru/llvm-project/commit/aa6d8b5bbc26d1187ea1049941da9b522da5c5b4 - I am not ready to send out a PR for this yet, but I wanted to document this so I don't forget about it.

petrhosek commented 6 months ago

I apologize about the delayed response, I haven't actually forgotten about this issue, it's just that the nature of this issue is somewhat sensitive since it involves licensing.

I got in touch with the Microsoft STL team and while they're supportive of contributing their implementation, the blocker is the dual licensing—every contribution to LLVM has to be dual licensed under the terms of the new as well as the legacy license—and while STL uses Apache-2.0 WITH LLVM-exception license same as LLVM, relicensing the STL implementation under the legacy license would require a significant effort and not something the team is keen on pursuing.

I followed up with the LLVM Foundation regarding this issue and the result is the proposal to drop the requirement to contribute also under the legacy license. I hope we can make this change in time for LLVM 19 and I plan to follow up with the Microsoft STL team immediately afterwards.