From 965c1b20b5c647bee7cb1d090f7be5853ffbd96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20R=C3=B6mer?= Date: Fri, 31 Jul 2026 10:37:30 +0200 Subject: [PATCH] Hlms: give msThreadId default visibility so a static build links into a PIE OGRE_SHADER_THREADING_USE_TLS is a static-build setting, and there _OgreExport is visibility("hidden"). Hlms::msThreadId is declared in OgreHlms.h and defined constant-initialized in OgreHlms.cpp, so a translation unit that sees only the declaration emits the Itanium ABI thread-local access wrapper and its weak reference to the init function _ZTHN4Ogre4Hlms10msThreadIdE - a symbol a constant-initialized variable never defines. Resolving that dangling weak reference to zero needs a GOT entry, which the compiler only emits for a symbol that may bind externally; hidden, clang addresses it directly and leaves R_X86_64_PC32, which GNU ld refuses to link into a position-independent executable. Only optimized builds hit it: at -O0 the relocation stays inside the wrapper's own COMDAT section, which the linker discards in favour of the clean copy OgreHlms.cpp contributes, while inlining moves it into ordinary .text where it survives to the final link. GCC reaches the symbol through the GOT regardless and AArch64 does too, so only clang on x86-64 in Release is affected. Declaring the member with explicit default visibility restores the indirection every other configuration already uses. There is no runtime cost: the linker relaxes the resulting general-dynamic TLS access back to local-exec because the variable is defined in the executable. MSVC keeps the byte-identical old declaration. --- OgreMain/include/OgreHlms.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/OgreMain/include/OgreHlms.h b/OgreMain/include/OgreHlms.h index b2ee9a8e54d..bead3ca794f 100644 --- a/OgreMain/include/OgreHlms.h +++ b/OgreMain/include/OgreHlms.h @@ -149,7 +149,38 @@ namespace Ogre #ifdef OGRE_SHADER_THREADING_BACKWARDS_COMPATIBLE_API # ifdef OGRE_SHADER_THREADING_USE_TLS +# ifdef OGRE_GCC_VISIBILITY + /** Left visible on purpose, even though _OgreExport hides every other + member of this class in a static build (OGRE_SHADER_THREADING_USE_TLS + is a static-only setting, so that is the only build this declaration + appears in). + + A translation unit that sees only the declaration cannot know that + the definition in OgreHlms.cpp is constant-initialized, so it emits + the Itanium ABI thread-local access wrapper (_ZTW...), which weakly + references the thread-local init function + _ZTHN4Ogre4Hlms10msThreadIdE - a symbol that a constant-initialized + variable never defines anywhere. Resolving that dangling weak + reference to zero is the linker's job, and it needs the indirection + of a GOT entry, which the compiler only emits for a symbol that may + bind externally; a hidden symbol it addresses directly, and the + resulting relocation cannot be linked into a position-independent + executable: + + relocation R_X86_64_PC32 against undefined hidden symbol + `_ZTHN4Ogre4Hlms10msThreadIdE' can not be used when making a + PIE object + + Only optimized builds hit it. Unoptimized ones leave the reference + inside the wrapper's own COMDAT section, which the linker discards + in favour of the clean copy OgreHlms.cpp contributes; inlining the + wrapper moves the relocation into ordinary code, where it survives + to the final link. + */ + static __attribute__( ( visibility( "default" ) ) ) thread_local uint32 msThreadId; +# else static thread_local uint32 msThreadId; +# endif # else static constexpr uint32 msThreadId = 0u; # endif