Bug 1471132 - Change how static xpcom components are linked. r=froydnj
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 26 Jun 2018 14:40:51 +0900
changeset 423766 7839040f0d428201a5d9afed324b7763ed555024
parent 423765 dfd0afd7b60fe420b67ebee82634915efaaf48b0
child 423767 32d574a02a2eb31ccee9259a210d67edaab0fb03
push id34192
push userdluca@mozilla.com
push dateWed, 27 Jun 2018 10:24:39 +0000
treeherdermozilla-central@9da8a5d80b98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1471132
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1471132 - Change how static xpcom components are linked. r=froydnj Overall, this makes the whole setup less fragile, and make it work with LTO in more situations.
python/mozbuild/mozbuild/action/check_binary.py
toolkit/library/StaticXULComponents.ld
toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp
toolkit/library/StaticXULComponentsEnd/moz.build
toolkit/library/StaticXULComponentsStart.cpp
toolkit/library/moz.build
toolkit/toolkit.mozbuild
xpcom/components/Module.h
xpcom/components/nsComponentManager.cpp
--- a/python/mozbuild/mozbuild/action/check_binary.py
+++ b/python/mozbuild/mozbuild/action/check_binary.py
@@ -171,52 +171,76 @@ def check_nsmodules(target, binary):
             data = line.split(None, 3)
             if data and len(data) == 4 and data[0].isdigit() and \
                     ishex(data[1]) and ishex(data[2]):
                 # - Some symbols in the table can be aliases, and appear as
                 #   `foo = bar`.
                 # - The MSVC mangling has some type info following `@@`
                 # - Any namespacing that can happen on the symbol appears as a
                 #   suffix, after a `@`.
-                name = data[3].split(' = ')[0].split('@@')[0].split('@')[0]
-                if name.endswith('_NSModule'):
-                    symbols.append((int(data[2], 16), 0, name.lstrip('?')))
+                # - Mangled symbols are prefixed with `?`.
+                name = data[3].split(' = ')[0].split('@@')[0].split('@')[0] \
+                              .lstrip('?')
+                if name.endswith('_NSModule') or name in (
+                        '__start_kPStaticModules',
+                        '__stop_kPStaticModules'):
+                    symbols.append((int(data[2], 16), GUESSED_NSMODULE_SIZE,
+                                    name))
     else:
-        for line in get_output(target['nm'], '-gP', binary):
+        for line in get_output(target['nm'], '-P', binary):
             data = line.split()
-            # NSModules symbols end with _NSModule or _NSModuleE when
-            # C++-mangled.
-            if len(data) == 4 and data[0].endswith(('_NSModule', '_NSModuleE')):
+            # Some symbols may not have a size listed at all.
+            if len(data) == 3:
+                data.append('0')
+            if len(data) == 4:
                 sym, _, addr, size = data
-                symbols.append((int(addr, 16), int(size, 16), sym))
+                # NSModules symbols end with _NSModule or _NSModuleE when
+                # C++-mangled.
+                if sym.endswith(('_NSModule', '_NSModuleE')):
+                    # On mac, nm doesn't actually print anything other than 0
+                    # for the size. So take our best guess.
+                    size = int(size, 16) or GUESSED_NSMODULE_SIZE
+                    symbols.append((int(addr, 16), size, sym))
+                elif sym.endswith(('__start_kPStaticModules',
+                                   '__stop_kPStaticModules')):
+                    # On ELF and mac systems, these symbols have no size, such
+                    # that the first actual NSModule has the same address as
+                    # the start symbol.
+                    symbols.append((int(addr, 16), 0, sym))
     if not symbols:
         raise RuntimeError('Could not find NSModules')
 
     def print_symbols(symbols):
         for addr, size, sym in symbols:
             print('%x %d %s' % (addr, size, sym))
 
     symbols = sorted(symbols)
     next_addr = None
     for addr, size, sym in symbols:
         if next_addr is not None and next_addr != addr:
             print_symbols(symbols)
             raise RuntimeError('NSModules are not adjacent')
-        # On mac, nm doesn't actually print anything other than 0 for the
-        # size. So take our best guess. On Windows, dumpbin doesn't give us
-        # any size at all.
-        if size == 0:
-            size = GUESSED_NSMODULE_SIZE
         next_addr = addr + size
+
+    # The mac linker doesn't emit the start/stop symbols in the symbol table.
+    # We'll just assume it did the job correctly.
+    if get_type(binary) == MACHO:
+        return
+
     first = symbols[0][2]
     last = symbols[-1][2]
     # On some platforms, there are extra underscores on symbol names.
-    if first.lstrip('_') != 'start_kPStaticModules_NSModule' or \
-            last.lstrip('_') != 'end_kPStaticModules_NSModule':
+    if first.lstrip('_') != 'start_kPStaticModules' or \
+            last.lstrip('_') != 'stop_kPStaticModules':
         print_symbols(symbols)
+        syms = set(sym for add, size, sym in symbols)
+        if 'start_kPStaticModules' not in syms:
+            raise RuntimeError('Could not find start_kPStaticModules symbol')
+        if 'stop_kPStaticModules' not in syms:
+            raise RuntimeError('Could not find stop_kPStaticModules symbol')
         raise RuntimeError('NSModules are not ordered appropriately')
 
 
 def check_pt_load(target, binary):
     if target is HOST or get_type(binary) != ELF or not is_libxul(binary):
         raise Skip()
     count = 0
     for line in get_output(target['readelf'], '-l', binary):
--- a/toolkit/library/StaticXULComponents.ld
+++ b/toolkit/library/StaticXULComponents.ld
@@ -1,5 +1,7 @@
 SECTIONS {
   .data.rel.ro : {
-    *(.kPStaticModules)
+    PROVIDE_HIDDEN(__start_kPStaticModules = .);
+    *(kPStaticModules)
+    PROVIDE_HIDDEN(__stop_kPStaticModules = .);
   }
 }
deleted file mode 100644
--- a/toolkit/library/StaticXULComponentsEnd/StaticXULComponentsEnd.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "mozilla/Module.h"
-
-/* Ensure end_kPStaticModules is at the end of the .kPStaticModules section
- * on Windows. Somehow, placing the object last is not enough with PGO/LTCG. */
-#ifdef _MSC_VER
-/* Sections on Windows are in two parts, separated with $. When linking,
- * sections with the same first part are all grouped, and ordered
- * alphabetically with the second part as sort key. */
-#  pragma section(".kPStaticModules$Z", read)
-#  undef NSMODULE_SECTION
-#  define NSMODULE_SECTION __declspec(allocate(".kPStaticModules$Z"), dllexport)
-#elif MOZ_LTO
-/* Clang+lld with LTO does not order modules correctly either, but fortunately
- * the same trick works. */
-#  undef NSMODULE_SECTION
-#  define NSMODULE_SECTION __attribute__((section(".kPStaticModules$Z"), visibility("default")))
-#endif
-/* This could be null, but this needs a dummy value to ensure it actually ends
- * up in the same section as other NSMODULE_DEFNs, instead of being moved to a
- * separate readonly section. */
-NSMODULE_DEFN(end_kPStaticModules) = (mozilla::Module*)&NSMODULE_NAME(end_kPStaticModules);
deleted file mode 100644
--- a/toolkit/library/StaticXULComponentsEnd/moz.build
+++ /dev/null
@@ -1,17 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-SOURCES += [
-    'StaticXULComponentsEnd.cpp',
-]
-
-# Don't let LTO reorder StaticXULComponentsStart.o.
-for f in CONFIG['OS_CXXFLAGS']:
-    if f.startswith('-flto'):
-        SOURCES['StaticXULComponentsEnd.cpp'].flags += ['-fno-lto']
-        break
-
-Library('StaticXULComponentsEnd')
-
-DEFINES['MOZILLA_INTERNAL_API'] = True
deleted file mode 100644
--- a/toolkit/library/StaticXULComponentsStart.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "mozilla/Module.h"
-
-/* This could be null, but this needs a dummy value to ensure it actually ends
- * up in the same section as other NSMODULE_DEFNs, instead of being moved to a
- * separate readonly section. */
-NSMODULE_DEFN(start_kPStaticModules) = (mozilla::Module*)&NSMODULE_NAME(start_kPStaticModules);
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -83,30 +83,16 @@ def Libxul(name):
         LDFLAGS += ['-NATVIS:%s/toolkit/library/gecko.natvis' % TOPSRCDIR]
 
 Libxul('xul')
 
 FORCE_STATIC_LIB = True
 
 STATIC_LIBRARY_NAME = 'xul_s'
 
-SOURCES += [
-    'StaticXULComponentsStart.cpp',
-]
-
-# This, combined with the fact the file is first, makes the start pointer
-# it contains first in Windows PGO builds.
-SOURCES['StaticXULComponentsStart.cpp'].no_pgo = True
-
-# Don't let LTO reorder StaticXULComponentsStart.o.
-for f in CONFIG['OS_CXXFLAGS']:
-    if f.startswith('-flto'):
-        SOURCES['StaticXULComponentsStart.cpp'].flags += ['-fno-lto']
-        break
-
 if CONFIG['OS_ARCH'] == 'WINNT':
     SOURCES += [
         'nsDllMain.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/config',
     # need widget/windows for resource.h (included from widget.rc)
@@ -359,15 +345,11 @@ if CONFIG['COMPILE_ENVIRONMENT']:
     FINAL_TARGET_FILES += ['!dependentlibs.list', '!dependentlibs.list.gtest']
 
     if CONFIG['OS_ARCH'] == 'Linux' and CONFIG['OS_TARGET'] != 'Android':
         GENERATED_FILES += ['symverscript']
         GENERATED_FILES['symverscript'].script = 'gen_symverscript.py'
         GENERATED_FILES['symverscript'].inputs = ['symverscript.in']
         SYMBOLS_FILE = '!symverscript'
 
-# This library needs to be last to make XPCOM module registration work.
-USE_LIBS += ['StaticXULComponentsEnd']
-
-# The above library needs to be last for C++ purposes.  This library,
-# however, is entirely composed of Rust code, and needs to come after
+# This library is entirely composed of Rust code, and needs to come after
 # all the C++ code so any possible C++ -> Rust calls can be resolved.
 USE_LIBS += ['gkrust']
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -145,17 +145,16 @@ DIRS += [
 ]
 
 if CONFIG['MOZ_PREF_EXTENSIONS']:
     DIRS += ['/extensions/pref']
 
 DIRS += [
     '/devtools',
     '/toolkit/library',
-    '/toolkit/library/StaticXULComponentsEnd',
     '/services',
     '/startupcache',
     '/js/ductwork/debugger',
     '/other-licenses/snappy',
 ]
 
 if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
     DIRS += ['/toolkit/system/gnome']
--- a/xpcom/components/Module.h
+++ b/xpcom/components/Module.h
@@ -134,21 +134,21 @@ struct Module
 #if defined(MOZILLA_INTERNAL_API)
 #  define NSMODULE_NAME(_name) _name##_NSModule
 #  if defined(_MSC_VER)
 #    pragma section(".kPStaticModules$M", read)
 #    pragma comment(linker, "/merge:.kPStaticModules=.rdata")
 #    define NSMODULE_SECTION __declspec(allocate(".kPStaticModules$M"), dllexport)
 #  elif defined(__GNUC__)
 #    if defined(__ELF__)
-#      define NSMODULE_SECTION __attribute__((section(".kPStaticModules"), visibility("default")))
+#      define NSMODULE_SECTION __attribute__((section("kPStaticModules"), visibility("default")))
 #    elif defined(__MACH__)
 #      define NSMODULE_SECTION __attribute__((section("__DATA, .kPStaticModules"), visibility("default")))
 #    elif defined (_WIN32)
-#      define NSMODULE_SECTION __attribute__((section(".kPStaticModules"), dllexport))
+#      define NSMODULE_SECTION __attribute__((section("kPStaticModules"), dllexport))
 #    endif
 #  endif
 #  if !defined(NSMODULE_SECTION)
 #    error Do not know how to define sections.
 #  endif
 #  if defined(MOZ_HAVE_ASAN_BLACKLIST)
 #    define NSMODULE_ASAN_BLACKLIST __attribute__((no_sanitize_address))
 #  else
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -242,31 +242,103 @@ nsComponentManagerImpl::nsComponentManag
   , mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH)
   , mLock("nsComponentManagerImpl.mLock")
   , mStatus(NOT_INITIALIZED)
 {
 }
 
 nsTArray<const mozilla::Module*>* nsComponentManagerImpl::sStaticModules;
 
-NSMODULE_DEFN(start_kPStaticModules);
-NSMODULE_DEFN(end_kPStaticModules);
+/* NSMODULE_DEFN places NSModules in specific sections, as per Module.h.
+ * The linker will group them all together, and we use tricks below to
+ * find the start and end of the grouped list of NSModules.
+ *
+ * On Windows, all the symbols in the .kPStaticModules* sections are
+ * grouped together, by lexical order of the section names. The NSModules
+ * themselves are in .kPStaticModules$M. We use the section name
+ * .kPStaticModules$A to add an empty entry that will be the first,
+ * and the section name .kPStaticModules$Z for another empty entry that
+ * will be the last. We make both null pointers, and skip them in the
+ * AllStaticModules range-iterator.
+ *
+ * On ELF (Linux, BSDs, ...), as well as Mingw builds, the linker itself
+ * provides symbols for the beginning and end of the consolidated section,
+ * but it only does so for sections that can be represented as C identifiers,
+ * so the section is named `kPStaticModules` rather than `.kPStaticModules`.
+ *
+ * We also use a linker script with BFD ld so that the sections end up
+ * folded into the .data.rel.ro section, but that actually breaks the above
+ * described behavior, so the linker script contains an additional trick
+ * to still provide the __start and __stop symbols (the linker script
+ * doesn't work with gold or lld).
+ *
+ * On Darwin, a similar setup is available through the use of some
+ * synthesized symbols (section$...).
+ *
+ * On all platforms, the __stop_kPStaticModules symbol is past all NSModule
+ * pointers.
+ * On Windows, the __start_kPStaticModules symbol points to an empty pointer
+ * preceding the first NSModule pointer. On other platforms, it points to the
+ * first NSModule pointer.
+ */
+
+// Dummy class to define a range-iterator for the static modules.
+class AllStaticModules {};
+
+#if defined(_MSC_VER)
+
+#  pragma section(".kPStaticModules$A", read)
+NSMODULE_ASAN_BLACKLIST __declspec(allocate(".kPStaticModules$A"), dllexport)
+extern mozilla::Module const* const __start_kPStaticModules = nullptr;
+
+mozilla::Module const* const* begin(AllStaticModules& _) {
+    return &__start_kPStaticModules + 1;
+}
+
+#  pragma section(".kPStaticModules$Z", read)
+NSMODULE_ASAN_BLACKLIST __declspec(allocate(".kPStaticModules$Z"), dllexport)
+extern mozilla::Module const* const __stop_kPStaticModules = nullptr;
+
+#else
+
+#  if defined(__ELF__) || (defined(_WIN32) && defined(__GNUC__))
+
+extern "C" mozilla::Module const* const __start_kPStaticModules;
+extern "C" mozilla::Module const* const __stop_kPStaticModules;
+
+#  elif defined(__MACH__)
+
+extern mozilla::Module const *const __start_kPStaticModules __asm("section$start$__DATA$.kPStaticModules");
+extern mozilla::Module const* const __stop_kPStaticModules __asm("section$end$__DATA$.kPStaticModules");
+
+#  else
+#    error Do not know how to find NSModules.
+#  endif
+
+mozilla::Module const* const* begin(AllStaticModules& _) {
+    return &__start_kPStaticModules;
+}
+
+#endif
+
+mozilla::Module const* const* end(AllStaticModules& _) {
+    return &__stop_kPStaticModules;
+}
 
 /* static */ void
 nsComponentManagerImpl::InitializeStaticModules()
 {
   if (sStaticModules) {
     return;
   }
 
   sStaticModules = new nsTArray<const mozilla::Module*>;
-  for (const mozilla::Module * const* staticModules =
-         &NSMODULE_NAME(start_kPStaticModules) + 1;
-       staticModules < &NSMODULE_NAME(end_kPStaticModules); ++staticModules)
-    sStaticModules->AppendElement(*staticModules);
+  for (auto module : AllStaticModules()) {
+    sStaticModules->AppendElement(module);
+  }
 }
 
 nsTArray<nsComponentManagerImpl::ComponentLocation>*
 nsComponentManagerImpl::sModuleLocations;
 
 /* static */ void
 nsComponentManagerImpl::InitializeModuleLocations()
 {