Commit 27975ea3 authored by Kris Maglione's avatar Kris Maglione
Browse files

Bug 1478124: Part 4b - Support loading components from static lookup tables. r=froydnj

This patch essentially creates a separate, static component database for
statically-defined CID and contract ID entries, and gives it precedence over
the runtime DB. It combines the two separate databases by updating existing
code to use lookup functions which understand both databases, and then access
all entries through wrappers which defer to the appropriate underlying type.

Static component entries require no runtime relocations, and require no
writable data allocation aside from one pointer-sized BSS entry per CID, and
one bit of BSS per contract ID.

To achieve this, all strings in the static lookup tables are stored as indexes
into a static string table, all constructor functions live in a switch
statement which compiles to a relative jump table, and all writable data for
static entries is accessed by indexed lookups into BSS arrays.

We also avoid creating nsIFactory entries for static components when possible
by adding a CreateInstance method to nsFactoryEntry and the corresponding
entry wrapper to directly call the appropriate constructor method, and only
create a factory object when required by external code.

Differential Revision: https://phabricator.services.mozilla.com/D15035

--HG--
extra : rebase_source : 903a6f31c6290d0090e6765e0e317d1f749c5855
extra : source : b8d2dfdfc324c53ce5aacc822ce52d4e2bfdc31a
parent cc171935
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -127,13 +127,13 @@ class TestPACMan : public ::testing::Test {

  virtual void SetUp() {
    ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref));
    nsFactoryEntry* factoryEntry =
        nsComponentManagerImpl::gComponentManager->GetFactoryEntry(
            kNS_TESTDHCPCLIENTSERVICE_CID);
    if (factoryEntry) {
      nsresult rv =
          nsComponentManagerImpl::gComponentManager->UnregisterFactory(
              kNS_TESTDHCPCLIENTSERVICE_CID, factoryEntry->mFactory);
    nsCOMPtr<nsIFactory> factory;
    nsresult rv = nsComponentManagerImpl::gComponentManager->GetClassObject(
        kNS_TESTDHCPCLIENTSERVICE_CID, NS_GET_IID(nsIFactory),
        getter_AddRefs(factory));
    if (NS_SUCCEEDED(rv) && factory) {
      rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory(
          kNS_TESTDHCPCLIENTSERVICE_CID, factory);
      ASSERT_EQ(NS_OK, rv);
    }
    nsComponentManagerImpl::gComponentManager->RegisterModule(
+228 −0
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#include "StaticComponents.h"

#include "mozilla/PerfectHash.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsComponentManager.h"
#include "nsIFactory.h"
#include "nsISupports.h"
#include "nsString.h"

// Cleanup pollution from zipstruct.h
#undef UNSUPPORTED

// Public includes
//# @includes@

// Relative includes
//# @relative_includes@

//# @decls@

namespace mozilla {
namespace xpcom {

namespace {
// Template helpers for constructor function sanity checks.
template <typename T>
struct RemoveAlreadyAddRefed {
  using Type = T;
};

template <typename T>
struct RemoveAlreadyAddRefed<already_AddRefed<T>> {
  using Type = T;
};
}  // anonymous namespace


uint8_t gInvalidContracts[kContractCount / 8 + 1];

static StaticRefPtr<nsISupports> gServiceInstances[kStaticModuleCount];

uint8_t gInitCalled[kModuleInitCount / 8 + 1];

static const char gStrings[] =
//# @strings@
  "";

const StaticCategory gStaticCategories[kStaticCategoryCount] = {
//# @categories@
};
const StaticCategoryEntry gStaticCategoryEntries[] = {
//# @category_entries@
};

/**
 * Returns a nsCString corresponding to the given entry in the `gStrings` string
 * table. The resulting nsCString points directly to static storage, and does
 * not incur any memory allocation overhead.
 */
static inline nsCString GetString(const StringOffset& aOffset) {
  const char* str = &gStrings[aOffset.mOffset];
  nsCString result;
  result.AssignLiteral(str, strlen(str));
  return result;
}

nsCString ContractEntry::ContractID() const {
  return GetString(mContractID);
}

bool ContractEntry::Matches(const nsACString& aContractID) const {
  return aContractID == ContractID() && Module().Active();
}


//# @module_cid_table@

//# @module_contract_id_table@

static inline bool CalledInit(size_t aIdx) {
  return GetBit(gInitCalled, aIdx);
}

static nsresult CallInitFunc(size_t aIdx) {
  if (CalledInit(aIdx)) {
    return NS_OK;
  }

  nsresult rv = NS_OK;
  switch (aIdx) {
//# @init_funcs@
  }

  SetBit(gInitCalled, aIdx);

  MOZ_ASSERT(NS_SUCCEEDED(rv));
  return rv;
}

static void CallUnloadFuncs() {
//# @unload_funcs@
}

static nsresult CreateInstanceImpl(ModuleID aID, nsISupports* aOuter,
                                   const nsIID& aIID, void** aResult) {
  if (aOuter) {
    return NS_ERROR_NO_AGGREGATION;
  }

  // The full set of constructors for all static modules.
  // This switch statement will be compiled to a relative address jump table
  // with no runtime relocations and a single indirect jump.
  switch (aID) {
//# @constructors@
  }

  MOZ_ASSERT_UNREACHABLE("Constructor didn't return");
  return NS_ERROR_FAILURE;
}


namespace {

class StaticModuleFactory final : public nsIFactory {
  NS_DECL_ISUPPORTS
  NS_DECL_NSIFACTORY

  explicit StaticModuleFactory(ModuleID aID) : mID(aID) {}

private:
  ~StaticModuleFactory() = default;

  const ModuleID mID;
};

NS_IMPL_ISUPPORTS(StaticModuleFactory, nsIFactory)

NS_IMETHODIMP StaticModuleFactory::CreateInstance(nsISupports* aOuter,
                                                  const nsIID& aIID,
                                                  void** aResult) {
  return CreateInstanceImpl(mID, aOuter, aIID, aResult);
}

NS_IMETHODIMP StaticModuleFactory::LockFactory(bool aLock) {
  MOZ_CRASH("LockFactory is no longer a thing");
  return NS_ERROR_NOT_IMPLEMENTED;
}

}  // anonymous namespace


already_AddRefed<nsIFactory> StaticModule::GetFactory() const {
  return do_AddRef(new StaticModuleFactory(ID()));
}

bool StaticModule::Active() const {
  return FastProcessSelectorMatches(mProcessSelector);
}

nsresult StaticModule::CreateInstance(nsISupports* aOuter, const nsIID& aIID,
                                      void** aResult) const {
  return CreateInstanceImpl(ID(), aOuter, aIID, aResult);
}


nsISupports* StaticModule::ServiceInstance() const {
  return gServiceInstances[Idx()];
}

void StaticModule::SetServiceInstance(
    already_AddRefed<nsISupports> aInst) const {
  gServiceInstances[Idx()] = aInst;
}


nsCString StaticCategoryEntry::Entry() const {
  return GetString(mEntry);
}
nsCString StaticCategoryEntry::Value() const {
  return GetString(mValue);
}
bool StaticCategoryEntry::Active() const {
  return FastProcessSelectorMatches(mProcessSelector);
}

nsCString StaticCategory::Name() const {
  return GetString(mName);
}


/* static */ const StaticModule* StaticComponents::LookupByCID(
    const nsID& aCID) {
  return ModuleByCID(aCID);
}

/* static */ const StaticModule* StaticComponents::LookupByContractID(
    const nsACString& aContractID) {
  if (const ContractEntry* entry = LookupContractID(aContractID)) {
    if (!entry->Invalid()) {
      return &entry->Module();
    }
  }
  return nullptr;
}

/* static */ bool StaticComponents::InvalidateContractID(
    const nsACString& aContractID, bool aInvalid) {
  if (const ContractEntry* entry = LookupContractID(aContractID)) {
    entry->SetInvalid(aInvalid);
    return true;
  }
  return false;
}

/* static */ void StaticComponents::Shutdown() {
  CallUnloadFuncs();
}

}  // namespace xpcom
}  // namespace mozilla
+203 −0
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#ifndef StaticComponents_h
#define StaticComponents_h

#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Module.h"
#include "nsID.h"
#include "nsStringFwd.h"
#include "nscore.h"

#include "mozilla/Components.h"
#include "StaticComponentData.h"

class nsIFactory;
class nsISupports;

namespace mozilla {
namespace xpcom {

struct ContractEntry;
struct StaticModule;

struct StaticCategoryEntry;
struct StaticCategory;

extern const StaticModule gStaticModules[kStaticModuleCount];

extern const ContractEntry gContractEntries[kContractCount];
extern uint8_t gInvalidContracts[kContractCount / 8 + 1];

extern const StaticCategory gStaticCategories[kStaticCategoryCount];
extern const StaticCategoryEntry gStaticCategoryEntries[];

template <size_t N>
static inline bool GetBit(const uint8_t (&aBits)[N], size_t aBit) {
  static constexpr size_t width = sizeof(aBits[0]) * 8;

  size_t idx = aBit / width;
  MOZ_ASSERT(idx < N);
  return aBits[idx] & (1 << (aBit % width));
}

template <size_t N>
static inline void SetBit(uint8_t (&aBits)[N], size_t aBit,
                          bool aValue = true) {
  static constexpr size_t width = sizeof(aBits[0]) * 8;

  size_t idx = aBit / width;
  MOZ_ASSERT(idx < N);
  if (aValue) {
    aBits[idx] |= 1 << (aBit % width);
  } else {
    aBits[idx] &= ~(1 << (aBit % width));
  }
}

/**
 * Represents a string entry in the static string table. Can be converted to a
 * nsCString using GetString() in StaticComponents.cpp.
 *
 * This is a struct rather than a pure offset primarily for the purposes of type
 * safety, but also so that it can easily be extended to include a static length
 * in the future, if efficiency concerns warrant it.
 */
struct StringOffset final {
  uint32_t mOffset;
};

/**
 * Represents a static component entry defined in a `Classes` list in an XPCOM
 * manifest. Handles creating instances of and caching service instances for
 * that class.
 */
struct StaticModule {
  nsID mCID;
  Module::ProcessSelector mProcessSelector;

  const nsID& CID() const { return mCID; }

  ModuleID ID() const { return ModuleID(this - gStaticModules); }

  /**
   * Returns this entry's index in the gStaticModules array.
   */
  size_t Idx() const { return size_t(ID()); }

  /**
   * Returns true if this entry is active. Typically this will only return false
   * if the entry's process selector does not match this process.
   */
  bool Active() const;

  already_AddRefed<nsIFactory> GetFactory() const;

  nsresult CreateInstance(nsISupports* aOuter, const nsIID& aIID,
                          void** aResult) const;

  nsISupports* ServiceInstance() const;
  void SetServiceInstance(already_AddRefed<nsISupports> aInst) const;
};

/**
 * Represents a static mapping between a contract ID string and a StaticModule
 * entry.
 */
struct ContractEntry final {
  StringOffset mContractID;
  ModuleID mModuleID;

  size_t Idx() const { return this - gContractEntries; }

  nsCString ContractID() const;

  const StaticModule& Module() const {
    return gStaticModules[size_t(mModuleID)];
  }

  /**
   * Returns true if this entry's underlying module is active, and its contract
   * ID matches the given contract ID string. This is used by the PerfectHash
   * function to determine whether to return a result for this entry.
   */
  bool Matches(const nsACString& aContractID) const;

  /**
   * Returns true if this entry has been invalidated, and should be ignored.
   *
   * Contract IDs may be overwritten at runtime. When that happens for a static
   * contract ID, we mark its entry invalid, and ignore it thereafter.
   */
  bool Invalid() const { return GetBit(gInvalidContracts, Idx()); }

  /**
   * Marks this entry invalid (or unsets the invalid bit if aInvalid is false),
   * after which it will be ignored in contract ID lookup attempts. See
   * `Invalid()` above.
   */
  void SetInvalid(bool aInvalid = true) const {
    return SetBit(gInvalidContracts, Idx(), aInvalid);
  }
};

/**
 * Represents a declared category manager entry declared in an XPCOM manifest.
 *
 * The entire set of static category entries is read at startup and loaded into
 * the category manager's dynamic hash tables, so there is memory and
 * initialization overhead for each entry in these tables. This may be further
 * optimized in the future to reduce some of that overhead.
 */
struct StaticCategoryEntry final {
  StringOffset mEntry;
  StringOffset mValue;
  Module::ProcessSelector mProcessSelector;

  nsCString Entry() const;
  nsCString Value() const;
  bool Active() const;
};

struct StaticCategory final {
  StringOffset mName;
  uint16_t mStart;
  uint16_t mCount;

  nsCString Name() const;

  const StaticCategoryEntry* begin() const {
    return &gStaticCategoryEntries[mStart];
  }
  const StaticCategoryEntry* end() const {
    return &gStaticCategoryEntries[mStart + mCount];
  }
};

class StaticComponents final {
 public:
  static const StaticModule* LookupByCID(const nsID& aCID);

  static const StaticModule* LookupByContractID(const nsACString& aContractID);

  /**
   * Marks a static contract ID entry invalid (or unsets the invalid bit if
   * aInvalid is false). See `CategoryEntry::Invalid()`.
   */
  static bool InvalidateContractID(const nsACString& aContractID,
                                   bool aInvalid = true);

  /**
   * Calls any module unload from manifests whose components have been loaded.
   */
  static void Shutdown();
};

}  // namespace xpcom
}  // namespace mozilla

#endif  // defined StaticComponents_h
+674 −0

File added.

Preview size limit exceeded, changes collapsed.

+20 −0
Original line number Diff line number Diff line
@@ -29,6 +29,20 @@ EXPORTS.mozilla += [
    'ModuleUtils.h',
]

if CONFIG['COMPILE_ENVIRONMENT']:
    EXPORTS.mozilla += [
        '!Components.h',
    ]

    generated = ('Components.h', 'StaticComponentData.h',
                 'StaticComponents.cpp')

    GENERATED_FILES += [generated]

    gen = GENERATED_FILES[generated]
    gen.script = 'gen_static_components.py'
    gen.inputs += ['!manifest-lists.json', 'StaticComponents.cpp.in']

UNIFIED_SOURCES += [
    'GenericFactory.cpp',
    'ManifestParser.cpp',
@@ -38,6 +52,10 @@ UNIFIED_SOURCES += [
    'nsComponentManagerUtils.cpp',
]

SOURCES += [
    '!StaticComponents.cpp',
]

FINAL_LIBRARY = 'xul'

LOCAL_INCLUDES += [
@@ -53,3 +71,5 @@ LOCAL_INCLUDES += [

if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
    CXXFLAGS += CONFIG['TK_CFLAGS']

include('/ipc/chromium/chromium-config.mozbuild')
Loading