Enable code item deduping

Dedupe code items if they have the same contents, this is safe
because of the fixed quickening logic.

Removed duplicate code items in ManyMethods that caused the startup
methods section to be empty in dex2oat_test.

To avoid errors related to quickening and compilation of shared code
items, quickening is currently disabled for shared code items.

Bug: 63756964
Test: test-art-host

Change-Id: I7c62eb746785d787c5269effd396f7be4859d3a6
diff --git a/compiler/compiled_method.cc b/compiler/compiled_method.cc
index 0f69dba..e413718 100644
--- a/compiler/compiled_method.cc
+++ b/compiler/compiled_method.cc
@@ -159,10 +159,4 @@
   storage->ReleaseMethodInfo(method_info_);
 }
 
-void CompiledMethod::ReleaseVMapTable() {
-  CompiledMethodStorage* storage = GetCompilerDriver()->GetCompiledMethodStorage();
-  storage->ReleaseVMapTable(vmap_table_);
-  vmap_table_ = nullptr;
-}
-
 }  // namespace art
diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h
index 4e8f3ef..acdce26 100644
--- a/compiler/compiled_method.h
+++ b/compiler/compiled_method.h
@@ -168,10 +168,6 @@
 
   ArrayRef<const linker::LinkerPatch> GetPatches() const;
 
-  // The compiler sometimes unquickens shared code items. In that case, we need to clear the vmap
-  // table to avoid writing the quicken info to the vdex file.
-  void ReleaseVMapTable();
-
  private:
   static constexpr size_t kIsIntrinsicLsb = kNumberOfCompiledCodePackedBits;
   static constexpr size_t kIsIntrinsicSize = 1u;
@@ -190,7 +186,7 @@
   // For quick code, method specific information that is not very dedupe friendly (method indices).
   const LengthPrefixedArray<uint8_t>* const method_info_;
   // For quick code, holds code infos which contain stack maps, inline information, and etc.
-  const LengthPrefixedArray<uint8_t>* vmap_table_;
+  const LengthPrefixedArray<uint8_t>* const vmap_table_;
   // For quick code, a FDE entry for the debug_frame section.
   const LengthPrefixedArray<uint8_t>* const cfi_info_;
   // For quick code, linker patches needed by the method.
diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc
index 28c7fe2..9f0aaa4 100644
--- a/compiler/dex/dex_to_dex_compiler.cc
+++ b/compiler/dex/dex_to_dex_compiler.cc
@@ -45,6 +45,85 @@
 // Control check-cast elision.
 const bool kEnableCheckCastEllision = true;
 
+// Holds the state for compiling a single method.
+struct DexToDexCompiler::CompilationState {
+  struct QuickenedInfo {
+    QuickenedInfo(uint32_t pc, uint16_t index) : dex_pc(pc), dex_member_index(index) {}
+
+    uint32_t dex_pc;
+    uint16_t dex_member_index;
+  };
+
+  CompilationState(DexToDexCompiler* compiler,
+                   const DexCompilationUnit& unit,
+                   const CompilationLevel compilation_level,
+                   const std::vector<uint8_t>* quicken_data);
+
+  const std::vector<QuickenedInfo>& GetQuickenedInfo() const {
+    return quickened_info_;
+  }
+
+  // Returns the quickening info, or an empty array if it was not quickened.
+  // If already_quickened is true, then don't change anything but still return what the quicken
+  // data would have been.
+  std::vector<uint8_t> Compile();
+
+  const DexFile& GetDexFile() const;
+
+  // Compiles a RETURN-VOID into a RETURN-VOID-BARRIER within a constructor where
+  // a barrier is required.
+  void CompileReturnVoid(Instruction* inst, uint32_t dex_pc);
+
+  // Compiles a CHECK-CAST into 2 NOP instructions if it is known to be safe. In
+  // this case, returns the second NOP instruction pointer. Otherwise, returns
+  // the given "inst".
+  Instruction* CompileCheckCast(Instruction* inst, uint32_t dex_pc);
+
+  // Compiles a field access into a quick field access.
+  // The field index is replaced by an offset within an Object where we can read
+  // from / write to this field. Therefore, this does not involve any resolution
+  // at runtime.
+  // Since the field index is encoded with 16 bits, we can replace it only if the
+  // field offset can be encoded with 16 bits too.
+  void CompileInstanceFieldAccess(Instruction* inst, uint32_t dex_pc,
+                                  Instruction::Code new_opcode, bool is_put);
+
+  // Compiles a virtual method invocation into a quick virtual method invocation.
+  // The method index is replaced by the vtable index where the corresponding
+  // executable can be found. Therefore, this does not involve any resolution
+  // at runtime.
+  // Since the method index is encoded with 16 bits, we can replace it only if the
+  // vtable index can be encoded with 16 bits too.
+  void CompileInvokeVirtual(Instruction* inst, uint32_t dex_pc,
+                            Instruction::Code new_opcode, bool is_range);
+
+  // Return the next index.
+  uint16_t NextIndex();
+
+  // Returns the dequickened index if an instruction is quickened, otherwise return index.
+  uint16_t GetIndexForInstruction(const Instruction* inst, uint32_t index);
+
+  DexToDexCompiler* const compiler_;
+  CompilerDriver& driver_;
+  const DexCompilationUnit& unit_;
+  const CompilationLevel compilation_level_;
+
+  // Filled by the compiler when quickening, in order to encode that information
+  // in the .oat file. The runtime will use that information to get to the original
+  // opcodes.
+  std::vector<QuickenedInfo> quickened_info_;
+
+  // True if we optimized a return void to a return void no barrier.
+  bool optimized_return_void_ = false;
+
+  // If the code item was already quickened previously.
+  const bool already_quickened_;
+  const QuickenInfoTable existing_quicken_info_;
+  uint32_t quicken_index_ = 0u;
+
+  DISALLOW_COPY_AND_ASSIGN(CompilationState);
+};
+
 DexToDexCompiler::DexToDexCompiler(CompilerDriver* driver)
     : driver_(driver),
       lock_("Quicken lock", kDexToDexCompilerLock) {
@@ -55,16 +134,13 @@
   MutexLock lock(Thread::Current(), lock_);
   active_dex_file_ = nullptr;
   active_bit_vector_ = nullptr;
-  seen_code_items_.clear();
   should_quicken_.clear();
-  shared_code_items_.clear();
-  blacklisted_code_items_.clear();
   shared_code_item_quicken_info_.clear();
 }
 
-size_t DexToDexCompiler::NumUniqueCodeItems(Thread* self) const {
+size_t DexToDexCompiler::NumCodeItemsToQuicken(Thread* self) const {
   MutexLock lock(self, lock_);
-  return seen_code_items_.size();
+  return num_code_items_;
 }
 
 BitVector* DexToDexCompiler::GetOrAddBitVectorForDex(const DexFile* dex_file) {
@@ -80,17 +156,13 @@
 }
 
 void DexToDexCompiler::MarkForCompilation(Thread* self,
-                                          const MethodReference& method_ref,
-                                          const DexFile::CodeItem* code_item) {
+                                          const MethodReference& method_ref) {
   MutexLock lock(self, lock_);
   BitVector* const bitmap = GetOrAddBitVectorForDex(method_ref.dex_file);
   DCHECK(bitmap != nullptr);
   DCHECK(!bitmap->IsBitSet(method_ref.index));
   bitmap->SetBit(method_ref.index);
-  // Detect the shared code items.
-  if (!seen_code_items_.insert(code_item).second) {
-    shared_code_items_.insert(code_item);
-  }
+  ++num_code_items_;
 }
 
 DexToDexCompiler::CompilationState::CompilationState(DexToDexCompiler* compiler,
@@ -316,6 +388,7 @@
                  << " at dex pc " << StringPrintf("0x%x", dex_pc) << " in method "
                  << GetDexFile().PrettyMethod(unit_.GetDexMethodIndex(), true);
   inst->SetOpcode(Instruction::RETURN_VOID_NO_BARRIER);
+  optimized_return_void_ = true;
 }
 
 Instruction* DexToDexCompiler::CompilationState::CompileCheckCast(Instruction* inst,
@@ -464,51 +537,48 @@
   // If the code item is shared with multiple different method ids, make sure that we quicken only
   // once and verify that all the dequicken maps match.
   if (UNLIKELY(shared_code_items_.find(code_item) != shared_code_items_.end())) {
-    // For shared code items, use a lock to prevent races.
-    MutexLock mu(soa.Self(), lock_);
-    // Blacklisted means there was a quickening conflict previously, bail early.
-    if (blacklisted_code_items_.find(code_item) != blacklisted_code_items_.end()) {
+    // Avoid quickening the shared code items for now because the existing conflict detection logic
+    // does not currently handle cases where the code item is quickened in one place but
+    // compiled in another.
+    static constexpr bool kAvoidQuickeningSharedCodeItems = true;
+    if (kAvoidQuickeningSharedCodeItems) {
       return nullptr;
     }
+    // For shared code items, use a lock to prevent races.
+    MutexLock mu(soa.Self(), lock_);
     auto existing = shared_code_item_quicken_info_.find(code_item);
-    const bool already_quickened = existing != shared_code_item_quicken_info_.end();
+    QuickenState* existing_data = nullptr;
+    std::vector<uint8_t>* existing_quicken_data = nullptr;
+    if (existing != shared_code_item_quicken_info_.end()) {
+      existing_data = &existing->second;
+      if (existing_data->conflict_) {
+        return nullptr;
+      }
+      existing_quicken_data = &existing_data->quicken_data_;
+    }
+    bool optimized_return_void;
     {
-      CompilationState state(this,
-                             unit,
-                             compilation_level,
-                             already_quickened ? &existing->second.quicken_data_ : nullptr);
+      CompilationState state(this, unit, compilation_level, existing_quicken_data);
       quicken_data = state.Compile();
+      optimized_return_void = state.optimized_return_void_;
     }
 
     // Already quickened, check that the data matches what was previously seen.
     MethodReference method_ref(&dex_file, method_idx);
-    if (already_quickened) {
-      QuickenState* const existing_data = &existing->second;
-      if (existing_data->quicken_data_ != quicken_data) {
-        VLOG(compiler) << "Quicken data mismatch, dequickening method "
+    if (existing_data != nullptr) {
+      if (*existing_quicken_data != quicken_data ||
+          existing_data->optimized_return_void_ != optimized_return_void) {
+        VLOG(compiler) << "Quicken data mismatch, for method "
                        << dex_file.PrettyMethod(method_idx);
-        // Unquicken using the existing quicken data.
-        optimizer::ArtDecompileDEX(dex_file,
-                                   *code_item,
-                                   ArrayRef<const uint8_t>(existing_data->quicken_data_),
-                                   /* decompile_return_instruction*/ false);
-        // Go clear the vmaps for all the methods that were already quickened to avoid writing them
-        // out during oat writing.
-        for (const MethodReference& ref : existing_data->methods_) {
-          CompiledMethod* method = driver_->GetCompiledMethod(ref);
-          DCHECK(method != nullptr);
-          method->ReleaseVMapTable();
-        }
-        // Blacklist the method to never attempt to quicken it in the future.
-        blacklisted_code_items_.insert(code_item);
-        shared_code_item_quicken_info_.erase(existing);
-        return nullptr;
+        // Mark the method as a conflict to never attempt to quicken it in the future.
+        existing_data->conflict_ = true;
       }
       existing_data->methods_.push_back(method_ref);
     } else {
       QuickenState new_state;
       new_state.methods_.push_back(method_ref);
       new_state.quicken_data_ = quicken_data;
+      new_state.optimized_return_void_ = optimized_return_void;
       bool inserted = shared_code_item_quicken_info_.emplace(code_item, new_state).second;
       CHECK(inserted) << "Failed to insert " << dex_file.PrettyMethod(method_idx);
     }
@@ -556,9 +626,65 @@
       ArrayRef<const uint8_t>(quicken_data),       // vmap_table
       ArrayRef<const uint8_t>(),                   // cfi data
       ArrayRef<const linker::LinkerPatch>());
+  DCHECK(ret != nullptr);
   return ret;
 }
 
+void DexToDexCompiler::SetDexFiles(const std::vector<const DexFile*>& dex_files) {
+  // Record what code items are already seen to detect when multiple methods have the same code
+  // item.
+  std::unordered_set<const DexFile::CodeItem*> seen_code_items;
+  for (const DexFile* dex_file : dex_files) {
+    for (size_t i = 0; i < dex_file->NumClassDefs(); ++i) {
+      const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
+      const uint8_t* class_data = dex_file->GetClassData(class_def);
+      if (class_data == nullptr) {
+        continue;
+      }
+      ClassDataItemIterator it(*dex_file, class_data);
+      it.SkipAllFields();
+      for (; it.HasNextMethod(); it.Next()) {
+        const DexFile::CodeItem* code_item = it.GetMethodCodeItem();
+        // Detect the shared code items.
+        if (!seen_code_items.insert(code_item).second) {
+          shared_code_items_.insert(code_item);
+        }
+      }
+    }
+  }
+  VLOG(compiler) << "Shared code items " << shared_code_items_.size();
+}
+
+void DexToDexCompiler::UnquickenConflictingMethods() {
+  MutexLock mu(Thread::Current(), lock_);
+  size_t unquicken_count = 0;
+  for (const auto& pair : shared_code_item_quicken_info_) {
+    const DexFile::CodeItem* code_item = pair.first;
+    const QuickenState& state = pair.second;
+    CHECK_GE(state.methods_.size(), 1u);
+    if (state.conflict_) {
+      // Unquicken using the existing quicken data.
+      // TODO: Do we really need to pass a dex file in?
+      optimizer::ArtDecompileDEX(*state.methods_[0].dex_file,
+                                 *code_item,
+                                 ArrayRef<const uint8_t>(state.quicken_data_),
+                                 /* decompile_return_instruction*/ true);
+      ++unquicken_count;
+      // Go clear the vmaps for all the methods that were already quickened to avoid writing them
+      // out during oat writing.
+      for (const MethodReference& ref : state.methods_) {
+        CompiledMethod* method = driver_->RemoveCompiledMethod(ref);
+        if (method != nullptr) {
+          // There is up to one compiled method for each method ref. Releasing it leaves the
+          // deduped data intact, this means its safe to do even when other threads might be
+          // compiling.
+          CompiledMethod::ReleaseSwapAllocatedCompiledMethod(driver_, method);
+        }
+      }
+    }
+  }
+}
+
 }  // namespace optimizer
 
 }  // namespace art
diff --git a/compiler/dex/dex_to_dex_compiler.h b/compiler/dex/dex_to_dex_compiler.h
index 2105a9d..7df09f1 100644
--- a/compiler/dex/dex_to_dex_compiler.h
+++ b/compiler/dex/dex_to_dex_compiler.h
@@ -59,99 +59,35 @@
                                 const CompilationLevel compilation_level) WARN_UNUSED;
 
   void MarkForCompilation(Thread* self,
-                          const MethodReference& method_ref,
-                          const DexFile::CodeItem* code_item);
+                          const MethodReference& method_ref);
 
   void ClearState();
 
+  // Unquicken all methods that have conflicting quicken info. This is not done during the
+  // quickening process to avoid race conditions.
+  void UnquickenConflictingMethods();
+
   CompilerDriver* GetDriver() {
     return driver_;
   }
 
   bool ShouldCompileMethod(const MethodReference& ref);
 
-  size_t NumUniqueCodeItems(Thread* self) const;
+  // Return the number of code items to quicken.
+  size_t NumCodeItemsToQuicken(Thread* self) const;
+
+  void SetDexFiles(const std::vector<const DexFile*>& dex_files);
 
  private:
   // Holds the state for compiling a single method.
-  struct CompilationState {
-    struct QuickenedInfo {
-      QuickenedInfo(uint32_t pc, uint16_t index) : dex_pc(pc), dex_member_index(index) {}
+  struct CompilationState;
 
-      uint32_t dex_pc;
-      uint16_t dex_member_index;
-    };
-
-    CompilationState(DexToDexCompiler* compiler,
-                     const DexCompilationUnit& unit,
-                     const CompilationLevel compilation_level,
-                     const std::vector<uint8_t>* quicken_data);
-
-    const std::vector<QuickenedInfo>& GetQuickenedInfo() const {
-      return quickened_info_;
-    }
-
-    // Returns the quickening info, or an empty array if it was not quickened.
-    // If already_quickened is true, then don't change anything but still return what the quicken
-    // data would have been.
-    std::vector<uint8_t> Compile();
-
-    const DexFile& GetDexFile() const;
-
-    // Compiles a RETURN-VOID into a RETURN-VOID-BARRIER within a constructor where
-    // a barrier is required.
-    void CompileReturnVoid(Instruction* inst, uint32_t dex_pc);
-
-    // Compiles a CHECK-CAST into 2 NOP instructions if it is known to be safe. In
-    // this case, returns the second NOP instruction pointer. Otherwise, returns
-    // the given "inst".
-    Instruction* CompileCheckCast(Instruction* inst, uint32_t dex_pc);
-
-    // Compiles a field access into a quick field access.
-    // The field index is replaced by an offset within an Object where we can read
-    // from / write to this field. Therefore, this does not involve any resolution
-    // at runtime.
-    // Since the field index is encoded with 16 bits, we can replace it only if the
-    // field offset can be encoded with 16 bits too.
-    void CompileInstanceFieldAccess(Instruction* inst, uint32_t dex_pc,
-                                    Instruction::Code new_opcode, bool is_put);
-
-    // Compiles a virtual method invocation into a quick virtual method invocation.
-    // The method index is replaced by the vtable index where the corresponding
-    // executable can be found. Therefore, this does not involve any resolution
-    // at runtime.
-    // Since the method index is encoded with 16 bits, we can replace it only if the
-    // vtable index can be encoded with 16 bits too.
-    void CompileInvokeVirtual(Instruction* inst, uint32_t dex_pc,
-                              Instruction::Code new_opcode, bool is_range);
-
-    // Return the next index.
-    uint16_t NextIndex();
-
-    // Returns the dequickened index if an instruction is quickened, otherwise return index.
-    uint16_t GetIndexForInstruction(const Instruction* inst, uint32_t index);
-
-    DexToDexCompiler* const compiler_;
-    CompilerDriver& driver_;
-    const DexCompilationUnit& unit_;
-    const CompilationLevel compilation_level_;
-
-    // Filled by the compiler when quickening, in order to encode that information
-    // in the .oat file. The runtime will use that information to get to the original
-    // opcodes.
-    std::vector<QuickenedInfo> quickened_info_;
-
-    // If the code item was already quickened previously.
-    const bool already_quickened_;
-    const QuickenInfoTable existing_quicken_info_;
-    uint32_t quicken_index_ = 0u;
-
-    DISALLOW_COPY_AND_ASSIGN(CompilationState);
-  };
-
+  // Quicken state for a code item, may be referenced by multiple methods.
   struct QuickenState {
     std::vector<MethodReference> methods_;
     std::vector<uint8_t> quicken_data_;
+    bool optimized_return_void_ = false;
+    bool conflict_ = false;
   };
 
   BitVector* GetOrAddBitVectorForDex(const DexFile* dex_file) REQUIRES(lock_);
@@ -166,15 +102,14 @@
   mutable Mutex lock_;
   // Record what method references are going to get quickened.
   std::unordered_map<const DexFile*, BitVector> should_quicken_;
-  // Record what code items are already seen to detect when multiple methods have the same code
-  // item.
-  std::unordered_set<const DexFile::CodeItem*> seen_code_items_ GUARDED_BY(lock_);
   // Guarded by lock_ during writing, accessed without a lock during quickening.
   // This is safe because no thread is adding to the shared code items during the quickening phase.
   std::unordered_set<const DexFile::CodeItem*> shared_code_items_;
-  std::unordered_set<const DexFile::CodeItem*> blacklisted_code_items_ GUARDED_BY(lock_);
+  // Blacklisted code items are unquickened in UnquickenConflictingMethods.
   std::unordered_map<const DexFile::CodeItem*, QuickenState> shared_code_item_quicken_info_
       GUARDED_BY(lock_);
+  // Number of added code items.
+  size_t num_code_items_ GUARDED_BY(lock_) = 0u;
 };
 
 std::ostream& operator<<(std::ostream& os, const DexToDexCompiler::CompilationLevel& rhs);
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index b3adf10..fb428b8 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -656,7 +656,7 @@
               optimizer::DexToDexCompiler::CompilationLevel::kDontDexToDexCompile) {
         DCHECK(!Runtime::Current()->UseJitCompilation());
         // TODO: add a command-line option to disable DEX-to-DEX compilation ?
-        driver->GetDexToDexCompiler().MarkForCompilation(self, method_ref, code_item);
+        driver->GetDexToDexCompiler().MarkForCompilation(self, method_ref);
       }
     }
     return compiled_method;
@@ -730,7 +730,7 @@
                      true,
                      dex_cache);
 
-  const size_t num_methods = dex_to_dex_compiler_.NumUniqueCodeItems(self);
+  const size_t num_methods = dex_to_dex_compiler_.NumCodeItemsToQuicken(self);
   if (num_methods != 0) {
     DCHECK_EQ(num_methods, 1u);
     CompileMethodDex2Dex(self,
@@ -2808,7 +2808,7 @@
     Runtime::Current()->ReclaimArenaPoolMemory();
   }
 
-  if (dex_to_dex_compiler_.NumUniqueCodeItems(Thread::Current()) > 0u) {
+  if (dex_to_dex_compiler_.NumCodeItemsToQuicken(Thread::Current()) > 0u) {
     // TODO: Not visit all of the dex files, its probably rare that only one would have quickened
     // methods though.
     for (const DexFile* dex_file : dex_files) {
@@ -2840,6 +2840,12 @@
   DCHECK(GetCompiledMethod(method_ref) != nullptr) << method_ref.PrettyMethod();
 }
 
+CompiledMethod* CompilerDriver::RemoveCompiledMethod(const MethodReference& method_ref) {
+  CompiledMethod* ret = nullptr;
+  CHECK(compiled_methods_.Remove(method_ref, &ret));
+  return ret;
+}
+
 bool CompilerDriver::GetCompiledClass(const ClassReference& ref, ClassStatus* status) const {
   DCHECK(status != nullptr);
   // The table doesn't know if something wasn't inserted. For this case it will return
@@ -3013,6 +3019,7 @@
 void CompilerDriver::SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) {
   dex_files_for_oat_file_ = dex_files;
   compiled_classes_.AddDexFiles(dex_files);
+  dex_to_dex_compiler_.SetDexFiles(dex_files);
 }
 
 void CompilerDriver::SetClasspathDexFiles(const std::vector<const DexFile*>& dex_files) {
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index e32b5c4..2b524a3 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -165,6 +165,7 @@
   void AddCompiledMethod(const MethodReference& method_ref,
                          CompiledMethod* const compiled_method,
                          size_t non_relative_linker_patch_count);
+  CompiledMethod* RemoveCompiledMethod(const MethodReference& method_ref);
 
   void SetRequiresConstructorBarrier(Thread* self,
                                      const DexFile* dex_file,
diff --git a/compiler/utils/atomic_dex_ref_map-inl.h b/compiler/utils/atomic_dex_ref_map-inl.h
index 203e484..7023b9a 100644
--- a/compiler/utils/atomic_dex_ref_map-inl.h
+++ b/compiler/utils/atomic_dex_ref_map-inl.h
@@ -75,6 +75,18 @@
 }
 
 template <typename DexFileReferenceType, typename Value>
+inline bool AtomicDexRefMap<DexFileReferenceType, Value>::Remove(const DexFileReferenceType& ref,
+                                                                 Value* out) {
+  ElementArray* const array = GetArray(ref.dex_file);
+  if (array == nullptr) {
+    return false;
+  }
+  *out = (*array)[ref.index].LoadRelaxed();
+  (*array)[ref.index].StoreSequentiallyConsistent(nullptr);
+  return true;
+}
+
+template <typename DexFileReferenceType, typename Value>
 inline void AtomicDexRefMap<DexFileReferenceType, Value>::AddDexFile(const DexFile* dex_file) {
   arrays_.Put(dex_file, std::move(ElementArray(NumberOfDexIndices(dex_file))));
 }
diff --git a/compiler/utils/atomic_dex_ref_map.h b/compiler/utils/atomic_dex_ref_map.h
index 9ff506d..3474e16 100644
--- a/compiler/utils/atomic_dex_ref_map.h
+++ b/compiler/utils/atomic_dex_ref_map.h
@@ -45,6 +45,9 @@
   // Retreive an item, returns false if the dex file is not added.
   bool Get(const DexFileReferenceType& ref, Value* out) const;
 
+  // Remove an item and return the existing value. Returns false if the dex file is not added.
+  bool Remove(const DexFileReferenceType& ref, Value* out);
+
   // Dex files must be added before method references belonging to them can be used as keys. Not
   // thread safe.
   void AddDexFile(const DexFile* dex_file);
diff --git a/dexlayout/compact_dex_writer.cc b/dexlayout/compact_dex_writer.cc
index 08438c4..9462fe5 100644
--- a/dexlayout/compact_dex_writer.cc
+++ b/dexlayout/compact_dex_writer.cc
@@ -460,6 +460,11 @@
     // Rewrite the header with the calculated checksum.
     WriteHeader(main_stream);
   }
+
+  // Clear the dedupe to prevent interdex code item deduping. This does not currently work well with
+  // dex2oat's class unloading. The issue is that verification encounters quickened opcodes after
+  // the first dex gets unloaded.
+  code_item_dedupe_->Clear();
 }
 
 std::unique_ptr<DexContainer> CompactDexWriter::CreateDexContainer() const {
diff --git a/dexlayout/compact_dex_writer.h b/dexlayout/compact_dex_writer.h
index 24d0fbf..ea9f7d1 100644
--- a/dexlayout/compact_dex_writer.h
+++ b/dexlayout/compact_dex_writer.h
@@ -44,6 +44,11 @@
     // Returns the offset of the deduplicated data or kDidNotDedupe did deduplication did not occur.
     uint32_t Dedupe(uint32_t data_start, uint32_t data_end, uint32_t item_offset);
 
+    // Clear dedupe state to prevent deduplication against existing items in the future.
+    void Clear() {
+      dedupe_map_.clear();
+    }
+
    private:
     class HashedMemoryRange {
      public:
diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h
index d2f9cb9..5635271 100644
--- a/dexlayout/dexlayout.h
+++ b/dexlayout/dexlayout.h
@@ -66,8 +66,7 @@
   bool visualize_pattern_ = false;
   bool update_checksum_ = false;
   CompactDexLevel compact_dex_level_ = CompactDexLevel::kCompactDexLevelNone;
-  // Disabled until dex2oat properly handles quickening of deduped code items.
-  bool dedupe_code_items_ = false;
+  bool dedupe_code_items_ = true;
   OutputFormat output_format_ = kOutputPlain;
   const char* output_dex_directory_ = nullptr;
   const char* output_file_name_ = nullptr;
diff --git a/runtime/dex/dex_file_layout.h b/runtime/dex/dex_file_layout.h
index a7b9051..793e3b5 100644
--- a/runtime/dex/dex_file_layout.h
+++ b/runtime/dex/dex_file_layout.h
@@ -83,7 +83,7 @@
     }
 
     void CombineSection(uint32_t start_offset, uint32_t end_offset) {
-      DCHECK_LT(start_offset, end_offset);
+      DCHECK_LE(start_offset, end_offset);
       if (start_offset_ == end_offset_) {
         start_offset_ = start_offset;
         end_offset_ = end_offset;
diff --git a/test/ManyMethods/ManyMethods.java b/test/ManyMethods/ManyMethods.java
index b3a57f6..98b9faf 100644
--- a/test/ManyMethods/ManyMethods.java
+++ b/test/ManyMethods/ManyMethods.java
@@ -26,6 +26,8 @@
     public static String msg7 = "Hello World7";
     public static String msg8 = "Hello World8";
     public static String msg9 = "Hello World9";
+    public static String msg10 = "Hello World10";
+    public static String msg11 = "Hello World11";
   }
 
   static class Printer {
@@ -57,35 +59,35 @@
   }
 
   public static void Print4() {
-    Printer.Print(Strings.msg2);
+    Printer.Print(Strings.msg4);
   }
 
   public static void Print5() {
-    Printer.Print(Strings.msg3);
-  }
-
-  public static void Print6() {
-    Printer2.Print(Strings.msg4);
-  }
-
-  public static void Print7() {
     Printer.Print(Strings.msg5);
   }
 
+  public static void Print6() {
+    Printer2.Print(Strings.msg6);
+  }
+
+  public static void Print7() {
+    Printer.Print(Strings.msg7);
+  }
+
   public static void Print8() {
-    Printer.Print(Strings.msg6);
+    Printer.Print(Strings.msg8);
   }
 
   public static void Print9() {
-    Printer2.Print(Strings.msg7);
+    Printer2.Print(Strings.msg9);
   }
 
   public static void Print10() {
-    Printer2.Print(Strings.msg8);
+    Printer2.Print(Strings.msg10);
   }
 
   public static void Print11() {
-    Printer.Print(Strings.msg9);
+    Printer.Print(Strings.msg11);
   }
 
   public static void main(String args[]) {