Bug 1555387 - Part 2: Add HuffmanDictionaryForMetadata. r=Yoric
authorTooru Fujisawa <arai_a@mac.com>
Fri, 22 Nov 2019 14:40:37 +0000
changeset 503368 32906323feef46004cbe87c420a56578e5190798
parent 503367 0983a81c46c52a2681112fedb286b739c92f482b
child 503369 47efbdaab5bf9bcd07c6a3114713146debb9ef0e
push id36833
push userbtara@mozilla.com
push dateFri, 22 Nov 2019 21:40:53 +0000
treeherdermozilla-central@2c912e46295e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersYoric
bugs1555387
milestone72.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 1555387 - Part 2: Add HuffmanDictionaryForMetadata. r=Yoric HuffmanDictionaryForMetadata behaves similar to HuffmanDictionary. HuffmanDictionary is used while reading prelude, to store intermediate state. HuffmanDictionaryForMetadata is used after reading prelude, to store the complete state of the dictionary, with single chunk of memory. HuffmanDictionaryForMetadata has payload to store items that is linked from tables. Differential Revision: https://phabricator.services.mozilla.com/D52956
js/src/frontend/BinASTTokenReaderContext.cpp
js/src/frontend/BinASTTokenReaderContext.h
--- a/js/src/frontend/BinASTTokenReaderContext.cpp
+++ b/js/src/frontend/BinASTTokenReaderContext.cpp
@@ -2876,16 +2876,244 @@ MOZ_MUST_USE JS::Result<Ok>
 HuffmanPreludeReader::readSingleValueTable<UnsignedLong>(
     GenericHuffmanTable& table, const UnsignedLong& entry) {
   BINJS_MOZ_TRY_DECL(index, reader_.readUnpackedLong());
   MOZ_TRY(
       table.initWithSingleValue(cx_, BinASTSymbol::fromUnsignedLong(index)));
   return Ok();
 }
 
+HuffmanDictionaryForMetadata::~HuffmanDictionaryForMetadata() {
+  for (size_t i = 0; i < numTables_; i++) {
+    tablesBase()[i].~GenericHuffmanTable();
+  }
+  for (size_t i = 0; i < numSingleTables_; i++) {
+    singleTablesBase()[i].~SingleLookupHuffmanTable();
+  }
+  for (size_t i = 0; i < numTwoTables_; i++) {
+    twoTablesBase()[i].~TwoLookupsHuffmanTable();
+  }
+}
+
+/* static */
+HuffmanDictionaryForMetadata* HuffmanDictionaryForMetadata::createFrom(
+    HuffmanDictionary* dictionary, TemporaryStorage* tempStorage) {
+  size_t numTables = dictionary->numTables();
+  size_t numHuffmanEntries = tempStorage->numHuffmanEntries();
+  size_t numInternalIndices = tempStorage->numInternalIndices();
+  while (numInternalIndices * sizeof(InternalIndex) % sizeof(uintptr_t) != 0) {
+    numInternalIndices++;
+  }
+  size_t numSingleTables = tempStorage->numSingleTables();
+  size_t numTwoTables = tempStorage->numTwoTables();
+
+  HuffmanDictionaryForMetadata* data =
+      static_cast<HuffmanDictionaryForMetadata*>(
+          js_malloc(totalSize(numTables, numHuffmanEntries, numInternalIndices,
+                              numSingleTables, numTwoTables)));
+  if (MOZ_UNLIKELY(!data)) {
+    return nullptr;
+  }
+
+  new (mozilla::KnownNotNull, data) HuffmanDictionaryForMetadata(
+      numTables, numHuffmanEntries, numInternalIndices, numSingleTables,
+      numTwoTables);
+
+  data->moveFrom(dictionary, tempStorage);
+  return data;
+}
+
+void HuffmanDictionaryForMetadata::moveFrom(HuffmanDictionary* dictionary,
+                                            TemporaryStorage* tempStorage) {
+  // Move tableIndices_ from HuffmanDictionary to the payload of
+  // HuffmanDictionaryForMetadata.
+  for (size_t i = 0; i < TableIdentity::Limit; i++) {
+    // HuffmanDictionaryForMetadata.tableIndices_ is initialized to
+    // UnreachableIndex, and we don't have to move if
+    // dictionary->status_[i] == HuffmanDictionary::TableStatus::Unreachable.
+    if (dictionary->status_[i] == HuffmanDictionary::TableStatus::Ready) {
+      tableIndices_[i] = dictionary->tableIndices_[i];
+    }
+  }
+
+  // Fill items of each array from the beginning.
+  auto tablePtr = tablesBase();
+  auto huffmanEntryPtr = huffmanEntriesBase();
+  auto internalIndexPtr = internalIndicesBase();
+  auto singleTablePtr = singleTablesBase();
+  auto twoTablePtr = twoTablesBase();
+
+  // Move the content of SingleLookupHuffmanTable from
+  // TemporaryStorage to the payload of HuffmanDictionaryForMetadata.
+  //
+  // SingleLookupHuffmanTable itself should already be moved to
+  // HuffmanDictionaryForMetadata.
+  auto moveSingleTableContent =
+      [&huffmanEntryPtr, &internalIndexPtr](SingleLookupHuffmanTable& table) {
+        // table.{values_,saturated_} points the spans in TemporaryStorage.
+        // Move those items to the payload and then update
+        // table.{values_,saturated_} to point that range.
+
+        {
+          size_t size = table.values_.size();
+          memmove(huffmanEntryPtr.get(), table.values_.data(),
+                  sizeof(HuffmanEntry) * size);
+          table.values_ = mozilla::MakeSpan(huffmanEntryPtr.get(), size);
+          huffmanEntryPtr += size;
+        }
+
+        {
+          size_t size = table.saturated_.size();
+          memmove(internalIndexPtr.get(), table.saturated_.data(),
+                  sizeof(InternalIndex) * size);
+          table.saturated_ = mozilla::MakeSpan(internalIndexPtr.get(), size);
+          internalIndexPtr += size;
+        }
+      };
+
+  // Move the content of TwoLookupsHuffmanTable from
+  // TemporaryStorage to the payload of HuffmanDictionaryForMetadata.
+  auto moveTwoTableContent =
+      [&huffmanEntryPtr, &singleTablePtr,
+       moveSingleTableContent](TwoLookupsHuffmanTable& table) {
+        // table.shortKeys_ instance itself is already moved.
+        // Move the contents to the payload.
+        moveSingleTableContent(table.shortKeys_);
+
+        // table.{values_,suffixTables_} points the spans in TemporaryStorage.
+        // Move those items to the payload and then update
+        // table.{values_,suffixTables_} to point that range.
+        // Also recursively move the content of suffixTables_.
+
+        {
+          size_t size = table.values_.size();
+          memmove(huffmanEntryPtr.get(), table.values_.data(),
+                  sizeof(HuffmanEntry) * size);
+          table.values_ = mozilla::MakeSpan(huffmanEntryPtr.get(), size);
+          huffmanEntryPtr += size;
+        }
+
+        {
+          size_t size = table.suffixTables_.size();
+          auto head = singleTablePtr.get();
+          for (auto& fromSubTable : table.suffixTables_) {
+            memmove(singleTablePtr.get(), &fromSubTable,
+                    sizeof(SingleLookupHuffmanTable));
+            auto& toSubTable = *singleTablePtr;
+            singleTablePtr++;
+
+            moveSingleTableContent(toSubTable);
+          }
+          table.suffixTables_ = mozilla::MakeSpan(head, size);
+        }
+      };
+
+  // Move the content of ThreeLookupsHuffmanTable from
+  // TemporaryStorage to the payload of HuffmanDictionaryForMetadata.
+  auto moveThreeTableContent =
+      [&huffmanEntryPtr, &twoTablePtr, moveSingleTableContent,
+       moveTwoTableContent](ThreeLookupsHuffmanTable& table) {
+        // table.shortKeys_ instance itself is already moved.
+        // Move the contents to the payload.
+        moveSingleTableContent(table.shortKeys_);
+
+        // table.{values_,suffixTables_} points the spans in TemporaryStorage.
+        // Move those items to the payload and then update
+        // table.{values_,suffixTables_} to point that range.
+        // Also recursively move the content of suffixTables_.
+
+        {
+          size_t size = table.values_.size();
+          memmove(huffmanEntryPtr.get(), table.values_.data(),
+                  sizeof(HuffmanEntry) * size);
+          table.values_ = mozilla::MakeSpan(huffmanEntryPtr.get(), size);
+          huffmanEntryPtr += size;
+        }
+
+        {
+          size_t size = table.suffixTables_.size();
+          auto head = twoTablePtr.get();
+          for (auto& fromSubTable : table.suffixTables_) {
+            memmove(twoTablePtr.get(), &fromSubTable,
+                    sizeof(TwoLookupsHuffmanTable));
+            auto& toSubTable = *twoTablePtr;
+            twoTablePtr++;
+
+            moveTwoTableContent(toSubTable);
+          }
+          table.suffixTables_ = mozilla::MakeSpan(head, size);
+        }
+      };
+
+  // Move tables from HuffmanDictionary to the payload of
+  // HuffmanDictionaryForMetadata, and then move contents of those tables
+  // to the payload of HuffmanDictionaryForMetadata.
+  for (size_t i = 0; i < numTables_; i++) {
+    auto& fromTable = dictionary->tableAtIndex(i);
+
+    if (fromTable.implementation_.is<SingleEntryHuffmanTable>() ||
+        fromTable.implementation_.is<TwoEntriesHuffmanTable>()) {
+      memmove(tablePtr.get(), &fromTable, sizeof(GenericHuffmanTable));
+      tablePtr++;
+    } else if (fromTable.implementation_.is<SingleLookupHuffmanTable>()) {
+      memmove(tablePtr.get(), &fromTable, sizeof(GenericHuffmanTable));
+      auto& specialized =
+          tablePtr->implementation_.as<SingleLookupHuffmanTable>();
+      tablePtr++;
+
+      moveSingleTableContent(specialized);
+    } else if (fromTable.implementation_.is<TwoLookupsHuffmanTable>()) {
+      memmove(tablePtr.get(), &fromTable, sizeof(GenericHuffmanTable));
+      auto& specialized =
+          tablePtr->implementation_.as<TwoLookupsHuffmanTable>();
+      tablePtr++;
+
+      moveTwoTableContent(specialized);
+    } else {
+      MOZ_ASSERT(fromTable.implementation_.is<ThreeLookupsHuffmanTable>());
+
+      memmove(tablePtr.get(), &fromTable, sizeof(GenericHuffmanTable));
+      auto& specialized =
+          tablePtr->implementation_.as<ThreeLookupsHuffmanTable>();
+      tablePtr++;
+
+      moveThreeTableContent(specialized);
+    }
+  }
+}
+
+/* static */
+size_t HuffmanDictionaryForMetadata::totalSize(size_t numTables,
+                                               size_t numHuffmanEntries,
+                                               size_t numInternalIndices,
+                                               size_t numSingleTables,
+                                               size_t numTwoTables) {
+  static_assert(alignof(GenericHuffmanTable) % sizeof(uintptr_t) == 0,
+                "should be aligned to pointer size");
+  static_assert(alignof(HuffmanEntry) % sizeof(uintptr_t) == 0,
+                "should be aligned to pointer size");
+  static_assert(alignof(SingleLookupHuffmanTable) % sizeof(uintptr_t) == 0,
+                "should be aligned to pointer size");
+  static_assert(alignof(TwoLookupsHuffmanTable) % sizeof(uintptr_t) == 0,
+                "should be aligned to pointer size");
+
+  // InternalIndex is known not to aligned to pointer size.
+  // Make sure `numInternalIndices` meets the requirement that
+  // the entire block size is aligned to pointer size.
+  MOZ_ASSERT(numInternalIndices * sizeof(InternalIndex) % sizeof(uintptr_t) ==
+             0);
+
+  return sizeof(HuffmanDictionaryForMetadata) +
+         numTables * sizeof(GenericHuffmanTable) +
+         numHuffmanEntries * sizeof(HuffmanEntry) +
+         numInternalIndices * sizeof(InternalIndex) +
+         numSingleTables * sizeof(SingleLookupHuffmanTable) +
+         numTwoTables * sizeof(TwoLookupsHuffmanTable);
+}
+
 HuffmanDictionary::~HuffmanDictionary() {
   for (size_t i = 0; i < nextIndex_; i++) {
     tableAtIndex(i).~GenericHuffmanTable();
   }
 }
 
 uint32_t HuffmanLookup::leadingBits(const uint8_t aBitLength) const {
   MOZ_ASSERT(aBitLength <= bitLength_);
--- a/js/src/frontend/BinASTTokenReaderContext.h
+++ b/js/src/frontend/BinASTTokenReaderContext.h
@@ -7,16 +7,17 @@
 #ifndef frontend_BinASTTokenReaderContext_h
 #define frontend_BinASTTokenReaderContext_h
 
 #include "mozilla/Array.h"         // mozilla::Array
 #include "mozilla/Assertions.h"    // MOZ_ASSERT
 #include "mozilla/Attributes.h"    // MOZ_MUST_USE, MOZ_STACK_CLASS
 #include "mozilla/IntegerRange.h"  // mozilla::IntegerRange
 #include "mozilla/Maybe.h"         // mozilla::Maybe
+#include "mozilla/RangedPtr.h"     // mozilla::RangedPtr
 #include "mozilla/Span.h"          // mozilla::Span
 #include "mozilla/Variant.h"       // mozilla::Variant
 
 #include <stddef.h>  // size_t
 #include <stdint.h>  // uint8_t, uint32_t
 
 #include "jstypes.h"                        // JS_PUBLIC_API
 #include "frontend/BinASTRuntimeSupport.h"  // CharSlice, BinASTSourceMetadata
@@ -527,16 +528,18 @@ class SingleLookupHuffmanTable {
 
   // The entries in this Huffman table, prepared for lookup.
   //
   // Invariant (once `init*` has been called):
   // - Length is `1 << largestBitLength_`.
   // - for all i, `saturated_[i] < values_.size()`
   mozilla::Span<InternalIndex> saturated_;
 
+  friend class HuffmanDictionaryForMetadata;
+
   // The maximal bitlength of a value in this table.
   //
   // Invariant (once `init*` has been called):
   // - `largestBitLength_ <= MAX_CODE_BIT_LENGTH`
   uint8_t largestBitLength_;
 
 #ifdef DEBUG
   Use use_;
@@ -763,16 +766,18 @@ class MultiLookupHuffmanTable {
   // maps to a subtable that holds all values associated
   // with a key that starts with `HuffmanKey(i, prefixBitLen)`.
   //
   // Note that, to allow the use of smaller tables, keys
   // inside the subtables have been stripped
   // from the prefix `HuffmanKey(i, prefixBitLen)`.
   mozilla::Span<Subtable> suffixTables_;
 
+  friend class HuffmanDictionaryForMetadata;
+
   // The maximal bitlength of a value in this table.
   //
   // Invariant (once `init*` has been called):
   // - `largestBitLength_ <= MAX_CODE_BIT_LENGTH`
   uint8_t largestBitLength_;
 
   friend class HuffmanPreludeReader;
 };
@@ -871,16 +876,18 @@ struct GenericHuffmanTable {
     return begin()->toKind() == BinASTKind::_Null;
   }
 
  private:
   mozilla::Variant<SingleEntryHuffmanTable, TwoEntriesHuffmanTable,
                    SingleLookupHuffmanTable, TwoLookupsHuffmanTable,
                    ThreeLookupsHuffmanTable, TableImplementationUninitialized>
       implementation_;
+
+  friend class HuffmanDictionaryForMetadata;
 };
 
 // Temporary space to allocate `T` typed items with less alloc/free calls,
 // to reduce mutex call inside allocator.
 //
 // Items are preallocated in `alloc` call, with at least `Chunk::DefaultSize`
 // items at once, and freed in the TemporaryStorageItem destructor all at once.
 //
@@ -926,38 +933,51 @@ class TemporaryStorageItem {
       js_free(chunk);
       chunk = next;
     }
   }
 
   // Allocate `count` number of `T` items and returns the pointer to the
   // first item.
   T* alloc(JSContext* cx, size_t count);
+
+  // The total number of used items in this storage.
+  size_t total() const { return total_; }
 };
 
 // Temporary storage used for dynamic allocations while reading the Huffman
 // prelude. Once reading is complete, we move them to metadata.
 //
 // Each items are allocated with `TemporaryStorageItem`, with less alloc/free
 // calls (See `TemporaryStorageItem` doc for more details).
 class TemporaryStorage {
-  using InternalIndex = uint8_t;
+  using InternalIndex = SingleLookupHuffmanTable::InternalIndex;
+
+  static_assert(sizeof(SingleLookupHuffmanTable::InternalIndex) ==
+                    sizeof(TwoLookupsHuffmanTable::InternalIndex),
+                "InternalIndex should be same between tables");
 
   TemporaryStorageItem<HuffmanEntry> huffmanEntries_;
   TemporaryStorageItem<InternalIndex> internalIndices_;
   TemporaryStorageItem<SingleLookupHuffmanTable> singleTables_;
   TemporaryStorageItem<TwoLookupsHuffmanTable> twoTables_;
 
  public:
   TemporaryStorage() {}
 
   // Allocate `count` number of `T` items and returns the span to point the
   // allocated items.
   template <typename T>
   JS::Result<mozilla::Span<T>> alloc(JSContext* cx, size_t count);
+
+  // The total number of used items in this storage.
+  size_t numHuffmanEntries() const { return huffmanEntries_.total(); }
+  size_t numInternalIndices() const { return internalIndices_.total(); }
+  size_t numSingleTables() const { return singleTables_.total(); }
+  size_t numTwoTables() const { return twoTables_.total(); }
 };
 
 // Handles the mapping from NormalizedInterfaceAndField and BinASTList to
 // the index inside the list of huffman tables.
 //
 // The mapping from `(Interface, Field) -> index` and `List -> index` is
 // extracted statically from the webidl specs.
 class TableIdentity {
@@ -972,21 +992,198 @@ class TableIdentity {
   explicit TableIdentity(NormalizedInterfaceAndField index)
       : index_(static_cast<size_t>(index.identity_)) {}
   explicit TableIdentity(BinASTList list)
       : index_(static_cast<size_t>(list) + ListIdentityBase) {}
 
   size_t toIndex() const { return index_; }
 };
 
-// A Huffman dictionary for the current file.
+class HuffmanDictionary;
+
+// A Huffman dictionary for the current file, used after reading the dictionary.
 //
 // A Huffman dictionary consists in a (contiguous) set of Huffman tables
-// to predict field values and a second (contiguous) set of Huffman tables
-// to predict list lengths.
+// to predict field values and list lengths, and (contiguous) sets of
+// items pointed by each tables.
+class HuffmanDictionaryForMetadata {
+  static const uint16_t UnreachableIndex = uint16_t(-1);
+
+  using InternalIndex = uint8_t;
+
+  HuffmanDictionaryForMetadata(size_t numTables, size_t numHuffmanEntries,
+                               size_t numInternalIndices,
+                               size_t numSingleTables, size_t numTwoTables)
+      : numTables_(numTables),
+        numHuffmanEntries_(numHuffmanEntries),
+        numInternalIndices_(numInternalIndices),
+        numSingleTables_(numSingleTables),
+        numTwoTables_(numTwoTables) {}
+
+  // This class is allocated with extra payload for storing tables and items.
+  // The full layout is the following:
+  //
+  //   HuffmanDictionaryForMetadata
+  //   GenericHuffmanTable[numTables_]
+  //   HuffmanEntry[numHuffmanEntries_]
+  //   InternalIndex[numInternalIndices_]
+  //   SingleLookupHuffmanTable[numSingleTables_]
+  //   TwoLookupsHuffmanTable[numTwoTables_]
+
+  // Accessors for the above payload.
+  // Each accessor returns the pointer to the first element of each array.
+  mozilla::RangedPtr<GenericHuffmanTable> tablesBase() {
+    auto p = reinterpret_cast<GenericHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1));
+    return mozilla::RangedPtr<GenericHuffmanTable>(p, p, p + numTables_);
+  }
+  const mozilla::RangedPtr<GenericHuffmanTable> tablesBase() const {
+    auto p = reinterpret_cast<GenericHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1));
+    return mozilla::RangedPtr<GenericHuffmanTable>(p, p, p + numTables_);
+  }
+
+  mozilla::RangedPtr<HuffmanEntry> huffmanEntriesBase() {
+    auto p = reinterpret_cast<HuffmanEntry*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_);
+    return mozilla::RangedPtr<HuffmanEntry>(p, p, p + numHuffmanEntries_);
+  }
+  const mozilla::RangedPtr<HuffmanEntry> huffmanEntriesBase() const {
+    auto p = reinterpret_cast<HuffmanEntry*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_);
+    return mozilla::RangedPtr<HuffmanEntry>(p, p, p + numHuffmanEntries_);
+  }
+
+  mozilla::RangedPtr<InternalIndex> internalIndicesBase() {
+    auto p = reinterpret_cast<InternalIndex*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_);
+    return mozilla::RangedPtr<InternalIndex>(p, p, p + numInternalIndices_);
+  }
+  const mozilla::RangedPtr<InternalIndex> internalIndicesBase() const {
+    auto p = reinterpret_cast<InternalIndex*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_);
+    return mozilla::RangedPtr<InternalIndex>(p, p, p + numInternalIndices_);
+  }
+
+  mozilla::RangedPtr<SingleLookupHuffmanTable> singleTablesBase() {
+    auto p = reinterpret_cast<SingleLookupHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_ +
+        sizeof(InternalIndex) * numInternalIndices_);
+    return mozilla::RangedPtr<SingleLookupHuffmanTable>(p, p,
+                                                        p + numSingleTables_);
+  }
+  const mozilla::RangedPtr<SingleLookupHuffmanTable> singleTablesBase() const {
+    auto p = reinterpret_cast<SingleLookupHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_ +
+        sizeof(InternalIndex) * numInternalIndices_);
+    return mozilla::RangedPtr<SingleLookupHuffmanTable>(p, p,
+                                                        p + numSingleTables_);
+  }
+
+  mozilla::RangedPtr<TwoLookupsHuffmanTable> twoTablesBase() {
+    auto p = reinterpret_cast<TwoLookupsHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_ +
+        sizeof(InternalIndex) * numInternalIndices_ +
+        sizeof(SingleLookupHuffmanTable) * numSingleTables_);
+    return mozilla::RangedPtr<TwoLookupsHuffmanTable>(p, p, p + numTwoTables_);
+  }
+  const mozilla::RangedPtr<TwoLookupsHuffmanTable> twoTablesBase() const {
+    auto p = reinterpret_cast<TwoLookupsHuffmanTable*>(
+        reinterpret_cast<uintptr_t>(this + 1) +
+        sizeof(GenericHuffmanTable) * numTables_ +
+        sizeof(HuffmanEntry) * numHuffmanEntries_ +
+        sizeof(InternalIndex) * numInternalIndices_ +
+        sizeof(SingleLookupHuffmanTable) * numSingleTables_);
+    return mozilla::RangedPtr<TwoLookupsHuffmanTable>(p, p, p + numTwoTables_);
+  }
+
+ public:
+  HuffmanDictionaryForMetadata() = delete;
+  ~HuffmanDictionaryForMetadata();
+
+  // Create HuffmanDictionaryForMetadata by moving data from
+  // HuffmanDictionary and items allocated in TemporaryStorage.
+  //
+  // After calling this, consumers shouldn't use `dictionary` and
+  // `tempStorage`.
+  static HuffmanDictionaryForMetadata* createFrom(
+      HuffmanDictionary* dictionary, TemporaryStorage* tempStorage);
+
+ private:
+  // Returns the total required size of HuffmanDictionaryForMetadata with
+  // extra payload to store items, in bytes.
+  static size_t totalSize(size_t numTables, size_t numHuffmanEntries,
+                          size_t numInternalIndices, size_t numSingleTables,
+                          size_t numTwoTables);
+
+  // After allocating HuffmanDictionaryForMetadata with extra payload,
+  // move data from HuffmanDictionary and items allocated in TemporaryStorage.
+  //
+  // Called by createFrom.
+  void moveFrom(HuffmanDictionary* dictionary, TemporaryStorage* tempStorage);
+
+ public:
+  bool isUnreachable(TableIdentity i) const {
+    return tableIndices_[i.toIndex()] == UnreachableIndex;
+  }
+
+  bool isReady(TableIdentity i) const {
+    return tableIndices_[i.toIndex()] != UnreachableIndex;
+  }
+
+  const GenericHuffmanTable& getTable(TableIdentity i) const {
+    MOZ_ASSERT(isReady(i));
+    return table(i);
+  }
+
+ private:
+  size_t numTables_ = 0;
+  size_t numHuffmanEntries_ = 0;
+  size_t numInternalIndices_ = 0;
+  size_t numSingleTables_ = 0;
+  size_t numTwoTables_ = 0;
+
+  uint16_t tableIndices_[TableIdentity::Limit] = {UnreachableIndex};
+
+  GenericHuffmanTable& table(TableIdentity i) {
+    return tableAtIndex(tableIndices_[i.toIndex()]);
+  }
+
+  const GenericHuffmanTable& table(TableIdentity i) const {
+    return tableAtIndex(tableIndices_[i.toIndex()]);
+  }
+
+  GenericHuffmanTable& tableAtIndex(size_t i) { return tablesBase()[i]; }
+
+  const GenericHuffmanTable& tableAtIndex(size_t i) const {
+    return tablesBase()[i];
+  }
+};
+
+// A Huffman dictionary for the current file, used while reading the dictionary.
+// When finished reading the dictionary, all data is moved to
+// `HuffmanDictionaryForMetadata`.
+//
+// A Huffman dictionary consists in a (contiguous) set of Huffman tables
+// to predict field values and list lengths.
+//
+// Each table can contain pointers to items allocated inside
+// `TemporaryStorageItem`.
 class HuffmanDictionary {
   // While reading the Huffman prelude, whenever we first encounter a
   // table with `Unreachable` status, we set its status with a `Initializing`
   // to mark that we should not attempt to read/initialize it again.
   // Once the table is initialized, it becomes `Ready`.
   enum class TableStatus : uint8_t {
     Unreachable,
     Initializing,
@@ -1030,16 +1227,18 @@ class HuffmanDictionary {
     return t;
   }
 
   const GenericHuffmanTable& getTable(TableIdentity i) const {
     MOZ_ASSERT(isReady(i));
     return table(i);
   }
 
+  size_t numTables() const { return nextIndex_; }
+
  private:
   // For the following purpose, tables are stored as an array of status
   // and a uninitialized buffer to store an array of tables.
   //
   //   * In most case a single BinAST file doesn't use all tables
   //   * GenericHuffmanTable constructor/destructor costs are not negligible,
   //     and we don't want to call them for unused tables
   //   * Initializing status for whether the table is used or not takes
@@ -1078,16 +1277,18 @@ class HuffmanDictionary {
 
   GenericHuffmanTable& tableAtIndex(size_t i) {
     return (reinterpret_cast<GenericHuffmanTable*>(tables_))[i];
   }
 
   const GenericHuffmanTable& tableAtIndex(size_t i) const {
     return (reinterpret_cast<const GenericHuffmanTable*>(tables_))[i];
   }
+
+  friend class HuffmanDictionaryForMetadata;
 };
 
 /**
  * A token reader implementing the "context" serialization format for BinAST.
  *
  * This serialization format, which is also supported by the reference
  * implementation of the BinAST compression suite, is designed to be
  * space- and time-efficient.