Hello Takashi,
To be clear, I'm not asking you to fix the TLS functionality of compiler-rt; I'm just asking you to not package a DLL built with -rtlib=compiler-rt (and also -stdlib=libc++). Such a library implicitly enforces the use of specific compiler options, making it incompatible with other libraries, even if compiler-rt is provided as a shared library. 2026年1月24日 18:29:52 (+09:00) で、Takashi Yano via Cygwin さんが書きました: > > > > I’m concerned this makes the situation worse than providing a static > > library. I guess > > that the upstream has never built it as a shared library. Consequently, > > that configuration > > appears to be largely untested. > > And then, what is the benefit of "specifying -rtlib=compiler-rt makes us > > cyggcc_s.dll free but > > requires a dependency on cygclang_rt.builtins-21-x86_64.dll" ? Is there > > another benefit? > > I don't think I could get your point. The benefit of this test version is > solving the broken thread local variable reference between dlls and > executable, > of course. > That doesn't explain the benefit of using compiler-rt instead of libgcc. The availability itself of the option to select a runtime (i.e., the existence of the compiler-rt package -- thanks for your effort!) is indeed a benefit for programmers, but not for other packages or users, including programmers who want to use the other package as a library. Regarding building a package with -rtlib=compiler-rt, either verification that the problem does not affect programmers in practice or a clear justification is needed, since there is at least one downside. To verify that a package built with -rtlib=compiler-rt doesn't cause a problem with the TLS, it would be enough to check that if no module (not only DLLs; EXEs also can export symbols) in the package exports a symbol beginning with __emutls_. However, I believe this kind of check should be automated for *all* packages by default, regardless of who maintains it or whether the package uses compiler-rt; otherwise, the check will easily be overlooked. Of course, the checker must always pass for a module which is linked to libgcc_s. > > Furthermore, if you really intend to provide a DLL linked with compiler-rt, > > the > > DLL comes with limitations that are hard to notice. > > (If this is not what you are planning, please feel free to ignore this.) > > - Linking the DLL without -rtlib=compiler-rt breaks the EmuTLS > > functionality silently. There is > > no way to know that the linking needs compiler-rt except inspecting the > > DLL with ldd or objdump. > > Why? If -rtlib=compiler-rt is not specified, the program uses libgcc_s > which is a shared library. Therefore, emutls works as expected except > for a bug in libstdc++: > https://cygwin.com/pipermail/cygwin/2025-November/258943.html > As you confirmed below, importing a TLS variable from a DLL that built with -rtlib=compiler-rt requires end-programmers to also use the same flag for it to work correctly. Therefore, a DLL built with comipler-rt can't work with another DLL that exports a TLS variable. > > - GCC doesn't support -rtlib=compiler-rt. That effectively enforces the use > > of clang. > > - A single module (EXE or DLL) can link against only one of the TLS > > implementations, either the > > one from libgcc_s or the one from compiler-rt. A TLS variable that relies > > on the other > > implementation would be broken (again, silently). > > These are not related to the aproach whether compiler-rt provides shared > library or not, I think. Even if compiler-rt provides static library, > above two problems still exist. > Yes, I explained that building compiler-rt as a shared library doesn't solve the problem. > > Of course that might not matter if EmuTLS is never used, however, I worry > > about the > > assumptions that are not enforced or validators automatically would be > > broken silently. > > > > Therefore, my conclusion remains unchanged -- "Please don't do that." > > I have uploaded compiler-rt 21.1.4-3 (Test), where the shared library > is used only for __emutls_get_address(), and programs that do not use > emutls will be linked with compiler-rt statically. > I don't understand how the split runtime solves the problem. > > Just to note, we're now focusing on compiler-rt because my original > > attention is about > > EmuTLS, but the same discussion is applicable to libc++. A DLL that exports > > C++ interfaces > > and linked to libc++ (if such a DLL were to be provided, would it be > > cygLLVM-X.dll?) can't be > > linked together with another libstdc++-ed C++ library (e.g., boost). > > No. cygLLVM-x.dll is a part of llvm package. The shared library in libc++ > package is cygc++-1.dll. Anyway, I don't understand what is the problem > in that case. > It seems you're saying "No" to something what I didn't claim. I'm worried that you might build the next version of the llvm package (or other library packages you maintain) with -stdlib=libc++ and -rtlib=compiler-rt. That's all I'm asking with "Please don't do that." Consider to the case of someone is developing an LLVM plugin helped with Boost. Both of the LLVM headers and the Boost headers include standard C++ libraries. If the llvm package were built with libc++, how would they specify the -stdlib flag to compile a .cpp file that includes both of LLVM and Boost headers? > Of course, the cases bellow do not work because executable and DLL > call different __emutls_get_address(). > > $ clang++ -stdlib=libc++ -unwindlib=libunwind -rtlib=compiler-rt > emutlstest.cc -o emudll.dll -DDLL=1 -shared && clang++ -stdlib=libc++ > -unwindlib=libgcc -rtlib=libgcc emutlstest.cc -o emuexe.exe -DEXE=1 > emudll.dll && ./emuexe.exe > 0 0xa00000538 0xa00000558 > 0 0xa00010ba8 0xa00010c58 > $ clang++ -stdlib=libc++ -unwindlib=libgcc -rtlib=libgcc emutlstest.cc -o > emudll.dll -DDLL=1 -shared && clang++ -stdlib=libc++ -unwindlib=libunwind > -rtlib=compiler-rt emutlstest.cc -o emuexe.exe -DEXE=1 emudll.dll && > ./emuexe.exe > 0 0xa00000538 0xa00000558 > 0 0xa00010b18 0xa00010c58 > This is the problem I mentioned as "no way to know that the linking needs compiler-rt except inspecting the DLL with ldd or objdump". > Furthermore, the following test case also works. > > $ cat hello.cc > #include <iostream> > #ifdef DLL > void func() > { > std::cout << "Hello(dll)" << std::endl; > } > #endif > #ifdef EXE > extern void func(); > int main() > { > std::cout << "Hello(main)" << std::endl; > func(); > return 0; > } > #endif > $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL > hello.cc -shared -o hello.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc > -unwindlib=libgcc -DEXE hello.cc hello.dll -o hello.exe && ./hello.exe > Hello(main) > Hello(dll) > $ clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DDLL hello.cc > -shared -o hello.dll && clang++ -stdlib=libc++ -rtlib=compiler-rt > -unwindlib=libunwind -DEXE hello.cc hello.dll -o hello.exe && ./hello.exe > Hello(main) > Hello(dll) > This is not a case of "A DLL that exports C++ interfaces", even though the exported symbol is C++-mangled. hello.exe references std::cout independently of hello.dll, meaning they pull references from different instances. $ cat ios.cc #include <iostream> void s(); #ifdef DLL void s() { std::cout << 42 << std::endl; } #endif #ifdef EXE int main() { std::cout << std::hex << 42 << std::endl; s(); std::cout << 42 << std::endl; } #endif $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL ios.cc -shared -o ios.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE ios.cc ios.dll -o ios.exe && ./ios.exe 2a 42 2a There are examples of how fail with "A DLL that exports C++ interfaces" below. A case that fails to link: $ cat vec.cc #include <vector> void f(std::vector<int> *x); #ifdef DLL void f(std::vector<int> *x) {}; #endif #ifdef EXE int main() { f(nullptr); } #endif $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL vec.cc -shared -o vec.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE vec.cc vec.dll -o vec.exe && ./vec.exe /usr/bin/ld: /tmp/vec-c89382.o:vec.cc:(.text+0x14): undefined reference to `f(std::vector<int, std::allocator<int> >*)' clang++: error: linker command failed with exit code 1 (use -v to see invocation) A case that causes a runtime error or broken silently: #include <string> std::string g(); #ifdef DLL std::string g() { return "abc"; } #endif #ifdef EXE #include <stdio.h> int main() { printf("%zu\n", g().length()); } #endif $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL str.cc -shared -o str.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE str.cc str.dll -o str.exe && ./str.exe Segmentation fault $ clang++ -stdlib=libstdc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL str.cc -shared -o str.dll && clang++ -stdlib=libc++ -rtlib=libgcc -unwindlib=libgcc -DEXE str.cc str.dll -o str.exe && ./str.exe 92 In this case, changing "abc" to "abcdefghijkl" gives: $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL str.cc -shared -o str.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE str.cc str.dll -o str.exe && ./str.exe (succeeded with no output) However, gdb says that it aborts with SIGSEGV: $ gdb ./str.exe -q --batch -ex r -ex q [New Thread 85460.0xb39c] [New Thread 85460.0xee68] [New Thread 85460.0x11f24] [New Thread 85460.0xc7d0] Thread 1 "str" received signal SIGSEGV, Segmentation fault. 0x00000003f7c20af3 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::length (this=0x7ffffcc08) at /usr/src/debug/gcc-13.4.0-1/libstdc++-v3/include/bits/cow_string.h:929 929 { return size(); } A debugging session is active. Inferior 1 [process 85460] will be killed. Quit anyway? (y or n) [answered Y; input not from terminal] Another case that is silently broken: $ cat exc.cc void e(); #ifdef DLL void e() { throw 3; } #endif #ifdef EXE #include <stdio.h> int main() { try { puts("try"); e(); } catch (int a) { printf("catch %d\n", a); } catch (...) { puts("catch other"); } puts("finish"); } #endif $ clang++ -stdlib=libstdc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL exc.cc -shared -o exc.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE exc.cc exc.dll -o exc.exe && ./exc.exe try catch 3 finish $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL exc.cc -shared -o exc.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE exc.cc exc.dll -o exc.exe && ./exc.exe try catch other finish A case that looks slightly more practical: $ cat deq.cc #include <deque> class some_state { std::deque<int> state; public: void modify_state(int x); bool verify_state() const; }; #ifdef DLL void some_state::modify_state(int x) { state.push_back(x); } bool some_state::verify_state() const { int x = 0; for (auto i: state) x += i; return x > 0; } #endif #ifdef EXE #include <stdio.h> int main() { some_state s; s.modify_state(3); printf("%d\n", s.verify_state()); } #endif $ clang++ -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL deq.cc -shared -o deq.dll && clang++ -stdlib=libstdc++ -rtlib=libgcc -unwindlib=libgcc -DEXE deq.cc deq.dll -o deq.exe && ./deq.exe Segmentation fault $ clang++ -stdlib=libstdc++ -rtlib=compiler-rt -unwindlib=libunwind -DDLL deq.cc -shared -o deq.dll && clang++ -stdlib=libc++ -rtlib=libgcc -unwindlib=libgcc -DEXE deq.cc deq.dll -o deq.exe && ./deq.exe (succeeded with no output, the same as the str.cpp case. I'm not sure whether cygwin1.dll or libc++ needs to be fixed.) Therefore, providing a libc++-ed DLL that exports C++ interfaces is quite dangerous. Please don't do that. To be fair, a DLL that only exports pure C interfaces doesn't force programmers to specify a particular -stdlib flag, even if the DLL is linked with libc++. Therefore, what I'm worried about doesn't matter in that case. Checking that no exported symbols begin with _Z may be sufficient to verify the exports (again, this check should be automated, I think.) -- Tomohiro Kashiwada (@kikairoya) -- Problem reports: https://cygwin.com/problems.html FAQ: https://cygwin.com/faq/ Documentation: https://cygwin.com/docs.html Unsubscribe info: https://cygwin.com/ml/#unsubscribe-simple

