Rewrite TypeLookupTable.

Improve bit-packing of the data to store twice as many bits
of the hash as previously. Check for bucket mismatch after
the first partial hash conflict (previous comments alluded
to the bucket check but it was not implemented).

Avoid an unnecessary unique_ptr<> indirection by making the
TypeLookupTable moveable.

Test: Rely on Treehugger.
Bug: 79514364
Change-Id: I9fa6f712b037a6e6904d09c88670966486f56621
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 4046dc1..9951668 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -4032,13 +4032,13 @@
     // TypeLookupTable allocates its own and OatDexFile takes ownership.
     const DexFile& dex_file = *opened_dex_files[i];
     {
-      std::unique_ptr<TypeLookupTable> type_lookup_table =
-          TypeLookupTable::Create(dex_file, /* storage */ nullptr);
+      TypeLookupTable type_lookup_table = TypeLookupTable::Create(dex_file);
       type_lookup_table_oat_dex_files_.push_back(
           std::make_unique<art::OatDexFile>(std::move(type_lookup_table)));
       dex_file.SetOatDexFile(type_lookup_table_oat_dex_files_.back().get());
     }
-    TypeLookupTable* const table = type_lookup_table_oat_dex_files_.back()->GetTypeLookupTable();
+    const TypeLookupTable& table = type_lookup_table_oat_dex_files_.back()->GetTypeLookupTable();
+    DCHECK(table.Valid());
 
     // Type tables are required to be 4 byte aligned.
     size_t initial_offset = oat_size_;
@@ -4057,9 +4057,9 @@
 
     DCHECK_EQ(oat_data_offset_ + rodata_offset,
               static_cast<size_t>(oat_rodata->Seek(0u, kSeekCurrent)));
-    DCHECK_EQ(table_size, table->RawDataLength());
+    DCHECK_EQ(table_size, table.RawDataLength());
 
-    if (!oat_rodata->WriteFully(table->RawData(), table_size)) {
+    if (!oat_rodata->WriteFully(table.RawData(), table_size)) {
       PLOG(ERROR) << "Failed to write lookup table."
                   << " File: " << oat_dex_file->GetLocation()
                   << " Output: " << oat_rodata->GetLocation();
diff --git a/libdexfile/dex/type_lookup_table.cc b/libdexfile/dex/type_lookup_table.cc
index ca5ec2f..00ec358 100644
--- a/libdexfile/dex/type_lookup_table.cc
+++ b/libdexfile/dex/type_lookup_table.cc
@@ -20,72 +20,43 @@
 #include <memory>
 
 #include "base/bit_utils.h"
+#include "base/leb128.h"
 #include "dex/dex_file-inl.h"
 #include "dex/utf-inl.h"
 
 namespace art {
 
-static uint16_t MakeData(uint16_t class_def_idx, uint32_t hash, uint32_t mask) {
-  uint16_t hash_mask = static_cast<uint16_t>(~mask);
-  return (static_cast<uint16_t>(hash) & hash_mask) | class_def_idx;
+static inline bool ModifiedUtf8StringEquals(const char* lhs, const char* rhs) {
+  return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(lhs, rhs) == 0;
 }
 
-TypeLookupTable::~TypeLookupTable() {
-  if (!owns_entries_) {
-    // We don't actually own the entries, don't let the unique_ptr release them.
-    entries_.release();
+TypeLookupTable TypeLookupTable::Create(const DexFile& dex_file) {
+  uint32_t num_class_defs = dex_file.NumClassDefs();
+  if (UNLIKELY(!SupportedSize(num_class_defs))) {
+    return TypeLookupTable();
   }
-}
+  size_t mask_bits = CalculateMaskBits(num_class_defs);
+  size_t size = 1u << mask_bits;
+  std::unique_ptr<Entry[]> owned_entries(new Entry[size]);
+  Entry* entries = owned_entries.get();
 
-uint32_t TypeLookupTable::RawDataLength(uint32_t num_class_defs) {
-  return SupportedSize(num_class_defs) ? RoundUpToPowerOfTwo(num_class_defs) * sizeof(Entry) : 0u;
-}
-
-uint32_t TypeLookupTable::CalculateMask(uint32_t num_class_defs) {
-  return SupportedSize(num_class_defs) ? RoundUpToPowerOfTwo(num_class_defs) - 1u : 0u;
-}
-
-bool TypeLookupTable::SupportedSize(uint32_t num_class_defs) {
-  return num_class_defs != 0u && num_class_defs <= std::numeric_limits<uint16_t>::max();
-}
-
-std::unique_ptr<TypeLookupTable> TypeLookupTable::Create(const DexFile& dex_file,
-                                                         uint8_t* storage) {
-  const uint32_t num_class_defs = dex_file.NumClassDefs();
-  return std::unique_ptr<TypeLookupTable>(SupportedSize(num_class_defs)
-      ? new TypeLookupTable(dex_file, storage)
-      : nullptr);
-}
-
-std::unique_ptr<TypeLookupTable> TypeLookupTable::Open(const uint8_t* dex_file_pointer,
-                                                       const uint8_t* raw_data,
-                                                       uint32_t num_class_defs) {
-  return std::unique_ptr<TypeLookupTable>(
-      new TypeLookupTable(dex_file_pointer, raw_data, num_class_defs));
-}
-
-TypeLookupTable::TypeLookupTable(const DexFile& dex_file, uint8_t* storage)
-    : dex_data_begin_(dex_file.DataBegin()),
-      raw_data_length_(RawDataLength(dex_file.NumClassDefs())),
-      mask_(CalculateMask(dex_file.NumClassDefs())),
-      entries_(storage != nullptr ? reinterpret_cast<Entry*>(storage) : new Entry[mask_ + 1]),
-      owns_entries_(storage == nullptr) {
   static_assert(alignof(Entry) == 4u, "Expecting Entry to be 4-byte aligned.");
-  DCHECK_ALIGNED(storage, alignof(Entry));
+  const uint32_t mask = Entry::GetMask(mask_bits);
   std::vector<uint16_t> conflict_class_defs;
   // The first stage. Put elements on their initial positions. If an initial position is already
   // occupied then delay the insertion of the element to the second stage to reduce probing
   // distance.
-  for (size_t i = 0; i < dex_file.NumClassDefs(); ++i) {
-    const DexFile::ClassDef& class_def = dex_file.GetClassDef(i);
+  for (size_t class_def_idx = 0; class_def_idx < dex_file.NumClassDefs(); ++class_def_idx) {
+    const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_idx);
     const DexFile::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_);
     const DexFile::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_);
     const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id));
-    Entry entry;
-    entry.str_offset = str_id.string_data_off_;
-    entry.data = MakeData(i, hash, GetSizeMask());
-    if (!SetOnInitialPos(entry, hash)) {
-      conflict_class_defs.push_back(i);
+    const uint32_t pos = hash & mask;
+    if (entries[pos].IsEmpty()) {
+      entries[pos] = Entry(str_id.string_data_off_, hash, class_def_idx, mask_bits);
+      DCHECK(entries[pos].IsLast(mask_bits));
+    } else {
+      conflict_class_defs.push_back(class_def_idx);
     }
   }
   // The second stage. The initial position of these elements had a collision. Put these elements
@@ -95,51 +66,111 @@
     const DexFile::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_);
     const DexFile::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_);
     const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id));
-    Entry entry;
-    entry.str_offset = str_id.string_data_off_;
-    entry.data = MakeData(class_def_idx, hash, GetSizeMask());
-    Insert(entry, hash);
+    // Find the last entry in the chain.
+    uint32_t tail_pos = hash & mask;
+    DCHECK(!entries[tail_pos].IsEmpty());
+    while (!entries[tail_pos].IsLast(mask_bits)) {
+      tail_pos = (tail_pos + entries[tail_pos].GetNextPosDelta(mask_bits)) & mask;
+      DCHECK(!entries[tail_pos].IsEmpty());
+    }
+    // Find an empty entry for insertion.
+    uint32_t insert_pos = tail_pos;
+    do {
+      insert_pos = (insert_pos + 1) & mask;
+    } while (!entries[insert_pos].IsEmpty());
+    // Insert and chain the new entry.
+    entries[insert_pos] = Entry(str_id.string_data_off_, hash, class_def_idx, mask_bits);
+    entries[tail_pos].SetNextPosDelta((insert_pos - tail_pos) & mask, mask_bits);
+    DCHECK(entries[insert_pos].IsLast(mask_bits));
+    DCHECK(!entries[tail_pos].IsLast(mask_bits));
   }
+
+  return TypeLookupTable(dex_file.DataBegin(), mask_bits, entries, std::move(owned_entries));
 }
 
-TypeLookupTable::TypeLookupTable(const uint8_t* dex_file_pointer,
-                                 const uint8_t* raw_data,
-                                 uint32_t num_class_defs)
-    : dex_data_begin_(dex_file_pointer),
-      raw_data_length_(RawDataLength(num_class_defs)),
-      mask_(CalculateMask(num_class_defs)),
-      entries_(reinterpret_cast<Entry*>(const_cast<uint8_t*>(raw_data))),
-      owns_entries_(false) {}
-
-bool TypeLookupTable::SetOnInitialPos(const Entry& entry, uint32_t hash) {
-  const uint32_t pos = hash & GetSizeMask();
-  if (!entries_[pos].IsEmpty()) {
-    return false;
-  }
-  entries_[pos] = entry;
-  entries_[pos].next_pos_delta = 0;
-  return true;
+TypeLookupTable TypeLookupTable::Open(const uint8_t* dex_data_pointer,
+                                      const uint8_t* raw_data,
+                                      uint32_t num_class_defs) {
+  DCHECK_ALIGNED(raw_data, alignof(Entry));
+  const Entry* entries = reinterpret_cast<const Entry*>(raw_data);
+  size_t mask_bits = CalculateMaskBits(num_class_defs);
+  return TypeLookupTable(dex_data_pointer, mask_bits, entries, /* owned_entries */ nullptr);
 }
 
-void TypeLookupTable::Insert(const Entry& entry, uint32_t hash) {
-  uint32_t pos = FindLastEntryInBucket(hash & GetSizeMask());
-  uint32_t next_pos = (pos + 1) & GetSizeMask();
-  while (!entries_[next_pos].IsEmpty()) {
-    next_pos = (next_pos + 1) & GetSizeMask();
-  }
-  const uint32_t delta = (next_pos >= pos) ? (next_pos - pos) : (next_pos + Size() - pos);
-  entries_[pos].next_pos_delta = delta;
-  entries_[next_pos] = entry;
-  entries_[next_pos].next_pos_delta = 0;
-}
-
-uint32_t TypeLookupTable::FindLastEntryInBucket(uint32_t pos) const {
+uint32_t TypeLookupTable::Lookup(const char* str, uint32_t hash) const {
+  uint32_t mask = Entry::GetMask(mask_bits_);
+  uint32_t pos = hash & mask;
+  // Thanks to special insertion algorithm, the element at position pos can be empty
+  // or start of the right bucket, or anywhere in the wrong bucket's chain.
   const Entry* entry = &entries_[pos];
-  while (!entry->IsLast()) {
-    pos = (pos + entry->next_pos_delta) & GetSizeMask();
-    entry = &entries_[pos];
+  if (entry->IsEmpty()) {
+    return dex::kDexNoIndex;
   }
-  return pos;
+  // Look for the partial hash match first, even if traversing the wrong bucket's chain.
+  uint32_t compared_hash_bits = (hash << mask_bits_) >> (2 * mask_bits_);
+  while (compared_hash_bits != entry->GetHashBits(mask_bits_)) {
+    if (entry->IsLast(mask_bits_)) {
+      return dex::kDexNoIndex;
+    }
+    pos = (pos + entry->GetNextPosDelta(mask_bits_)) & mask;
+    entry = &entries_[pos];
+    DCHECK(!entry->IsEmpty());
+  }
+  // Found partial hash match, compare strings (expecting this to succeed).
+  const char* first_checked_str = GetStringData(*entry);
+  if (ModifiedUtf8StringEquals(str, first_checked_str)) {
+    return entry->GetClassDefIdx(mask_bits_);
+  }
+  // If we're at the end of the chain, return before doing further expensive work.
+  if (entry->IsLast(mask_bits_)) {
+    return dex::kDexNoIndex;
+  }
+  // Check if we're traversing the right bucket. This is important if the compared
+  // partial hash has only a few bits (i.e. it can match frequently).
+  if (((ComputeModifiedUtf8Hash(first_checked_str) ^ hash) & mask) != 0u) {
+    return dex::kDexNoIndex;  // Low hash bits mismatch.
+  }
+  // Continue looking for the string in the rest of the chain.
+  do {
+    pos = (pos + entry->GetNextPosDelta(mask_bits_)) & mask;
+    entry = &entries_[pos];
+    DCHECK(!entry->IsEmpty());
+    if (compared_hash_bits == entry->GetHashBits(mask_bits_) &&
+        ModifiedUtf8StringEquals(str, GetStringData(*entry))) {
+      return entry->GetClassDefIdx(mask_bits_);
+    }
+  } while (!entry->IsLast(mask_bits_));
+  // Not found.
+  return dex::kDexNoIndex;
+}
+
+uint32_t TypeLookupTable::RawDataLength(uint32_t num_class_defs) {
+  return SupportedSize(num_class_defs) ? RoundUpToPowerOfTwo(num_class_defs) * sizeof(Entry) : 0u;
+}
+
+uint32_t TypeLookupTable::CalculateMaskBits(uint32_t num_class_defs) {
+  return SupportedSize(num_class_defs) ? MinimumBitsToStore(num_class_defs - 1u) : 0u;
+}
+
+bool TypeLookupTable::SupportedSize(uint32_t num_class_defs) {
+  return num_class_defs != 0u && num_class_defs <= std::numeric_limits<uint16_t>::max();
+}
+
+TypeLookupTable::TypeLookupTable(const uint8_t* dex_data_pointer,
+                                 uint32_t mask_bits,
+                                 const Entry* entries,
+                                 std::unique_ptr<Entry[]> owned_entries)
+    : dex_data_begin_(dex_data_pointer),
+      mask_bits_(mask_bits),
+      entries_(entries),
+      owned_entries_(std::move(owned_entries)) {}
+
+const char* TypeLookupTable::GetStringData(const Entry& entry) const {
+  DCHECK(dex_data_begin_ != nullptr);
+  const uint8_t* ptr = dex_data_begin_ + entry.GetStringOffset();
+  // Skip string length.
+  DecodeUnsignedLeb128(&ptr);
+  return reinterpret_cast<const char*>(ptr);
 }
 
 }  // namespace art
diff --git a/libdexfile/dex/type_lookup_table.h b/libdexfile/dex/type_lookup_table.h
index 0ba2b75..7005d34 100644
--- a/libdexfile/dex/type_lookup_table.h
+++ b/libdexfile/dex/type_lookup_table.h
@@ -17,9 +17,8 @@
 #ifndef ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_
 #define ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_
 
-#include "base/leb128.h"
+#include "base/logging.h"
 #include "dex/dex_file_types.h"
-#include "dex/utf.h"
 
 namespace art {
 
@@ -34,140 +33,146 @@
  */
 class TypeLookupTable {
  public:
-  ~TypeLookupTable();
+  // Method creates lookup table for dex file.
+  static TypeLookupTable Create(const DexFile& dex_file);
+
+  // Method opens lookup table from binary data. Lookups will traverse strings and other
+  // data contained in dex_file as well.  Lookup table does not own raw_data or dex_file.
+  static TypeLookupTable Open(const uint8_t* dex_data_pointer,
+                              const uint8_t* raw_data,
+                              uint32_t num_class_defs);
+
+  // Create an invalid lookup table.
+  TypeLookupTable()
+      : dex_data_begin_(nullptr),
+        mask_bits_(0u),
+        entries_(nullptr),
+        owned_entries_(nullptr) {}
+
+  TypeLookupTable(TypeLookupTable&& src) noexcept = default;
+  TypeLookupTable& operator=(TypeLookupTable&& src) noexcept = default;
+
+  ~TypeLookupTable() {
+    // Implicit deallocation by std::unique_ptr<> destructor.
+  }
+
+  // Returns whether the TypeLookupTable is valid.
+  bool Valid() const {
+    return entries_ != nullptr;
+  }
 
   // Return the number of buckets in the lookup table.
   uint32_t Size() const {
-    return mask_ + 1;
+    DCHECK(Valid());
+    return 1u << mask_bits_;
   }
 
   // Method search class_def_idx by class descriptor and it's hash.
   // If no data found then the method returns dex::kDexNoIndex.
-  uint32_t Lookup(const char* str, uint32_t hash) const {
-    uint32_t pos = hash & GetSizeMask();
-    // Thanks to special insertion algorithm, the element at position pos can be empty or start of
-    // bucket.
-    const Entry* entry = &entries_[pos];
-    while (!entry->IsEmpty()) {
-      if (CmpHashBits(entry->data, hash) && IsStringsEquals(str, entry->str_offset)) {
-        return GetClassDefIdx(entry->data);
-      }
-      if (entry->IsLast()) {
-        return dex::kDexNoIndex;
-      }
-      pos = (pos + entry->next_pos_delta) & GetSizeMask();
-      entry = &entries_[pos];
-    }
-    return dex::kDexNoIndex;
-  }
-
-  // Method creates lookup table for dex file
-  static std::unique_ptr<TypeLookupTable> Create(const DexFile& dex_file,
-                                                 uint8_t* storage = nullptr);
-
-  // Method opens lookup table from binary data. Lookups will traverse strings and other
-  // data contained in dex_file as well.  Lookup table does not own raw_data or dex_file.
-  static std::unique_ptr<TypeLookupTable> Open(const uint8_t* dex_file_pointer,
-                                               const uint8_t* raw_data,
-                                               uint32_t num_class_defs);
+  uint32_t Lookup(const char* str, uint32_t hash) const;
 
   // Method returns pointer to binary data of lookup table. Used by the oat writer.
   const uint8_t* RawData() const {
-    return reinterpret_cast<const uint8_t*>(entries_.get());
+    DCHECK(Valid());
+    return reinterpret_cast<const uint8_t*>(entries_);
   }
 
   // Method returns length of binary data. Used by the oat writer.
-  uint32_t RawDataLength() const { return raw_data_length_; }
+  uint32_t RawDataLength() const {
+    DCHECK(Valid());
+    return Size() * sizeof(Entry);
+  }
 
   // Method returns length of binary data for the specified number of class definitions.
   static uint32_t RawDataLength(uint32_t num_class_defs);
 
  private:
-   /**
-    * To find element we need to compare strings.
-    * It is faster to compare first hashes and then strings itself.
-    * But we have no full hash of element of table. But we can use 2 ideas.
-    * 1. All minor bits of hash inside one bucket are equals.
-    * 2. If dex file contains N classes and size of hash table is 2^n (where N <= 2^n)
-    *    then 16-n bits are free. So we can encode part of element's hash into these bits.
-    * So hash of element can be divided on three parts:
-    * XXXX XXXX XXXX YYYY YZZZ ZZZZ ZZZZZ
-    * Z - a part of hash encoded in bucket (these bits of has are same for all elements in bucket) -
-    * n bits
-    * Y - a part of hash that we can write into free 16-n bits (because only n bits used to store
-    * class_def_idx)
-    * X - a part of has that we can't use without increasing increase
-    * So the data element of Entry used to store class_def_idx and part of hash of the entry.
-    */
-  struct Entry {
-    uint32_t str_offset;
-    uint16_t data;
-    uint16_t next_pos_delta;
+  /**
+   * To find element we need to compare strings.
+   * It is faster to compare first hashes and then strings itself.
+   * But we have no full hash of element of table. But we can use 2 ideas.
+   * 1. All minor bits of hash inside one bucket are equal.
+   *    (TODO: We're not actually using this, are we?)
+   * 2. If the dex file contains N classes and the size of the hash table is 2^n (where N <= 2^n)
+   *    then we need n bits for the class def index and n bits for the next position delta.
+   *    So we can encode part of element's hash into the remaining 32-2*n (n <= 16) bits which
+   *    would be otherwise wasted as a padding.
+   * So hash of element can be divided on three parts:
+   *     XXXX XXXX XXXY YYYY YYYY YZZZ ZZZZ ZZZZ  (example with n=11)
+   *     Z - a part of hash encoded implicitly in the bucket index
+   *         (these bits are same for all elements in bucket)
+   *     Y - a part of hash that we can write into free 32-2*n bits
+   *     X - a part of hash that we can't use without increasing the size of the entry
+   * So the `data` element of Entry is used to store the next position delta, class_def_index
+   * and a part of hash of the entry.
+   */
+  class Entry {
+   public:
+    Entry() : str_offset_(0u), data_(0u) {}
+    Entry(uint32_t str_offset, uint32_t hash, uint32_t class_def_index, uint32_t mask_bits)
+        : str_offset_(str_offset),
+          data_(((hash & ~GetMask(mask_bits)) | class_def_index) << mask_bits) {
+      DCHECK_EQ(class_def_index & ~GetMask(mask_bits), 0u);
+    }
 
-    Entry() : str_offset(0), data(0), next_pos_delta(0) {}
+    void SetNextPosDelta(uint32_t next_pos_delta, uint32_t mask_bits) {
+      DCHECK_EQ(GetNextPosDelta(mask_bits), 0u);
+      DCHECK_EQ(next_pos_delta & ~GetMask(mask_bits), 0u);
+      DCHECK_NE(next_pos_delta, 0u);
+      data_ |= next_pos_delta;
+    }
 
     bool IsEmpty() const {
-      return str_offset == 0;
+      return str_offset_ == 0u;
     }
 
-    bool IsLast() const {
-      return next_pos_delta == 0;
+    bool IsLast(uint32_t mask_bits) const {
+      return GetNextPosDelta(mask_bits) == 0u;
     }
+
+    uint32_t GetStringOffset() const {
+      return str_offset_;
+    }
+
+    uint32_t GetNextPosDelta(uint32_t mask_bits) const {
+      return data_ & GetMask(mask_bits);
+    }
+
+    uint32_t GetClassDefIdx(uint32_t mask_bits) const {
+      return (data_ >> mask_bits) & GetMask(mask_bits);
+    }
+
+    uint32_t GetHashBits(uint32_t mask_bits) const {
+      DCHECK_LE(mask_bits, 16u);
+      return data_ >> (2u * mask_bits);
+    }
+
+    static uint32_t GetMask(uint32_t mask_bits) {
+      DCHECK_LE(mask_bits, 16u);
+      return ~(std::numeric_limits<uint32_t>::max() << mask_bits);
+    }
+
+   private:
+    uint32_t str_offset_;
+    uint32_t data_;
   };
 
-  static uint32_t CalculateMask(uint32_t num_class_defs);
+  static uint32_t CalculateMaskBits(uint32_t num_class_defs);
   static bool SupportedSize(uint32_t num_class_defs);
 
-  // Construct from a dex file.
-  explicit TypeLookupTable(const DexFile& dex_file, uint8_t* storage);
+  // Construct the TypeLookupTable.
+  TypeLookupTable(const uint8_t* dex_data_pointer,
+                  uint32_t mask_bits,
+                  const Entry* entries,
+                  std::unique_ptr<Entry[]> owned_entries);
 
-  // Construct from a dex file with existing data.
-  TypeLookupTable(const uint8_t* dex_file_pointer,
-                  const uint8_t* raw_data,
-                  uint32_t num_class_defs);
-
-  bool IsStringsEquals(const char* str, uint32_t str_offset) const {
-    const uint8_t* ptr = dex_data_begin_ + str_offset;
-    CHECK(dex_data_begin_ != nullptr);
-    // Skip string length.
-    DecodeUnsignedLeb128(&ptr);
-    return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(
-        str, reinterpret_cast<const char*>(ptr)) == 0;
-  }
-
-  // Method extracts hash bits from element's data and compare them with
-  // the corresponding bits of the specified hash
-  bool CmpHashBits(uint32_t data, uint32_t hash) const {
-    uint32_t mask = static_cast<uint16_t>(~GetSizeMask());
-    return (hash & mask) == (data & mask);
-  }
-
-  uint32_t GetClassDefIdx(uint32_t data) const {
-    return data & mask_;
-  }
-
-  uint32_t GetSizeMask() const {
-    return mask_;
-  }
-
-  // Attempt to set an entry on its hash's slot. If there is already something there, return false.
-  // Otherwise return true.
-  bool SetOnInitialPos(const Entry& entry, uint32_t hash);
-
-  // Insert an entry, probes until there is an empty slot.
-  void Insert(const Entry& entry, uint32_t hash);
-
-  // Find the last entry in a chain.
-  uint32_t FindLastEntryInBucket(uint32_t cur_pos) const;
+  const char* GetStringData(const Entry& entry) const;
 
   const uint8_t* dex_data_begin_;
-  const uint32_t raw_data_length_;
-  const uint32_t mask_;
-  std::unique_ptr<Entry[]> entries_;
-  // owns_entries_ specifies if the lookup table owns the entries_ array.
-  const bool owns_entries_;
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(TypeLookupTable);
+  uint32_t mask_bits_;
+  const Entry* entries_;
+  // `owned_entries_` is either null (not owning `entries_`) or same pointer as `entries_`.
+  std::unique_ptr<Entry[]> owned_entries_;
 };
 
 }  // namespace art
diff --git a/libdexfile/dex/type_lookup_table_test.cc b/libdexfile/dex/type_lookup_table_test.cc
index 6c3d291..4316be0 100644
--- a/libdexfile/dex/type_lookup_table_test.cc
+++ b/libdexfile/dex/type_lookup_table_test.cc
@@ -30,20 +30,20 @@
 
 TEST_F(TypeLookupTableTest, CreateLookupTable) {
   std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup"));
-  std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file));
-  ASSERT_NE(nullptr, table.get());
-  ASSERT_NE(nullptr, table->RawData());
-  ASSERT_EQ(32U, table->RawDataLength());
+  TypeLookupTable table = TypeLookupTable::Create(*dex_file);
+  ASSERT_TRUE(table.Valid());
+  ASSERT_NE(nullptr, table.RawData());
+  ASSERT_EQ(32U, table.RawDataLength());
 }
 
 TEST_P(TypeLookupTableTest, Find) {
   std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup"));
-  std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file));
-  ASSERT_NE(nullptr, table.get());
+  TypeLookupTable table(TypeLookupTable::Create(*dex_file));
+  ASSERT_TRUE(table.Valid());
   auto pair = GetParam();
   const char* descriptor = pair.first;
   size_t hash = ComputeModifiedUtf8Hash(descriptor);
-  uint32_t class_def_idx = table->Lookup(descriptor, hash);
+  uint32_t class_def_idx = table.Lookup(descriptor, hash);
   ASSERT_EQ(pair.second, class_def_idx);
 }
 
diff --git a/runtime/oat.h b/runtime/oat.h
index e7e5848..72eb27d 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Rewrite dex register map encoding.
-  static constexpr uint8_t kOatVersion[] = { '1', '4', '6', '\0' };
+  // Last oat version changed reason: Rewrite TypeLookupTable.
+  static constexpr uint8_t kOatVersion[] = { '1', '4', '7', '\0' };
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index ffbc26c..2b05b0e 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1687,6 +1687,7 @@
       type_bss_mapping_(type_bss_mapping_data),
       string_bss_mapping_(string_bss_mapping_data),
       oat_class_offsets_pointer_(oat_class_offsets_pointer),
+      lookup_table_(),
       dex_layout_sections_(dex_layout_sections) {
   // Initialize TypeLookupTable.
   if (lookup_table_data_ != nullptr) {
@@ -1706,7 +1707,7 @@
   }
 }
 
-OatFile::OatDexFile::OatDexFile(std::unique_ptr<TypeLookupTable>&& lookup_table)
+OatFile::OatDexFile::OatDexFile(TypeLookupTable&& lookup_table)
     : lookup_table_(std::move(lookup_table)) {}
 
 OatFile::OatDexFile::~OatDexFile() {}
@@ -1783,9 +1784,9 @@
   DCHECK_EQ(ComputeModifiedUtf8Hash(descriptor), hash);
   bool used_lookup_table = false;
   const DexFile::ClassDef* lookup_table_classdef = nullptr;
-  if (LIKELY((oat_dex_file != nullptr) && (oat_dex_file->GetTypeLookupTable() != nullptr))) {
+  if (LIKELY((oat_dex_file != nullptr) && oat_dex_file->GetTypeLookupTable().Valid())) {
     used_lookup_table = true;
-    const uint32_t class_def_idx = oat_dex_file->GetTypeLookupTable()->Lookup(descriptor, hash);
+    const uint32_t class_def_idx = oat_dex_file->GetTypeLookupTable().Lookup(descriptor, hash);
     lookup_table_classdef = (class_def_idx != dex::kDexNoIndex)
         ? &dex_file.GetClassDef(class_def_idx)
         : nullptr;
@@ -1796,6 +1797,7 @@
   // Fast path for rare no class defs case.
   const uint32_t num_class_defs = dex_file.NumClassDefs();
   if (num_class_defs == 0) {
+    DCHECK(!used_lookup_table);
     return nullptr;
   }
   const DexFile::TypeId* type_id = dex_file.FindTypeId(descriptor);
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 8e18cee..d72b6a8 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -514,14 +514,14 @@
   // Madvise the dex file based on the state we are moving to.
   static void MadviseDexFile(const DexFile& dex_file, MadviseState state);
 
-  TypeLookupTable* GetTypeLookupTable() const {
-    return lookup_table_.get();
+  const TypeLookupTable& GetTypeLookupTable() const {
+    return lookup_table_;
   }
 
   ~OatDexFile();
 
   // Create only with a type lookup table, used by the compiler to speed up compilation.
-  explicit OatDexFile(std::unique_ptr<TypeLookupTable>&& lookup_table);
+  explicit OatDexFile(TypeLookupTable&& lookup_table);
 
   // Return the dex layout sections.
   const DexLayoutSections* GetDexLayoutSections() const {
@@ -553,7 +553,7 @@
   const IndexBssMapping* const type_bss_mapping_ = nullptr;
   const IndexBssMapping* const string_bss_mapping_ = nullptr;
   const uint32_t* const oat_class_offsets_pointer_ = 0u;
-  mutable std::unique_ptr<TypeLookupTable> lookup_table_;
+  TypeLookupTable lookup_table_;
   const DexLayoutSections* const dex_layout_sections_ = nullptr;
 
   friend class OatFile;