Bug 1471132 - Change how static xpcom components are linked. r?froydnj draft
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 26 Jun 2018 14:40:51 +0900
changeset 810989 d985aaae83e5837be59887a250332fdf56dac5d0
parent 810988 331edc9003b50826ef2dd34bba40e065c453d8ca
child 810990 fa8cd8fc0361753e7ffb0f672283949c92658d3d
push id114180
push userbmo:mh+mozilla@glandium.org
push dateTue, 26 Jun 2018 21:43:49 +0000
reviewersfroydnj
bugs1471132
milestone63.0a1
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()
 {