qversiontagging: Use C++17 inline variables also on Unix

C++17 inline variables allow us to declare a variable that shall be
merged before linking, which replaces the need for the "comdat" part of
the inline assembly. The GCC attribute "used" tells the compiler not to
discard this variable, like the MinGW case. Additionally, the "retain"
attribute (where supported) tells both the compiler and linker not to
discard, allowing an intermediary, static library to keep this
definition.

This enables support for OSes besides FreeBSD and Linux, where it was
previously available. For example, on macOS:

 $ nm libexec/rcc | grep qt_version_tag
                 U _qt_version_tag_6_4
 00000001000ec608 s _qt_version_tag_6_4_use

On Linux, the assembly output before this change (Clang 13) was:
        .section        .qtversion,"aG",@progbits,qt_version_tag,comdat
        .p2align        3
        .quad   qt_version_tag@GOT
        .long   394240
        .p2align        3

After this change:
        .hidden qt_version_tag_use              # @qt_version_tag_use
        .type   qt_version_tag_use,@object
        .section        .qtversion,"aGwR",@progbits,qt_version_tag_use,comdat
        .weak   qt_version_tag_use
        .p2align        3
qt_version_tag_use:
        .quad   qt_version_tag
        .quad   394240                          # 0x60400
        .size   qt_version_tag_use, 16

The notable changes here are that there is a symbol and that the section
is writable. The latter is required because we store the pointer to the
qt_version_tag variable instead of just an offset in the GOT. The total
number of relocations in the resulting binary remains the same. We've
actually shrunk the binary by one pointer size.

Change-Id: I74249c52dc02478ba93cfffd16d23951a6bcd784
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Thiago Macieira 2022-02-09 12:50:36 -08:00
parent 13ad42c679
commit 40c8bc4202
2 changed files with 23 additions and 16 deletions

View File

@ -43,18 +43,15 @@
#define SYM QT_MANGLE_NAMESPACE(qt_version_tag)
//#define SSYM QT_STRINGIFY(SYM)
#if defined(Q_CC_GNU) && defined(Q_OF_ELF) && !defined(Q_OS_ANDROID)
#if defined(Q_CC_GNU) && defined(Q_OF_ELF)
# define make_versioned_symbol2(sym, m, n, separator) \
Q_CORE_EXPORT extern const char sym ## _ ## m ## _ ## n = 0; \
asm(".symver " QT_STRINGIFY(sym) "_" QT_STRINGIFY(m) "_" QT_STRINGIFY(n) ", " \
QT_STRINGIFY(sym) separator "Qt_" QT_STRINGIFY(m) "." QT_STRINGIFY(n))
#elif defined(Q_OS_WIN)
#else
# define make_versioned_symbol2(sym, m, n, separator) \
Q_CORE_EXPORT extern const char sym ## _ ## m ## _ ## n = 0;
#else
# define make_versioned_symbol2(sym, m, n, separator)
#endif
#define make_versioned_symbol(sym, m, n, separator) make_versioned_symbol2(sym, m, n, separator)

View File

@ -86,19 +86,16 @@ QT_BEGIN_NAMESPACE
* const variable and tell the linker to merge them all via
* __declspec(selectany).
*
* ELF notes (Linux, FreeBSD):
* Unix notes:
*
* The symbol in question is simply "qt_version_tag" in both QtCore and in
* this ELF module, but it has an ELF version attached to it (see
* qversiontagging.cpp and QtFlagHandlingHelpers.cmake). That way, the error
* message from the dynamic linker will say it can't find version "Qt_6.x".
* On Unix, we use the same C++17 inline variable solution as MinGW, but we
* don't need the "__imp_" trick.
*
* This is currently implemented only for x86 by way of inline assembly. We
* inform the the linker to merge all TUs' .qtversion sections by way of the
* "comdat" attribute in the .section directive. The first pointer-sized
* variable is a GOT reference to qt_version_tag (that is: the address of
* qt_version_tag is stored in the .got section and the linker inserts the
* displacement to that pointer).
* Additionally, on ELF systems like Linux and FreeBSD, the symbol in question
* is simply "qt_version_tag" in both QtCore and in this ELF module, but it
* has an ELF version attached to it (see qversiontagging.cpp and
* QtFlagHandlingHelpers.cmake). That way, the error message from the dynamic
* linker will say it can't find version "Qt_6.x".
*/
namespace QtPrivate {
@ -133,6 +130,19 @@ struct QVersionTag
extern "C" const char * const imp; \
QT_VERSION_TAG_ATTRIBUTE QT_VERSION_TAG_SECTION QtPrivate::QVersionTag sym ## _used(&imp)
# define QT_VERSION_TAG(sym, imp) QT_VERSION_TAG2(sym, imp)
#elif defined(Q_CC_GNU) && __has_attribute(used)
# ifdef Q_OS_DARWIN
# define QT_VERSION_TAG_SECTION __attribute__((section("__DATA,.qtversion")))
# endif
# if __has_attribute(retain)
# define QT_VERSION_TAG_ATTRIBUTE __attribute__((visibility("hidden"), retain, used))
# else
# define QT_VERSION_TAG_ATTRIBUTE __attribute__((visibility("hidden"), used))
# endif
# define QT_VERSION_TAG2(sym, imp) \
extern "C" Q_DECL_IMPORT const char sym; \
QT_VERSION_TAG_ATTRIBUTE QT_VERSION_TAG_SECTION constexpr inline QtPrivate::QVersionTag sym ## _use(&sym)
# define QT_VERSION_TAG(sym, imp) QT_VERSION_TAG2(sym, imp)
#elif defined(Q_CC_GNU) && !defined(Q_OS_ANDROID)
# if defined(Q_PROCESSOR_X86) && (defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD_KERNEL))
# if defined(Q_PROCESSOR_X86_64) && QT_POINTER_SIZE == 8 // x86-64 64-bit