Merge "Revert "Assembly TLAB and RegionTLAB allocation fast path for x86""
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 8309bd8..e9b6a6d 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -269,12 +269,14 @@
   compiler/optimizing/nodes_test.cc \
   compiler/optimizing/parallel_move_test.cc \
   compiler/optimizing/pretty_printer_test.cc \
+  compiler/optimizing/reference_type_propagation_test.cc \
   compiler/optimizing/side_effects_test.cc \
   compiler/optimizing/ssa_test.cc \
   compiler/optimizing/stack_map_test.cc \
   compiler/optimizing/suspend_check_test.cc \
   compiler/utils/arena_allocator_test.cc \
   compiler/utils/dedupe_set_test.cc \
+  compiler/utils/intrusive_forward_list_test.cc \
   compiler/utils/swap_space_test.cc \
   compiler/utils/test_dex_file_builder_test.cc \
 
diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc
index b4ecbd8..7277107 100644
--- a/compiler/linker/arm64/relative_patcher_arm64.cc
+++ b/compiler/linker/arm64/relative_patcher_arm64.cc
@@ -210,7 +210,14 @@
   } else {
     if ((insn & 0xfffffc00) == 0x91000000) {
       // ADD immediate, 64-bit with imm12 == 0 (unset).
-      DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative) << patch.GetType();
+      if (!kEmitCompilerReadBarrier) {
+        DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative) << patch.GetType();
+      } else {
+        // With the read barrier (non-baker) enabled, it could be kDexCacheArray in the
+        // HLoadString::LoadKind::kDexCachePcRelative case of VisitLoadString().
+        DCHECK(patch.GetType() == LinkerPatch::Type::kStringRelative ||
+               patch.GetType() == LinkerPatch::Type::kDexCacheArray) << patch.GetType();
+      }
       shift = 0u;  // No shift for ADD.
     } else {
       // LDR 32-bit or 64-bit with imm12 == 0 (unset).
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index 084360f..659c6f8 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -1206,9 +1206,9 @@
           GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination));
       ArenaVector<HBoundsCheck*> standby(
           GetGraph()->GetArena()->Adapter(kArenaAllocBoundsCheckElimination));
-      for (HUseIterator<HInstruction*> it2(array_length->GetUses()); !it2.Done(); it2.Advance()) {
+      for (const HUseListNode<HInstruction*>& use : array_length->GetUses()) {
         // Another bounds check in same or dominated block?
-        HInstruction* user = it2.Current()->GetUser();
+        HInstruction* user = use.GetUser();
         HBasicBlock* other_block = user->GetBlock();
         if (user->IsBoundsCheck() && block->Dominates(other_block)) {
           HBoundsCheck* other_bounds_check = user->AsBoundsCheck();
@@ -1635,29 +1635,33 @@
         Primitive::Type type = instruction->GetType();
         HPhi* phi = nullptr;
         // Scan all uses of an instruction and replace each later use with a phi node.
-        for (HUseIterator<HInstruction*> it2(instruction->GetUses());
-             !it2.Done();
-             it2.Advance()) {
-          HInstruction* user = it2.Current()->GetUser();
+        const HUseList<HInstruction*>& uses = instruction->GetUses();
+        for (auto it2 = uses.begin(), end2 = uses.end(); it2 != end2; /* ++it2 below */) {
+          HInstruction* user = it2->GetUser();
+          size_t index = it2->GetIndex();
+          // Increment `it2` now because `*it2` may disappear thanks to user->ReplaceInput().
+          ++it2;
           if (user->GetBlock() != true_block) {
             if (phi == nullptr) {
               phi = NewPhi(new_preheader, instruction, type);
             }
-            user->ReplaceInput(phi, it2.Current()->GetIndex());
+            user->ReplaceInput(phi, index);  // Removes the use node from the list.
           }
         }
         // Scan all environment uses of an instruction and replace each later use with a phi node.
-        for (HUseIterator<HEnvironment*> it2(instruction->GetEnvUses());
-             !it2.Done();
-             it2.Advance()) {
-          HEnvironment* user = it2.Current()->GetUser();
+        const HUseList<HEnvironment*>& env_uses = instruction->GetEnvUses();
+        for (auto it2 = env_uses.begin(), end2 = env_uses.end(); it2 != end2; /* ++it2 below */) {
+          HEnvironment* user = it2->GetUser();
+          size_t index = it2->GetIndex();
+          // Increment `it2` now because `*it2` may disappear thanks to user->RemoveAsUserOfInput().
+          ++it2;
           if (user->GetHolder()->GetBlock() != true_block) {
             if (phi == nullptr) {
               phi = NewPhi(new_preheader, instruction, type);
             }
-            user->RemoveAsUserOfInput(it2.Current()->GetIndex());
-            user->SetRawEnvAt(it2.Current()->GetIndex(), phi);
-            phi->AddEnvUseAt(user, it2.Current()->GetIndex());
+            user->RemoveAsUserOfInput(index);
+            user->SetRawEnvAt(index, phi);
+            phi->AddEnvUseAt(user, index);
           }
         }
       }
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index 4f46d5e..580ef72 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -51,7 +51,7 @@
         compiler_driver_(driver),
         compilation_stats_(compiler_stats),
         block_builder_(graph, dex_file, code_item),
-        ssa_builder_(graph, handles),
+        ssa_builder_(graph, dex_compilation_unit->GetDexCache(), handles),
         instruction_builder_(graph,
                              &block_builder_,
                              &ssa_builder_,
@@ -78,7 +78,7 @@
         null_dex_cache_(),
         compilation_stats_(nullptr),
         block_builder_(graph, nullptr, code_item),
-        ssa_builder_(graph, handles),
+        ssa_builder_(graph, null_dex_cache_, handles),
         instruction_builder_(graph,
                              &block_builder_,
                              &ssa_builder_,
diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h
index 6412b24..a849448 100644
--- a/compiler/optimizing/common_arm64.h
+++ b/compiler/optimizing/common_arm64.h
@@ -199,7 +199,7 @@
 
   // For single uses we let VIXL handle the constant generation since it will
   // use registers that are not managed by the register allocator (wip0, wip1).
-  if (constant->GetUses().HasOnlyOneUse()) {
+  if (constant->GetUses().HasExactlyOneElement()) {
     return true;
   }
 
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc
index 9ea4b2d..96837a8 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -342,36 +342,34 @@
 
   // Ensure the uses of `instruction` are defined in a block of the graph,
   // and the entry in the use list is consistent.
-  for (HUseIterator<HInstruction*> use_it(instruction->GetUses());
-       !use_it.Done(); use_it.Advance()) {
-    HInstruction* use = use_it.Current()->GetUser();
-    const HInstructionList& list = use->IsPhi()
-        ? use->GetBlock()->GetPhis()
-        : use->GetBlock()->GetInstructions();
-    if (!list.Contains(use)) {
+  for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
+    HInstruction* user = use.GetUser();
+    const HInstructionList& list = user->IsPhi()
+        ? user->GetBlock()->GetPhis()
+        : user->GetBlock()->GetInstructions();
+    if (!list.Contains(user)) {
       AddError(StringPrintf("User %s:%d of instruction %d is not defined "
                             "in a basic block of the control-flow graph.",
-                            use->DebugName(),
-                            use->GetId(),
+                            user->DebugName(),
+                            user->GetId(),
                             instruction->GetId()));
     }
-    size_t use_index = use_it.Current()->GetIndex();
-    if ((use_index >= use->InputCount()) || (use->InputAt(use_index) != instruction)) {
+    size_t use_index = use.GetIndex();
+    if ((use_index >= user->InputCount()) || (user->InputAt(use_index) != instruction)) {
       AddError(StringPrintf("User %s:%d of instruction %s:%d has a wrong "
                             "UseListNode index.",
-                            use->DebugName(),
-                            use->GetId(),
+                            user->DebugName(),
+                            user->GetId(),
                             instruction->DebugName(),
                             instruction->GetId()));
     }
   }
 
   // Ensure the environment uses entries are consistent.
-  for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses());
-       !use_it.Done(); use_it.Advance()) {
-    HEnvironment* use = use_it.Current()->GetUser();
-    size_t use_index = use_it.Current()->GetIndex();
-    if ((use_index >= use->Size()) || (use->GetInstructionAt(use_index) != instruction)) {
+  for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
+    HEnvironment* user = use.GetUser();
+    size_t use_index = use.GetIndex();
+    if ((use_index >= user->Size()) || (user->GetInstructionAt(use_index) != instruction)) {
       AddError(StringPrintf("Environment user of %s:%d has a wrong "
                             "UseListNode index.",
                             instruction->DebugName(),
@@ -383,13 +381,11 @@
   for (size_t i = 0, e = instruction->InputCount(); i < e; ++i) {
     HUserRecord<HInstruction*> input_record = instruction->InputRecordAt(i);
     HInstruction* input = input_record.GetInstruction();
-    HUseListNode<HInstruction*>* use_node = input_record.GetUseNode();
-    size_t use_index = use_node->GetIndex();
-    if ((use_node == nullptr)
-        || !input->GetUses().Contains(use_node)
-        || (use_index >= e)
-        || (use_index != i)) {
-      AddError(StringPrintf("Instruction %s:%d has an invalid pointer to use entry "
+    if ((input_record.GetBeforeUseNode() == input->GetUses().end()) ||
+        (input_record.GetUseNode() == input->GetUses().end()) ||
+        !input->GetUses().ContainsNode(*input_record.GetUseNode()) ||
+        (input_record.GetUseNode()->GetIndex() != i)) {
+      AddError(StringPrintf("Instruction %s:%d has an invalid iterator before use entry "
                             "at input %u (%s:%d).",
                             instruction->DebugName(),
                             instruction->GetId(),
@@ -400,18 +396,17 @@
   }
 
   // Ensure an instruction dominates all its uses.
-  for (HUseIterator<HInstruction*> use_it(instruction->GetUses());
-       !use_it.Done(); use_it.Advance()) {
-    HInstruction* use = use_it.Current()->GetUser();
-    if (!use->IsPhi() && !instruction->StrictlyDominates(use)) {
+  for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
+    HInstruction* user = use.GetUser();
+    if (!user->IsPhi() && !instruction->StrictlyDominates(user)) {
       AddError(StringPrintf("Instruction %s:%d in block %d does not dominate "
                             "use %s:%d in block %d.",
                             instruction->DebugName(),
                             instruction->GetId(),
                             current_block_->GetBlockId(),
-                            use->DebugName(),
-                            use->GetId(),
-                            use->GetBlock()->GetBlockId()));
+                            user->DebugName(),
+                            user->GetId(),
+                            user->GetBlock()->GetBlockId()));
     }
   }
 
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index fe47f7d..9efc13f 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -608,12 +608,7 @@
     for (HInstructionIterator it(list); !it.Done(); it.Advance()) {
       HInstruction* instruction = it.Current();
       int bci = 0;
-      size_t num_uses = 0;
-      for (HUseIterator<HInstruction*> use_it(instruction->GetUses());
-           !use_it.Done();
-           use_it.Advance()) {
-        ++num_uses;
-      }
+      size_t num_uses = instruction->GetUses().SizeSlow();
       AddIndent();
       output_ << bci << " " << num_uses << " "
               << GetTypeId(instruction->GetType()) << instruction->GetId() << " ";
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 77e0cbc..836bcfa 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -411,7 +411,10 @@
 
   // Run type propagation to get the guard typed, and eventually propagate the
   // type of the receiver.
-  ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false);
+  ReferenceTypePropagation rtp_fixup(graph_,
+                                     outer_compilation_unit_.GetDexCache(),
+                                     handles_,
+                                     /* is_first_run */ false);
   rtp_fixup.Run();
 
   MaybeRecordStat(kInlinedMonomorphicCall);
@@ -532,7 +535,10 @@
   MaybeRecordStat(kInlinedPolymorphicCall);
 
   // Run type propagation to get the guards typed.
-  ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false);
+  ReferenceTypePropagation rtp_fixup(graph_,
+                                     outer_compilation_unit_.GetDexCache(),
+                                     handles_,
+                                     /* is_first_run */ false);
   rtp_fixup.Run();
   return true;
 }
@@ -709,7 +715,10 @@
   deoptimize->CopyEnvironmentFrom(invoke_instruction->GetEnvironment());
 
   // Run type propagation to get the guard typed.
-  ReferenceTypePropagation rtp_fixup(graph_, handles_, /* is_first_run */ false);
+  ReferenceTypePropagation rtp_fixup(graph_,
+                                     outer_compilation_unit_.GetDexCache(),
+                                     handles_,
+                                     /* is_first_run */ false);
   rtp_fixup.Run();
 
   MaybeRecordStat(kInlinedPolymorphicCall);
@@ -971,7 +980,8 @@
       // dex pc for the associated stack map. 0 is bogus but valid. Bug: 26854537.
       /* dex_pc */ 0);
   if (iget->GetType() == Primitive::kPrimNot) {
-    ReferenceTypePropagation rtp(graph_, handles_, /* is_first_run */ false);
+    // Use the same dex_cache that we used for field lookup as the hint_dex_cache.
+    ReferenceTypePropagation rtp(graph_, dex_cache, handles_, /* is_first_run */ false);
     rtp.Visit(iget);
   }
   return iget;
@@ -1303,11 +1313,12 @@
         DCHECK(return_replacement->IsPhi());
         size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
         mirror::Class* cls = resolved_method->GetReturnType(false /* resolve */, pointer_size);
-        if (cls != nullptr) {
+        if (cls != nullptr && !cls->IsErroneous()) {
           ReferenceTypeInfo::TypeHandle return_handle = handles_->NewHandle(cls);
           return_replacement->SetReferenceTypeInfo(ReferenceTypeInfo::Create(
               return_handle, return_handle->CannotBeAssignedFromOtherTypes() /* is_exact */));
         } else {
+          // Return inexact object type on failures.
           return_replacement->SetReferenceTypeInfo(graph_->GetInexactObjectRti());
         }
       }
@@ -1319,13 +1330,19 @@
         if (invoke_rti.IsStrictSupertypeOf(return_rti)
             || (return_rti.IsExact() && !invoke_rti.IsExact())
             || !return_replacement->CanBeNull()) {
-          ReferenceTypePropagation(graph_, handles_, /* is_first_run */ false).Run();
+          ReferenceTypePropagation(graph_,
+                                   outer_compilation_unit_.GetDexCache(),
+                                   handles_,
+                                   /* is_first_run */ false).Run();
         }
       }
     } else if (return_replacement->IsInstanceOf()) {
       if (do_rtp) {
         // Inlining InstanceOf into an If may put a tighter bound on reference types.
-        ReferenceTypePropagation(graph_, handles_, /* is_first_run */ false).Run();
+        ReferenceTypePropagation(graph_,
+                                 outer_compilation_unit_.GetDexCache(),
+                                 handles_,
+                                 /* is_first_run */ false).Run();
       }
     }
   }
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index 1f66db7..d7b3856 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -409,9 +409,9 @@
     return true;
   }
 
-  for (HUseIterator<HInstruction*> it(input->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* use = it.Current()->GetUser();
-    if (use->IsNullCheck() && use->StrictlyDominates(at)) {
+  for (const HUseListNode<HInstruction*>& use : input->GetUses()) {
+    HInstruction* user = use.GetUser();
+    if (user->IsNullCheck() && user->StrictlyDominates(at)) {
       return true;
     }
   }
@@ -1070,12 +1070,12 @@
   }
 
   // Is the Compare only used for this purpose?
-  if (!left->GetUses().HasOnlyOneUse()) {
+  if (!left->GetUses().HasExactlyOneElement()) {
     // Someone else also wants the result of the compare.
     return;
   }
 
-  if (!left->GetEnvUses().IsEmpty()) {
+  if (!left->GetEnvUses().empty()) {
     // There is a reference to the compare result in an environment. Do we really need it?
     if (GetGraph()->IsDebuggable()) {
       return;
diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc
index f00d960..e4a711e 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.cc
+++ b/compiler/optimizing/instruction_simplifier_arm64.cc
@@ -140,7 +140,7 @@
                                                                  shift_amount,
                                                                  use->GetDexPc());
     use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op);
-    if (bitfield_op->GetUses().IsEmpty()) {
+    if (bitfield_op->GetUses().empty()) {
       bitfield_op->GetBlock()->RemoveInstruction(bitfield_op);
     }
     RecordSimplification();
@@ -160,20 +160,22 @@
   const HUseList<HInstruction*>& uses = bitfield_op->GetUses();
 
   // Check whether we can merge the instruction in all its users' shifter operand.
-  for (HUseIterator<HInstruction*> it_use(uses); !it_use.Done(); it_use.Advance()) {
-    HInstruction* use = it_use.Current()->GetUser();
-    if (!HasShifterOperand(use)) {
+  for (const HUseListNode<HInstruction*>& use : uses) {
+    HInstruction* user = use.GetUser();
+    if (!HasShifterOperand(user)) {
       return false;
     }
-    if (!CanMergeIntoShifterOperand(use, bitfield_op)) {
+    if (!CanMergeIntoShifterOperand(user, bitfield_op)) {
       return false;
     }
   }
 
   // Merge the instruction into its uses.
-  for (HUseIterator<HInstruction*> it_use(uses); !it_use.Done(); it_use.Advance()) {
-    HInstruction* use = it_use.Current()->GetUser();
-    bool merged = MergeIntoShifterOperand(use, bitfield_op);
+  for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+    HInstruction* user = it->GetUser();
+    // Increment `it` now because `*it` will disappear thanks to MergeIntoShifterOperand().
+    ++it;
+    bool merged = MergeIntoShifterOperand(user, bitfield_op);
     DCHECK(merged);
   }
 
diff --git a/compiler/optimizing/instruction_simplifier_shared.cc b/compiler/optimizing/instruction_simplifier_shared.cc
index a11b5bd..dab1ebc 100644
--- a/compiler/optimizing/instruction_simplifier_shared.cc
+++ b/compiler/optimizing/instruction_simplifier_shared.cc
@@ -103,13 +103,10 @@
       return false;
   }
 
-  HInstruction* use = mul->HasNonEnvironmentUses()
-      ? mul->GetUses().GetFirst()->GetUser()
-      : nullptr;
-
   ArenaAllocator* arena = mul->GetBlock()->GetGraph()->GetArena();
 
   if (mul->HasOnlyOneNonEnvironmentUse()) {
+    HInstruction* use = mul->GetUses().front().GetUser();
     if (use->IsAdd() || use->IsSub()) {
       // Replace code looking like
       //    MUL tmp, x, y
diff --git a/compiler/optimizing/live_ranges_test.cc b/compiler/optimizing/live_ranges_test.cc
index bdaef1d..f9a955f 100644
--- a/compiler/optimizing/live_ranges_test.cc
+++ b/compiler/optimizing/live_ranges_test.cc
@@ -441,7 +441,7 @@
   ASSERT_TRUE(range->GetNext() == nullptr);
 
   HPhi* phi = liveness.GetInstructionFromSsaIndex(4)->AsPhi();
-  ASSERT_TRUE(phi->GetUses().HasOnlyOneUse());
+  ASSERT_TRUE(phi->GetUses().HasExactlyOneElement());
   interval = phi->GetLiveInterval();
   range = interval->GetFirstRange();
   ASSERT_EQ(26u, range->GetStart());
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index ac7ed86..2de4158 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -43,31 +43,29 @@
 
     // Visit all uses to determine if this reference can spread into the heap,
     // a method call, etc.
-    for (HUseIterator<HInstruction*> use_it(reference_->GetUses());
-         !use_it.Done();
-         use_it.Advance()) {
-      HInstruction* use = use_it.Current()->GetUser();
-      DCHECK(!use->IsNullCheck()) << "NullCheck should have been eliminated";
-      if (use->IsBoundType()) {
+    for (const HUseListNode<HInstruction*>& use : reference_->GetUses()) {
+      HInstruction* user = use.GetUser();
+      DCHECK(!user->IsNullCheck()) << "NullCheck should have been eliminated";
+      if (user->IsBoundType()) {
         // BoundType shouldn't normally be necessary for a NewInstance.
         // Just be conservative for the uncommon cases.
         is_singleton_ = false;
         is_singleton_and_not_returned_ = false;
         return;
       }
-      if (use->IsPhi() || use->IsSelect() || use->IsInvoke() ||
-          (use->IsInstanceFieldSet() && (reference_ == use->InputAt(1))) ||
-          (use->IsUnresolvedInstanceFieldSet() && (reference_ == use->InputAt(1))) ||
-          (use->IsStaticFieldSet() && (reference_ == use->InputAt(1))) ||
-          (use->IsUnresolvedStaticFieldSet() && (reference_ == use->InputAt(0))) ||
-          (use->IsArraySet() && (reference_ == use->InputAt(2)))) {
+      if (user->IsPhi() || user->IsSelect() || user->IsInvoke() ||
+          (user->IsInstanceFieldSet() && (reference_ == user->InputAt(1))) ||
+          (user->IsUnresolvedInstanceFieldSet() && (reference_ == user->InputAt(1))) ||
+          (user->IsStaticFieldSet() && (reference_ == user->InputAt(1))) ||
+          (user->IsUnresolvedStaticFieldSet() && (reference_ == user->InputAt(0))) ||
+          (user->IsArraySet() && (reference_ == user->InputAt(2)))) {
         // reference_ is merged to HPhi/HSelect, passed to a callee, or stored to heap.
         // reference_ isn't the only name that can refer to its value anymore.
         is_singleton_ = false;
         is_singleton_and_not_returned_ = false;
         return;
       }
-      if (use->IsReturn()) {
+      if (user->IsReturn()) {
         is_singleton_and_not_returned_ = false;
       }
     }
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 1afa36a..fe75451 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -618,7 +618,24 @@
     }
   }
 
-  if (is_irreducible_loop || graph->IsCompilingOsr()) {
+  if (!is_irreducible_loop && graph->IsCompilingOsr()) {
+    // When compiling in OSR mode, all loops in the compiled method may be entered
+    // from the interpreter. We treat this OSR entry point just like an extra entry
+    // to an irreducible loop, so we need to mark the method's loops as irreducible.
+    // This does not apply to inlined loops which do not act as OSR entry points.
+    if (suspend_check_ == nullptr) {
+      // Just building the graph in OSR mode, this loop is not inlined. We never build an
+      // inner graph in OSR mode as we can do OSR transition only from the outer method.
+      is_irreducible_loop = true;
+    } else {
+      // Look at the suspend check's environment to determine if the loop was inlined.
+      DCHECK(suspend_check_->HasEnvironment());
+      if (!suspend_check_->GetEnvironment()->IsFromInlinedInvoke()) {
+        is_irreducible_loop = true;
+      }
+    }
+  }
+  if (is_irreducible_loop) {
     irreducible_ = true;
     graph->SetHasIrreducibleLoops(true);
   }
@@ -681,8 +698,8 @@
     DCHECK_EQ(replacement->GetType(), Primitive::kPrimVoid);
     DCHECK_EQ(initial->GetBlock(), this);
     DCHECK_EQ(initial->GetType(), Primitive::kPrimVoid);
-    DCHECK(initial->GetUses().IsEmpty());
-    DCHECK(initial->GetEnvUses().IsEmpty());
+    DCHECK(initial->GetUses().empty());
+    DCHECK(initial->GetEnvUses().empty());
     replacement->SetBlock(this);
     replacement->SetId(GetGraph()->GetNextInstructionId());
     instructions_.InsertInstructionBefore(replacement, initial);
@@ -774,8 +791,8 @@
   instruction->SetBlock(nullptr);
   instruction_list->RemoveInstruction(instruction);
   if (ensure_safety) {
-    DCHECK(instruction->GetUses().IsEmpty());
-    DCHECK(instruction->GetEnvUses().IsEmpty());
+    DCHECK(instruction->GetUses().empty());
+    DCHECK(instruction->GetEnvUses().empty());
     RemoveAsUser(instruction);
   }
 }
@@ -839,8 +856,11 @@
 }
 
 void HEnvironment::RemoveAsUserOfInput(size_t index) const {
-  const HUserRecord<HEnvironment*>& user_record = vregs_[index];
-  user_record.GetInstruction()->RemoveEnvironmentUser(user_record.GetUseNode());
+  const HUserRecord<HEnvironment*>& env_use = vregs_[index];
+  HInstruction* user = env_use.GetInstruction();
+  auto before_env_use_node = env_use.GetBeforeUseNode();
+  user->env_uses_.erase_after(before_env_use_node);
+  user->FixUpUserRecordsAfterEnvUseRemoval(before_env_use_node);
 }
 
 HInstruction::InstructionKind HInstruction::GetKind() const {
@@ -985,30 +1005,36 @@
 
 void HInstruction::ReplaceWith(HInstruction* other) {
   DCHECK(other != nullptr);
-  for (HUseIterator<HInstruction*> it(GetUses()); !it.Done(); it.Advance()) {
-    HUseListNode<HInstruction*>* current = it.Current();
-    HInstruction* user = current->GetUser();
-    size_t input_index = current->GetIndex();
-    user->SetRawInputAt(input_index, other);
-    other->AddUseAt(user, input_index);
-  }
+  // Note: fixup_end remains valid across splice_after().
+  auto fixup_end = other->uses_.empty() ? other->uses_.begin() : ++other->uses_.begin();
+  other->uses_.splice_after(other->uses_.before_begin(), uses_);
+  other->FixUpUserRecordsAfterUseInsertion(fixup_end);
 
-  for (HUseIterator<HEnvironment*> it(GetEnvUses()); !it.Done(); it.Advance()) {
-    HUseListNode<HEnvironment*>* current = it.Current();
-    HEnvironment* user = current->GetUser();
-    size_t input_index = current->GetIndex();
-    user->SetRawEnvAt(input_index, other);
-    other->AddEnvUseAt(user, input_index);
-  }
+  // Note: env_fixup_end remains valid across splice_after().
+  auto env_fixup_end =
+      other->env_uses_.empty() ? other->env_uses_.begin() : ++other->env_uses_.begin();
+  other->env_uses_.splice_after(other->env_uses_.before_begin(), env_uses_);
+  other->FixUpUserRecordsAfterEnvUseInsertion(env_fixup_end);
 
-  uses_.Clear();
-  env_uses_.Clear();
+  DCHECK(uses_.empty());
+  DCHECK(env_uses_.empty());
 }
 
 void HInstruction::ReplaceInput(HInstruction* replacement, size_t index) {
-  RemoveAsUserOfInput(index);
-  SetRawInputAt(index, replacement);
-  replacement->AddUseAt(this, index);
+  HUserRecord<HInstruction*> input_use = InputRecordAt(index);
+  if (input_use.GetInstruction() == replacement) {
+    // Nothing to do.
+    return;
+  }
+  HUseList<HInstruction*>::iterator before_use_node = input_use.GetBeforeUseNode();
+  // Note: fixup_end remains valid across splice_after().
+  auto fixup_end =
+      replacement->uses_.empty() ? replacement->uses_.begin() : ++replacement->uses_.begin();
+  replacement->uses_.splice_after(replacement->uses_.before_begin(),
+                                  input_use.GetInstruction()->uses_,
+                                  before_use_node);
+  replacement->FixUpUserRecordsAfterUseInsertion(fixup_end);
+  input_use.GetInstruction()->FixUpUserRecordsAfterUseRemoval(before_use_node);
 }
 
 size_t HInstruction::EnvironmentSize() const {
@@ -1280,17 +1306,18 @@
   DCHECK_EQ(InputCount(), 0u);
 
   // Find the target block.
-  HUseIterator<HInstruction*> uses_it(GetUses());
-  HBasicBlock* target_block = uses_it.Current()->GetUser()->GetBlock();
-  uses_it.Advance();
-  while (!uses_it.Done() && uses_it.Current()->GetUser()->GetBlock() == target_block) {
-    uses_it.Advance();
+  auto uses_it = GetUses().begin();
+  auto uses_end = GetUses().end();
+  HBasicBlock* target_block = uses_it->GetUser()->GetBlock();
+  ++uses_it;
+  while (uses_it != uses_end && uses_it->GetUser()->GetBlock() == target_block) {
+    ++uses_it;
   }
-  if (!uses_it.Done()) {
+  if (uses_it != uses_end) {
     // This instruction has uses in two or more blocks. Find the common dominator.
     CommonDominator finder(target_block);
-    for (; !uses_it.Done(); uses_it.Advance()) {
-      finder.Update(uses_it.Current()->GetUser()->GetBlock());
+    for (; uses_it != uses_end; ++uses_it) {
+      finder.Update(uses_it->GetUser()->GetBlock());
     }
     target_block = finder.Get();
     DCHECK(target_block != nullptr);
@@ -1303,10 +1330,10 @@
 
   // Find insertion position.
   HInstruction* insert_pos = nullptr;
-  for (HUseIterator<HInstruction*> uses_it2(GetUses()); !uses_it2.Done(); uses_it2.Advance()) {
-    if (uses_it2.Current()->GetUser()->GetBlock() == target_block &&
-        (insert_pos == nullptr || uses_it2.Current()->GetUser()->StrictlyDominates(insert_pos))) {
-      insert_pos = uses_it2.Current()->GetUser();
+  for (const HUseListNode<HInstruction*>& use : GetUses()) {
+    if (use.GetUser()->GetBlock() == target_block &&
+        (insert_pos == nullptr || use.GetUser()->StrictlyDominates(insert_pos))) {
+      insert_pos = use.GetUser();
     }
   }
   if (insert_pos == nullptr) {
@@ -1586,10 +1613,10 @@
 static void RemoveUsesOfDeadInstruction(HInstruction* insn) {
   DCHECK(!insn->HasEnvironmentUses());
   while (insn->HasNonEnvironmentUses()) {
-    HUseListNode<HInstruction*>* use = insn->GetUses().GetFirst();
-    size_t use_index = use->GetIndex();
-    HBasicBlock* user_block =  use->GetUser()->GetBlock();
-    DCHECK(use->GetUser()->IsPhi() && user_block->IsCatchBlock());
+    const HUseListNode<HInstruction*>& use = insn->GetUses().front();
+    size_t use_index = use.GetIndex();
+    HBasicBlock* user_block =  use.GetUser()->GetBlock();
+    DCHECK(use.GetUser()->IsPhi() && user_block->IsCatchBlock());
     for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
       phi_it.Current()->AsPhi()->RemoveInputAt(use_index);
     }
@@ -2190,6 +2217,7 @@
   if (kIsDebugBuild) {
     ScopedObjectAccess soa(Thread::Current());
     DCHECK(IsValidHandle(type_handle));
+    DCHECK(!type_handle->IsErroneous());
     if (!is_exact) {
       DCHECK(!type_handle->CannotBeAssignedFromOtherTypes())
           << "Callers of ReferenceTypeInfo::Create should ensure is_exact is properly computed";
@@ -2391,12 +2419,11 @@
 }
 
 void HInstruction::RemoveEnvironmentUsers() {
-  for (HUseIterator<HEnvironment*> use_it(GetEnvUses()); !use_it.Done(); use_it.Advance()) {
-    HUseListNode<HEnvironment*>* user_node = use_it.Current();
-    HEnvironment* user = user_node->GetUser();
-    user->SetRawEnvAt(user_node->GetIndex(), nullptr);
+  for (const HUseListNode<HEnvironment*>& use : GetEnvUses()) {
+    HEnvironment* user = use.GetUser();
+    user->SetRawEnvAt(use.GetIndex(), nullptr);
   }
-  env_uses_.Clear();
+  env_uses_.clear();
 }
 
 // Returns an instruction with the opposite Boolean value from 'cond'.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 1ea2247..afb995d 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -36,6 +36,7 @@
 #include "offsets.h"
 #include "primitive.h"
 #include "utils/array_ref.h"
+#include "utils/intrusive_forward_list.h"
 
 namespace art {
 
@@ -169,7 +170,7 @@
     return handle.GetReference() != nullptr;
   }
 
-  bool IsValid() const SHARED_REQUIRES(Locks::mutator_lock_) {
+  bool IsValid() const {
     return IsValidHandle(type_handle_);
   }
 
@@ -1334,127 +1335,31 @@
   const H##type* As##type() const { return this; }                      \
   H##type* As##type() { return this; }
 
-template <typename T> class HUseList;
-
 template <typename T>
 class HUseListNode : public ArenaObject<kArenaAllocUseListNode> {
  public:
-  HUseListNode* GetPrevious() const { return prev_; }
-  HUseListNode* GetNext() const { return next_; }
   T GetUser() const { return user_; }
   size_t GetIndex() const { return index_; }
   void SetIndex(size_t index) { index_ = index; }
 
+  // Hook for the IntrusiveForwardList<>.
+  // TODO: Hide this better.
+  IntrusiveForwardListHook hook;
+
  private:
   HUseListNode(T user, size_t index)
-      : user_(user), index_(index), prev_(nullptr), next_(nullptr) {}
+      : user_(user), index_(index) {}
 
   T const user_;
   size_t index_;
-  HUseListNode<T>* prev_;
-  HUseListNode<T>* next_;
 
-  friend class HUseList<T>;
+  friend class HInstruction;
 
   DISALLOW_COPY_AND_ASSIGN(HUseListNode);
 };
 
 template <typename T>
-class HUseList : public ValueObject {
- public:
-  HUseList() : first_(nullptr) {}
-
-  void Clear() {
-    first_ = nullptr;
-  }
-
-  // Adds a new entry at the beginning of the use list and returns
-  // the newly created node.
-  HUseListNode<T>* AddUse(T user, size_t index, ArenaAllocator* arena) {
-    HUseListNode<T>* new_node = new (arena) HUseListNode<T>(user, index);
-    if (IsEmpty()) {
-      first_ = new_node;
-    } else {
-      first_->prev_ = new_node;
-      new_node->next_ = first_;
-      first_ = new_node;
-    }
-    return new_node;
-  }
-
-  HUseListNode<T>* GetFirst() const {
-    return first_;
-  }
-
-  void Remove(HUseListNode<T>* node) {
-    DCHECK(node != nullptr);
-    DCHECK(Contains(node));
-
-    if (node->prev_ != nullptr) {
-      node->prev_->next_ = node->next_;
-    }
-    if (node->next_ != nullptr) {
-      node->next_->prev_ = node->prev_;
-    }
-    if (node == first_) {
-      first_ = node->next_;
-    }
-  }
-
-  bool Contains(const HUseListNode<T>* node) const {
-    if (node == nullptr) {
-      return false;
-    }
-    for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) {
-      if (current == node) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  bool IsEmpty() const {
-    return first_ == nullptr;
-  }
-
-  bool HasOnlyOneUse() const {
-    return first_ != nullptr && first_->next_ == nullptr;
-  }
-
-  size_t SizeSlow() const {
-    size_t count = 0;
-    for (HUseListNode<T>* current = first_; current != nullptr; current = current->GetNext()) {
-      ++count;
-    }
-    return count;
-  }
-
- private:
-  HUseListNode<T>* first_;
-};
-
-template<typename T>
-class HUseIterator : public ValueObject {
- public:
-  explicit HUseIterator(const HUseList<T>& uses) : current_(uses.GetFirst()) {}
-
-  bool Done() const { return current_ == nullptr; }
-
-  void Advance() {
-    DCHECK(!Done());
-    current_ = current_->GetNext();
-  }
-
-  HUseListNode<T>* Current() const {
-    DCHECK(!Done());
-    return current_;
-  }
-
- private:
-  HUseListNode<T>* current_;
-
-  friend class HValue;
-};
+using HUseList = IntrusiveForwardList<HUseListNode<T>>;
 
 // This class is used by HEnvironment and HInstruction classes to record the
 // instructions they use and pointers to the corresponding HUseListNodes kept
@@ -1462,25 +1367,26 @@
 template <typename T>
 class HUserRecord : public ValueObject {
  public:
-  HUserRecord() : instruction_(nullptr), use_node_(nullptr) {}
-  explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), use_node_(nullptr) {}
+  HUserRecord() : instruction_(nullptr), before_use_node_() {}
+  explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), before_use_node_() {}
 
-  HUserRecord(const HUserRecord<T>& old_record, HUseListNode<T>* use_node)
-    : instruction_(old_record.instruction_), use_node_(use_node) {
+  HUserRecord(const HUserRecord<T>& old_record, typename HUseList<T>::iterator before_use_node)
+      : HUserRecord(old_record.instruction_, before_use_node) {}
+  HUserRecord(HInstruction* instruction, typename HUseList<T>::iterator before_use_node)
+      : instruction_(instruction), before_use_node_(before_use_node) {
     DCHECK(instruction_ != nullptr);
-    DCHECK(use_node_ != nullptr);
-    DCHECK(old_record.use_node_ == nullptr);
   }
 
   HInstruction* GetInstruction() const { return instruction_; }
-  HUseListNode<T>* GetUseNode() const { return use_node_; }
+  typename HUseList<T>::iterator GetBeforeUseNode() const { return before_use_node_; }
+  typename HUseList<T>::iterator GetUseNode() const { return ++GetBeforeUseNode(); }
 
  private:
   // Instruction used by the user.
   HInstruction* instruction_;
 
-  // Corresponding entry in the use list kept by 'instruction_'.
-  HUseListNode<T>* use_node_;
+  // Iterator before the corresponding entry in the use list kept by 'instruction_'.
+  typename HUseList<T>::iterator before_use_node_;
 };
 
 /**
@@ -1805,14 +1711,6 @@
   }
 
  private:
-  // Record instructions' use entries of this environment for constant-time removal.
-  // It should only be called by HInstruction when a new environment use is added.
-  void RecordEnvUse(HUseListNode<HEnvironment*>* env_use) {
-    DCHECK(env_use->GetUser() == this);
-    size_t index = env_use->GetIndex();
-    vregs_[index] = HUserRecord<HEnvironment*>(vregs_[index], env_use);
-  }
-
   ArenaVector<HUserRecord<HEnvironment*>> vregs_;
   ArenaVector<Location> locations_;
   HEnvironment* parent_;
@@ -1916,36 +1814,44 @@
   ReferenceTypeInfo GetReferenceTypeInfo() const {
     DCHECK_EQ(GetType(), Primitive::kPrimNot);
     return ReferenceTypeInfo::CreateUnchecked(reference_type_handle_,
-                                              GetPackedFlag<kFlagReferenceTypeIsExact>());;
+                                              GetPackedFlag<kFlagReferenceTypeIsExact>());
   }
 
   void AddUseAt(HInstruction* user, size_t index) {
     DCHECK(user != nullptr);
-    HUseListNode<HInstruction*>* use =
-        uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena());
-    user->SetRawInputRecordAt(index, HUserRecord<HInstruction*>(user->InputRecordAt(index), use));
+    // Note: fixup_end remains valid across push_front().
+    auto fixup_end = uses_.empty() ? uses_.begin() : ++uses_.begin();
+    HUseListNode<HInstruction*>* new_node =
+        new (GetBlock()->GetGraph()->GetArena()) HUseListNode<HInstruction*>(user, index);
+    uses_.push_front(*new_node);
+    FixUpUserRecordsAfterUseInsertion(fixup_end);
   }
 
   void AddEnvUseAt(HEnvironment* user, size_t index) {
     DCHECK(user != nullptr);
-    HUseListNode<HEnvironment*>* env_use =
-        env_uses_.AddUse(user, index, GetBlock()->GetGraph()->GetArena());
-    user->RecordEnvUse(env_use);
+    // Note: env_fixup_end remains valid across push_front().
+    auto env_fixup_end = env_uses_.empty() ? env_uses_.begin() : ++env_uses_.begin();
+    HUseListNode<HEnvironment*>* new_node =
+        new (GetBlock()->GetGraph()->GetArena()) HUseListNode<HEnvironment*>(user, index);
+    env_uses_.push_front(*new_node);
+    FixUpUserRecordsAfterEnvUseInsertion(env_fixup_end);
   }
 
   void RemoveAsUserOfInput(size_t input) {
     HUserRecord<HInstruction*> input_use = InputRecordAt(input);
-    input_use.GetInstruction()->uses_.Remove(input_use.GetUseNode());
+    HUseList<HInstruction*>::iterator before_use_node = input_use.GetBeforeUseNode();
+    input_use.GetInstruction()->uses_.erase_after(before_use_node);
+    input_use.GetInstruction()->FixUpUserRecordsAfterUseRemoval(before_use_node);
   }
 
   const HUseList<HInstruction*>& GetUses() const { return uses_; }
   const HUseList<HEnvironment*>& GetEnvUses() const { return env_uses_; }
 
-  bool HasUses() const { return !uses_.IsEmpty() || !env_uses_.IsEmpty(); }
-  bool HasEnvironmentUses() const { return !env_uses_.IsEmpty(); }
-  bool HasNonEnvironmentUses() const { return !uses_.IsEmpty(); }
+  bool HasUses() const { return !uses_.empty() || !env_uses_.empty(); }
+  bool HasEnvironmentUses() const { return !env_uses_.empty(); }
+  bool HasNonEnvironmentUses() const { return !uses_.empty(); }
   bool HasOnlyOneNonEnvironmentUse() const {
-    return !HasEnvironmentUses() && GetUses().HasOnlyOneUse();
+    return !HasEnvironmentUses() && GetUses().HasExactlyOneElement();
   }
 
   // Does this instruction strictly dominate `other_instruction`?
@@ -2147,7 +2053,45 @@
   }
 
  private:
-  void RemoveEnvironmentUser(HUseListNode<HEnvironment*>* use_node) { env_uses_.Remove(use_node); }
+  void FixUpUserRecordsAfterUseInsertion(HUseList<HInstruction*>::iterator fixup_end) {
+    auto before_use_node = uses_.before_begin();
+    for (auto use_node = uses_.begin(); use_node != fixup_end; ++use_node) {
+      HInstruction* user = use_node->GetUser();
+      size_t input_index = use_node->GetIndex();
+      user->SetRawInputRecordAt(input_index, HUserRecord<HInstruction*>(this, before_use_node));
+      before_use_node = use_node;
+    }
+  }
+
+  void FixUpUserRecordsAfterUseRemoval(HUseList<HInstruction*>::iterator before_use_node) {
+    auto next = ++HUseList<HInstruction*>::iterator(before_use_node);
+    if (next != uses_.end()) {
+      HInstruction* next_user = next->GetUser();
+      size_t next_index = next->GetIndex();
+      DCHECK(next_user->InputRecordAt(next_index).GetInstruction() == this);
+      next_user->SetRawInputRecordAt(next_index, HUserRecord<HInstruction*>(this, before_use_node));
+    }
+  }
+
+  void FixUpUserRecordsAfterEnvUseInsertion(HUseList<HEnvironment*>::iterator env_fixup_end) {
+    auto before_env_use_node = env_uses_.before_begin();
+    for (auto env_use_node = env_uses_.begin(); env_use_node != env_fixup_end; ++env_use_node) {
+      HEnvironment* user = env_use_node->GetUser();
+      size_t input_index = env_use_node->GetIndex();
+      user->vregs_[input_index] = HUserRecord<HEnvironment*>(this, before_env_use_node);
+      before_env_use_node = env_use_node;
+    }
+  }
+
+  void FixUpUserRecordsAfterEnvUseRemoval(HUseList<HEnvironment*>::iterator before_env_use_node) {
+    auto next = ++HUseList<HEnvironment*>::iterator(before_env_use_node);
+    if (next != env_uses_.end()) {
+      HEnvironment* next_user = next->GetUser();
+      size_t next_index = next->GetIndex();
+      DCHECK(next_user->vregs_[next_index].GetInstruction() == this);
+      next_user->vregs_[next_index] = HUserRecord<HEnvironment*>(this, before_env_use_node);
+    }
+  }
 
   HInstruction* previous_;
   HInstruction* next_;
diff --git a/compiler/optimizing/nodes_test.cc b/compiler/optimizing/nodes_test.cc
index 764f5fe..d4e2a58 100644
--- a/compiler/optimizing/nodes_test.cc
+++ b/compiler/optimizing/nodes_test.cc
@@ -91,7 +91,7 @@
   entry->InsertInstructionBefore(to_insert, parameter2);
 
   ASSERT_TRUE(parameter1->HasUses());
-  ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse());
+  ASSERT_TRUE(parameter1->GetUses().HasExactlyOneElement());
 }
 
 /**
@@ -115,7 +115,7 @@
   entry->AddInstruction(to_add);
 
   ASSERT_TRUE(parameter->HasUses());
-  ASSERT_TRUE(parameter->GetUses().HasOnlyOneUse());
+  ASSERT_TRUE(parameter->GetUses().HasExactlyOneElement());
 }
 
 TEST(Node, ParentEnvironment) {
@@ -134,7 +134,7 @@
   entry->AddInstruction(new (&allocator) HExit());
 
   ASSERT_TRUE(parameter1->HasUses());
-  ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse());
+  ASSERT_TRUE(parameter1->GetUses().HasExactlyOneElement());
 
   HEnvironment* environment = new (&allocator) HEnvironment(
       &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, with_environment);
@@ -145,7 +145,7 @@
   with_environment->SetRawEnvironment(environment);
 
   ASSERT_TRUE(parameter1->HasEnvironmentUses());
-  ASSERT_TRUE(parameter1->GetEnvUses().HasOnlyOneUse());
+  ASSERT_TRUE(parameter1->GetEnvUses().HasExactlyOneElement());
 
   HEnvironment* parent1 = new (&allocator) HEnvironment(
       &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr);
diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc
index fc72727..dcc89e8 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -63,8 +63,8 @@
 void PrepareForRegisterAllocation::VisitClinitCheck(HClinitCheck* check) {
   // Try to find a static invoke or a new-instance from which this check originated.
   HInstruction* implicit_clinit = nullptr;
-  for (HUseIterator<HInstruction*> it(check->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* user = it.Current()->GetUser();
+  for (const HUseListNode<HInstruction*>& use : check->GetUses()) {
+    HInstruction* user = use.GetUser();
     if ((user->IsInvokeStaticOrDirect() || user->IsNewInstance()) &&
         CanMoveClinitCheck(check, user)) {
       implicit_clinit = user;
@@ -85,11 +85,12 @@
   // If we found a static invoke or new-instance for merging, remove the check
   // from dominated static invokes.
   if (implicit_clinit != nullptr) {
-    for (HUseIterator<HInstruction*> it(check->GetUses()); !it.Done(); ) {
-      HInstruction* user = it.Current()->GetUser();
+    const HUseList<HInstruction*>& uses = check->GetUses();
+    for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+      HInstruction* user = it->GetUser();
       // All other uses must be dominated.
       DCHECK(implicit_clinit->StrictlyDominates(user) || (implicit_clinit == user));
-      it.Advance();  // Advance before we remove the node, reference to the next node is preserved.
+      ++it;  // Advance before we remove the node, reference to the next node is preserved.
       if (user->IsInvokeStaticOrDirect()) {
         user->AsInvokeStaticOrDirect()->RemoveExplicitClinitCheck(
             HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
@@ -159,7 +160,7 @@
 
 void PrepareForRegisterAllocation::VisitCondition(HCondition* condition) {
   if (condition->HasOnlyOneNonEnvironmentUse()) {
-    HInstruction* user = condition->GetUses().GetFirst()->GetUser();
+    HInstruction* user = condition->GetUses().front().GetUser();
     if (CanEmitConditionAt(condition, user)) {
       condition->MarkEmittedAtUseSite();
     }
diff --git a/compiler/optimizing/pretty_printer.h b/compiler/optimizing/pretty_printer.h
index 429e6e3..ee32518 100644
--- a/compiler/optimizing/pretty_printer.h
+++ b/compiler/optimizing/pretty_printer.h
@@ -55,13 +55,13 @@
     if (instruction->HasUses()) {
       PrintString(" [");
       bool first = true;
-      for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) {
+      for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
         if (first) {
           first = false;
         } else {
           PrintString(", ");
         }
-        PrintInt(it.Current()->GetUser()->GetId());
+        PrintInt(use.GetUser()->GetId());
       }
       PrintString("]");
     }
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index 95f10e0..04c9ff9 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -23,6 +23,17 @@
 
 namespace art {
 
+static inline mirror::DexCache* FindDexCacheWithHint(Thread* self,
+                                                     const DexFile& dex_file,
+                                                     Handle<mirror::DexCache> hint_dex_cache)
+    SHARED_REQUIRES(Locks::mutator_lock_) {
+  if (LIKELY(hint_dex_cache->GetDexFile() == &dex_file)) {
+    return hint_dex_cache.Get();
+  } else {
+    return Runtime::Current()->GetClassLinker()->FindDexCache(self, dex_file);
+  }
+}
+
 static inline ReferenceTypeInfo::TypeHandle GetRootHandle(StackHandleScopeCollection* handles,
                                                           ClassLinker::ClassRoot class_root,
                                                           ReferenceTypeInfo::TypeHandle* cache) {
@@ -54,10 +65,12 @@
 class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor {
  public:
   RTPVisitor(HGraph* graph,
+             Handle<mirror::DexCache> hint_dex_cache,
              HandleCache* handle_cache,
              ArenaVector<HInstruction*>* worklist,
              bool is_first_run)
     : HGraphDelegateVisitor(graph),
+      hint_dex_cache_(hint_dex_cache),
       handle_cache_(handle_cache),
       worklist_(worklist),
       is_first_run_(is_first_run) {}
@@ -70,7 +83,8 @@
   void VisitNewArray(HNewArray* instr) OVERRIDE;
   void VisitParameterValue(HParameterValue* instr) OVERRIDE;
   void UpdateFieldAccessTypeInfo(HInstruction* instr, const FieldInfo& info);
-  void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass, bool is_exact);
+  void SetClassAsTypeInfo(HInstruction* instr, mirror::Class* klass, bool is_exact)
+      SHARED_REQUIRES(Locks::mutator_lock_);
   void VisitInstanceFieldGet(HInstanceFieldGet* instr) OVERRIDE;
   void VisitStaticFieldGet(HStaticFieldGet* instr) OVERRIDE;
   void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* instr) OVERRIDE;
@@ -86,16 +100,19 @@
                                bool is_exact);
 
  private:
+  Handle<mirror::DexCache> hint_dex_cache_;
   HandleCache* handle_cache_;
   ArenaVector<HInstruction*>* worklist_;
   const bool is_first_run_;
 };
 
 ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph,
+                                                   Handle<mirror::DexCache> hint_dex_cache,
                                                    StackHandleScopeCollection* handles,
                                                    bool is_first_run,
                                                    const char* name)
     : HOptimization(graph, name),
+      hint_dex_cache_(hint_dex_cache),
       handle_cache_(handles),
       worklist_(graph->GetArena()->Adapter(kArenaAllocReferenceTypePropagation)),
       is_first_run_(is_first_run) {
@@ -130,7 +147,7 @@
 }
 
 void ReferenceTypePropagation::Visit(HInstruction* instruction) {
-  RTPVisitor visitor(graph_, &handle_cache_, &worklist_, is_first_run_);
+  RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
   instruction->Accept(&visitor);
 }
 
@@ -149,7 +166,7 @@
 }
 
 void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) {
-  RTPVisitor visitor(graph_, &handle_cache_, &worklist_, is_first_run_);
+  RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
   // Handle Phis first as there might be instructions in the same block who depend on them.
   for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
     VisitPhi(it.Current()->AsPhi());
@@ -187,8 +204,8 @@
   if (existing_bound_type->GetUpperBound().IsSupertypeOf(upper_bound)) {
     if (kIsDebugBuild) {
       // Check that the existing HBoundType dominates all the uses.
-      for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) {
-        HInstruction* user = it.Current()->GetUser();
+      for (const HUseListNode<HInstruction*>& use : obj->GetUses()) {
+        HInstruction* user = use.GetUser();
         if (dominator_instr != nullptr) {
           DCHECK(!dominator_instr->StrictlyDominates(user)
               || user == existing_bound_type
@@ -242,8 +259,12 @@
       ? ifInstruction->IfTrueSuccessor()
       : ifInstruction->IfFalseSuccessor();
 
-  for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* user = it.Current()->GetUser();
+  const HUseList<HInstruction*>& uses = obj->GetUses();
+  for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+    HInstruction* user = it->GetUser();
+    size_t index = it->GetIndex();
+    // Increment `it` now because `*it` may disappear thanks to user->ReplaceInput().
+    ++it;
     if (notNullBlock->Dominates(user->GetBlock())) {
       if (bound_type == nullptr) {
         ScopedObjectAccess soa(Thread::Current());
@@ -264,7 +285,7 @@
           break;
         }
       }
-      user->ReplaceInput(bound_type, it.Current()->GetIndex());
+      user->ReplaceInput(bound_type, index);
     }
   }
 }
@@ -358,7 +379,6 @@
   HLoadClass* load_class = instanceOf->InputAt(1)->AsLoadClass();
   ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI();
   {
-    ScopedObjectAccess soa(Thread::Current());
     if (!class_rti.IsValid()) {
       // He have loaded an unresolved class. Don't bother bounding the type.
       return;
@@ -379,8 +399,12 @@
     return;
   }
   DCHECK(!obj->IsLoadClass()) << "We should not replace HLoadClass instructions";
-  for (HUseIterator<HInstruction*> it(obj->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* user = it.Current()->GetUser();
+  const HUseList<HInstruction*>& uses = obj->GetUses();
+  for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+    HInstruction* user = it->GetUser();
+    size_t index = it->GetIndex();
+    // Increment `it` now because `*it` may disappear thanks to user->ReplaceInput().
+    ++it;
     if (instanceOfTrueBlock->Dominates(user->GetBlock())) {
       if (bound_type == nullptr) {
         ScopedObjectAccess soa(Thread::Current());
@@ -396,7 +420,7 @@
           break;
         }
       }
-      user->ReplaceInput(bound_type, it.Current()->GetIndex());
+      user->ReplaceInput(bound_type, index);
     }
   }
 }
@@ -409,10 +433,10 @@
     if (kIsDebugBuild) {
       HInvoke* invoke = instr->AsInvoke();
       ClassLinker* cl = Runtime::Current()->GetClassLinker();
-      ScopedObjectAccess soa(Thread::Current());
-      StackHandleScope<2> hs(soa.Self());
+      Thread* self = Thread::Current();
+      StackHandleScope<2> hs(self);
       Handle<mirror::DexCache> dex_cache(
-          hs.NewHandle(cl->FindDexCache(soa.Self(), invoke->GetDexFile(), false)));
+          hs.NewHandle(FindDexCacheWithHint(self, invoke->GetDexFile(), hint_dex_cache_)));
       // Use a null loader. We should probably use the compiling method's class loader,
       // but then we would need to pass it to RTPVisitor just for this debug check. Since
       // the method is from the String class, the null loader is good enough.
@@ -430,10 +454,14 @@
     instr->SetReferenceTypeInfo(
         ReferenceTypeInfo::Create(handle_cache_->GetStringClassHandle(), /* is_exact */ true));
   } else if (klass != nullptr) {
-    ScopedObjectAccess soa(Thread::Current());
-    ReferenceTypeInfo::TypeHandle handle = handle_cache_->NewHandle(klass);
-    is_exact = is_exact || handle->CannotBeAssignedFromOtherTypes();
-    instr->SetReferenceTypeInfo(ReferenceTypeInfo::Create(handle, is_exact));
+    if (klass->IsErroneous()) {
+      // Set inexact object type for erroneous types.
+      instr->SetReferenceTypeInfo(instr->GetBlock()->GetGraph()->GetInexactObjectRti());
+    } else {
+      ReferenceTypeInfo::TypeHandle handle = handle_cache_->NewHandle(klass);
+      is_exact = is_exact || handle->CannotBeAssignedFromOtherTypes();
+      instr->SetReferenceTypeInfo(ReferenceTypeInfo::Create(handle, is_exact));
+    }
   } else {
     instr->SetReferenceTypeInfo(instr->GetBlock()->GetGraph()->GetInexactObjectRti());
   }
@@ -446,8 +474,7 @@
   DCHECK_EQ(instr->GetType(), Primitive::kPrimNot);
 
   ScopedObjectAccess soa(Thread::Current());
-  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
-      soa.Self(), dex_file, false);
+  mirror::DexCache* dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_);
   // Get type from dex cache assuming it was populated by the verifier.
   SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact);
 }
@@ -460,24 +487,24 @@
   UpdateReferenceTypeInfo(instr, instr->GetTypeIndex(), instr->GetDexFile(), /* is_exact */ true);
 }
 
-static mirror::Class* GetClassFromDexCache(Thread* self, const DexFile& dex_file, uint16_t type_idx)
+static mirror::Class* GetClassFromDexCache(Thread* self,
+                                           const DexFile& dex_file,
+                                           uint16_t type_idx,
+                                           Handle<mirror::DexCache> hint_dex_cache)
     SHARED_REQUIRES(Locks::mutator_lock_) {
-  mirror::DexCache* dex_cache =
-      Runtime::Current()->GetClassLinker()->FindDexCache(self, dex_file, /* allow_failure */ true);
-  if (dex_cache == nullptr) {
-    // Dex cache could not be found. This should only happen during gtests.
-    return nullptr;
-  }
+  mirror::DexCache* dex_cache = FindDexCacheWithHint(self, dex_file, hint_dex_cache);
   // Get type from dex cache assuming it was populated by the verifier.
   return dex_cache->GetResolvedType(type_idx);
 }
 
 void ReferenceTypePropagation::RTPVisitor::VisitParameterValue(HParameterValue* instr) {
-  ScopedObjectAccess soa(Thread::Current());
   // We check if the existing type is valid: the inliner may have set it.
   if (instr->GetType() == Primitive::kPrimNot && !instr->GetReferenceTypeInfo().IsValid()) {
-    mirror::Class* resolved_class =
-        GetClassFromDexCache(soa.Self(), instr->GetDexFile(), instr->GetTypeIndex());
+    ScopedObjectAccess soa(Thread::Current());
+    mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(),
+                                                         instr->GetDexFile(),
+                                                         instr->GetTypeIndex(),
+                                                         hint_dex_cache_);
     SetClassAsTypeInfo(instr, resolved_class, /* is_exact */ false);
   }
 }
@@ -532,9 +559,11 @@
 void ReferenceTypePropagation::RTPVisitor::VisitLoadClass(HLoadClass* instr) {
   ScopedObjectAccess soa(Thread::Current());
   // Get type from dex cache assuming it was populated by the verifier.
-  mirror::Class* resolved_class =
-      GetClassFromDexCache(soa.Self(), instr->GetDexFile(), instr->GetTypeIndex());
-  if (resolved_class != nullptr) {
+  mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(),
+                                                       instr->GetDexFile(),
+                                                       instr->GetTypeIndex(),
+                                                       hint_dex_cache_);
+  if (resolved_class != nullptr && !resolved_class->IsErroneous()) {
     instr->SetLoadedClassRTI(ReferenceTypeInfo::Create(
         handle_cache_->NewHandle(resolved_class), /* is_exact */ true));
   }
@@ -567,7 +596,6 @@
 }
 
 void ReferenceTypePropagation::RTPVisitor::VisitNullCheck(HNullCheck* instr) {
-  ScopedObjectAccess soa(Thread::Current());
   ReferenceTypeInfo parent_rti = instr->InputAt(0)->GetReferenceTypeInfo();
   if (parent_rti.IsValid()) {
     instr->SetReferenceTypeInfo(parent_rti);
@@ -575,10 +603,9 @@
 }
 
 void ReferenceTypePropagation::RTPVisitor::VisitBoundType(HBoundType* instr) {
-  ScopedObjectAccess soa(Thread::Current());
-
   ReferenceTypeInfo class_rti = instr->GetUpperBound();
   if (class_rti.IsValid()) {
+    ScopedObjectAccess soa(Thread::Current());
     // Narrow the type as much as possible.
     HInstruction* obj = instr->InputAt(0);
     ReferenceTypeInfo obj_rti = obj->GetReferenceTypeInfo();
@@ -609,8 +636,6 @@
 }
 
 void ReferenceTypePropagation::RTPVisitor::VisitCheckCast(HCheckCast* check_cast) {
-  ScopedObjectAccess soa(Thread::Current());
-
   HLoadClass* load_class = check_cast->InputAt(1)->AsLoadClass();
   ReferenceTypeInfo class_rti = load_class->GetLoadedClassRTI();
   HBoundType* bound_type = check_cast->GetNext()->AsBoundType();
@@ -645,7 +670,6 @@
       // point the interpreter jumps to that loop header.
       return;
     }
-    ScopedObjectAccess soa(Thread::Current());
     // Set the initial type for the phi. Use the non back edge input for reaching
     // a fixed point faster.
     HInstruction* first_input = phi->InputAt(0);
@@ -718,7 +742,7 @@
   }
 
   Handle<mirror::Class> handle = parent_rti.GetTypeHandle();
-  if (handle->IsObjectArrayClass()) {
+  if (handle->IsObjectArrayClass() && !handle->GetComponentType()->IsErroneous()) {
     ReferenceTypeInfo::TypeHandle component_handle =
         handle_cache->NewHandle(handle->GetComponentType());
     bool is_exact = component_handle->CannotBeAssignedFromOtherTypes();
@@ -760,7 +784,8 @@
 
   ScopedObjectAccess soa(Thread::Current());
   ClassLinker* cl = Runtime::Current()->GetClassLinker();
-  mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), instr->GetDexFile());
+  mirror::DexCache* dex_cache =
+      FindDexCacheWithHint(soa.Self(), instr->GetDexFile(), hint_dex_cache_);
   size_t pointer_size = cl->GetImagePointerSize();
   ArtMethod* method = dex_cache->GetResolvedMethod(instr->GetDexMethodIndex(), pointer_size);
   mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(false, pointer_size);
@@ -887,8 +912,8 @@
 }
 
 void ReferenceTypePropagation::AddDependentInstructionsToWorklist(HInstruction* instruction) {
-  for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* user = it.Current()->GetUser();
+  for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
+    HInstruction* user = use.GetUser();
     if ((user->IsPhi() && user->AsPhi()->IsLive())
        || user->IsBoundType()
        || user->IsNullCheck()
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 028a6fc..2106be6 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -32,6 +32,7 @@
 class ReferenceTypePropagation : public HOptimization {
  public:
   ReferenceTypePropagation(HGraph* graph,
+                           Handle<mirror::DexCache> hint_dex_cache,
                            StackHandleScopeCollection* handles,
                            bool is_first_run,
                            const char* name = kReferenceTypePropagationPassName);
@@ -90,6 +91,10 @@
 
   void ValidateTypes();
 
+  // Note: hint_dex_cache_ is usually, but not necessarily, the dex cache associated with
+  // graph_->GetDexFile(). Since we may look up also in other dex files, it's used only
+  // as a hint, to reduce the number of calls to the costly ClassLinker::FindDexCache().
+  Handle<mirror::DexCache> hint_dex_cache_;
   HandleCache handle_cache_;
 
   ArenaVector<HInstruction*> worklist_;
@@ -99,6 +104,8 @@
 
   static constexpr size_t kDefaultWorklistSize = 8;
 
+  friend class ReferenceTypePropagationTest;
+
   DISALLOW_COPY_AND_ASSIGN(ReferenceTypePropagation);
 };
 
diff --git a/compiler/optimizing/reference_type_propagation_test.cc b/compiler/optimizing/reference_type_propagation_test.cc
new file mode 100644
index 0000000..7649b50
--- /dev/null
+++ b/compiler/optimizing/reference_type_propagation_test.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/arena_allocator.h"
+#include "builder.h"
+#include "nodes.h"
+#include "object_lock.h"
+#include "optimizing_unit_test.h"
+#include "reference_type_propagation.h"
+
+namespace art {
+
+/**
+ * Fixture class for unit testing the ReferenceTypePropagation phase. Used to verify the
+ * functionality of methods and situations that are hard to set up with checker tests.
+ */
+class ReferenceTypePropagationTest : public CommonCompilerTest {
+ public:
+  ReferenceTypePropagationTest() : pool_(), allocator_(&pool_) {
+    graph_ = CreateGraph(&allocator_);
+  }
+
+  ~ReferenceTypePropagationTest() { }
+
+  void SetupPropagation(StackHandleScopeCollection* handles) {
+    graph_->InitializeInexactObjectRTI(handles);
+    propagation_ = new (&allocator_) ReferenceTypePropagation(graph_,
+                                                              Handle<mirror::DexCache>(),
+                                                              handles,
+                                                              true,
+                                                              "test_prop");
+  }
+
+  // Relay method to merge type in reference type propagation.
+  ReferenceTypeInfo MergeTypes(const ReferenceTypeInfo& a,
+                               const ReferenceTypeInfo& b) SHARED_REQUIRES(Locks::mutator_lock_) {
+    return propagation_->MergeTypes(a, b);
+  }
+
+  // Helper method to construct an invalid type.
+  ReferenceTypeInfo InvalidType() {
+    return ReferenceTypeInfo::CreateInvalid();
+  }
+
+  // Helper method to construct the Object type.
+  ReferenceTypeInfo ObjectType(bool is_exact = true) SHARED_REQUIRES(Locks::mutator_lock_) {
+    return ReferenceTypeInfo::Create(propagation_->handle_cache_.GetObjectClassHandle(), is_exact);
+  }
+
+  // Helper method to construct the String type.
+  ReferenceTypeInfo StringType(bool is_exact = true) SHARED_REQUIRES(Locks::mutator_lock_) {
+    return ReferenceTypeInfo::Create(propagation_->handle_cache_.GetStringClassHandle(), is_exact);
+  }
+
+  // General building fields.
+  ArenaPool pool_;
+  ArenaAllocator allocator_;
+  HGraph* graph_;
+
+  ReferenceTypePropagation* propagation_;
+};
+
+//
+// The actual ReferenceTypePropgation unit tests.
+//
+
+TEST_F(ReferenceTypePropagationTest, ProperSetup) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScopeCollection handles(soa.Self());
+  SetupPropagation(&handles);
+
+  EXPECT_TRUE(propagation_ != nullptr);
+  EXPECT_TRUE(graph_->GetInexactObjectRti().IsEqual(ObjectType(false)));
+}
+
+TEST_F(ReferenceTypePropagationTest, MergeInvalidTypes) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScopeCollection handles(soa.Self());
+  SetupPropagation(&handles);
+
+  // Two invalid types.
+  ReferenceTypeInfo t1(MergeTypes(InvalidType(), InvalidType()));
+  EXPECT_FALSE(t1.IsValid());
+  EXPECT_FALSE(t1.IsExact());
+  EXPECT_TRUE(t1.IsEqual(InvalidType()));
+
+  // Valid type on right.
+  ReferenceTypeInfo t2(MergeTypes(InvalidType(), ObjectType()));
+  EXPECT_TRUE(t2.IsValid());
+  EXPECT_TRUE(t2.IsExact());
+  EXPECT_TRUE(t2.IsEqual(ObjectType()));
+  ReferenceTypeInfo t3(MergeTypes(InvalidType(), StringType()));
+  EXPECT_TRUE(t3.IsValid());
+  EXPECT_TRUE(t3.IsExact());
+  EXPECT_TRUE(t3.IsEqual(StringType()));
+
+  // Valid type on left.
+  ReferenceTypeInfo t4(MergeTypes(ObjectType(), InvalidType()));
+  EXPECT_TRUE(t4.IsValid());
+  EXPECT_TRUE(t4.IsExact());
+  EXPECT_TRUE(t4.IsEqual(ObjectType()));
+  ReferenceTypeInfo t5(MergeTypes(StringType(), InvalidType()));
+  EXPECT_TRUE(t5.IsValid());
+  EXPECT_TRUE(t5.IsExact());
+  EXPECT_TRUE(t5.IsEqual(StringType()));
+}
+
+TEST_F(ReferenceTypePropagationTest, MergeValidTypes) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScopeCollection handles(soa.Self());
+  SetupPropagation(&handles);
+
+  // Same types.
+  ReferenceTypeInfo t1(MergeTypes(ObjectType(), ObjectType()));
+  EXPECT_TRUE(t1.IsValid());
+  EXPECT_TRUE(t1.IsExact());
+  EXPECT_TRUE(t1.IsEqual(ObjectType()));
+  ReferenceTypeInfo t2(MergeTypes(StringType(), StringType()));
+  EXPECT_TRUE(t2.IsValid());
+  EXPECT_TRUE(t2.IsExact());
+  EXPECT_TRUE(t2.IsEqual(StringType()));
+
+  // Left is super class of right.
+  ReferenceTypeInfo t3(MergeTypes(ObjectType(), StringType()));
+  EXPECT_TRUE(t3.IsValid());
+  EXPECT_FALSE(t3.IsExact());
+  EXPECT_TRUE(t3.IsEqual(ObjectType(false)));
+
+  // Right is super class of left.
+  ReferenceTypeInfo t4(MergeTypes(StringType(), ObjectType()));
+  EXPECT_TRUE(t4.IsValid());
+  EXPECT_FALSE(t4.IsExact());
+  EXPECT_TRUE(t4.IsEqual(ObjectType(false)));
+
+  // Same types, but one or both are inexact.
+  ReferenceTypeInfo t5(MergeTypes(ObjectType(false), ObjectType()));
+  EXPECT_TRUE(t5.IsValid());
+  EXPECT_FALSE(t5.IsExact());
+  EXPECT_TRUE(t5.IsEqual(ObjectType(false)));
+  ReferenceTypeInfo t6(MergeTypes(ObjectType(), ObjectType(false)));
+  EXPECT_TRUE(t6.IsValid());
+  EXPECT_FALSE(t6.IsExact());
+  EXPECT_TRUE(t6.IsEqual(ObjectType(false)));
+  ReferenceTypeInfo t7(MergeTypes(ObjectType(false), ObjectType(false)));
+  EXPECT_TRUE(t7.IsValid());
+  EXPECT_FALSE(t7.IsExact());
+  EXPECT_TRUE(t7.IsEqual(ObjectType(false)));
+}
+
+}  // namespace art
+
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 2fe2f20..c2aa0c0 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -108,8 +108,8 @@
   // marked dead/conflicting too, so we add them to the worklist. Otherwise we
   // add users whose type does not match and needs to be updated.
   bool add_all_live_phis = instruction->IsPhi() && instruction->AsPhi()->IsDead();
-  for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) {
-    HInstruction* user = it.Current()->GetUser();
+  for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
+    HInstruction* user = use.GetUser();
     if (user->IsPhi() && user->AsPhi()->IsLive()) {
       if (add_all_live_phis || user->GetType() != instruction->GetType()) {
         worklist->push_back(user->AsPhi());
@@ -412,27 +412,24 @@
 }
 
 static bool HasAliasInEnvironments(HInstruction* instruction) {
-  for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses());
-       !use_it.Done();
-       use_it.Advance()) {
-    HEnvironment* use = use_it.Current()->GetUser();
-    HUseListNode<HEnvironment*>* next = use_it.Current()->GetNext();
-    if (next != nullptr && next->GetUser() == use) {
+  HEnvironment* last_user = nullptr;
+  for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
+    DCHECK(use.GetUser() != nullptr);
+    // Note: The first comparison (== null) always fails.
+    if (use.GetUser() == last_user) {
       return true;
     }
+    last_user = use.GetUser();
   }
 
   if (kIsDebugBuild) {
     // Do a quadratic search to ensure same environment uses are next
     // to each other.
-    for (HUseIterator<HEnvironment*> use_it(instruction->GetEnvUses());
-         !use_it.Done();
-         use_it.Advance()) {
-      HUseListNode<HEnvironment*>* current = use_it.Current();
-      HUseListNode<HEnvironment*>* next = current->GetNext();
-      while (next != nullptr) {
+    const HUseList<HEnvironment*>& env_uses = instruction->GetEnvUses();
+    for (auto current = env_uses.begin(), end = env_uses.end(); current != end; ++current) {
+      auto next = current;
+      for (++next; next != end; ++next) {
         DCHECK(next->GetUser() != current->GetUser());
-        next = next->GetNext();
       }
     }
   }
@@ -509,7 +506,7 @@
 
   // 4) Compute type of reference type instructions. The pass assumes that
   // NullConstant has been fixed up.
-  ReferenceTypePropagation(graph_, handles_, /* is_first_run */ true).Run();
+  ReferenceTypePropagation(graph_, dex_cache_, handles_, /* is_first_run */ true).Run();
 
   // 5) HInstructionBuilder duplicated ArrayGet instructions with ambiguous type
   // (int/float or long/double) and marked ArraySets with ambiguous input type.
diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h
index c37c28c..d7360ad 100644
--- a/compiler/optimizing/ssa_builder.h
+++ b/compiler/optimizing/ssa_builder.h
@@ -47,8 +47,11 @@
  */
 class SsaBuilder : public ValueObject {
  public:
-  SsaBuilder(HGraph* graph, StackHandleScopeCollection* handles)
+  SsaBuilder(HGraph* graph,
+             Handle<mirror::DexCache> dex_cache,
+             StackHandleScopeCollection* handles)
       : graph_(graph),
+        dex_cache_(dex_cache),
         handles_(handles),
         agets_fixed_(false),
         ambiguous_agets_(graph->GetArena()->Adapter(kArenaAllocGraphBuilder)),
@@ -112,6 +115,7 @@
   void RemoveRedundantUninitializedStrings();
 
   HGraph* graph_;
+  Handle<mirror::DexCache> dex_cache_;
   StackHandleScopeCollection* const handles_;
 
   // True if types of ambiguous ArrayGets have been resolved.
diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc
index 83e9dac..5534aea 100644
--- a/compiler/optimizing/ssa_liveness_analysis.cc
+++ b/compiler/optimizing/ssa_liveness_analysis.cc
@@ -283,11 +283,9 @@
       if (current->IsEmittedAtUseSite()) {
         if (kIsDebugBuild) {
           DCHECK(!current->GetLocations()->Out().IsValid());
-          for (HUseIterator<HInstruction*> use_it(current->GetUses());
-               !use_it.Done();
-               use_it.Advance()) {
-            HInstruction* user = use_it.Current()->GetUser();
-            size_t index = use_it.Current()->GetIndex();
+          for (const HUseListNode<HInstruction*>& use : current->GetUses()) {
+            HInstruction* user = use.GetUser();
+            size_t index = use.GetIndex();
             DCHECK(!user->GetLocations()->InAt(index).IsValid());
           }
           DCHECK(!current->HasEnvironmentUses());
@@ -318,7 +316,7 @@
         for (uint32_t idx : live_in->Indexes()) {
           HInstruction* instruction = GetInstructionFromSsaIndex(idx);
           DCHECK(instruction->GetBlock()->IsEntryBlock()) << instruction->DebugName();
-          DCHECK(!instruction->IsParameterValue()) << instruction->DebugName();
+          DCHECK(!instruction->IsParameterValue());
           DCHECK(instruction->IsCurrentMethod() || instruction->IsConstant())
               << instruction->DebugName();
         }
diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h
index 719feec..40dab74 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -971,7 +971,7 @@
 
   bool IsLinearOrderWellFormed(const HGraph& graph) {
     for (HBasicBlock* header : graph.GetBlocks()) {
-      if (!header->IsLoopHeader()) {
+      if (header == nullptr || !header->IsLoopHeader()) {
         continue;
       }
 
diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc
index 6816b6a..aeb3109 100644
--- a/compiler/optimizing/ssa_phi_elimination.cc
+++ b/compiler/optimizing/ssa_phi_elimination.cc
@@ -43,8 +43,8 @@
 
       bool keep_alive = (graph_->IsDebuggable() && phi->HasEnvironmentUses());
       if (!keep_alive) {
-        for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); use_it.Advance()) {
-          if (!use_it.Current()->GetUser()->IsPhi()) {
+        for (const HUseListNode<HInstruction*>& use : phi->GetUses()) {
+          if (!use.GetUser()->IsPhi()) {
             keep_alive = true;
             break;
           }
@@ -94,9 +94,8 @@
       if (phi->IsDead()) {
         // Make sure the phi is only used by other dead phis.
         if (kIsDebugBuild) {
-          for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done();
-               use_it.Advance()) {
-            HInstruction* user = use_it.Current()->GetUser();
+          for (const HUseListNode<HInstruction*>& use : phi->GetUses()) {
+            HInstruction* user = use.GetUser();
             DCHECK(user->IsLoopHeaderPhi());
             DCHECK(user->AsPhi()->IsDead());
           }
@@ -106,11 +105,9 @@
           phi->RemoveAsUserOfInput(i);
         }
         // Remove the phi from environments that use it.
-        for (HUseIterator<HEnvironment*> use_it(phi->GetEnvUses()); !use_it.Done();
-             use_it.Advance()) {
-          HUseListNode<HEnvironment*>* user_node = use_it.Current();
-          HEnvironment* user = user_node->GetUser();
-          user->SetRawEnvAt(user_node->GetIndex(), nullptr);
+        for (const HUseListNode<HEnvironment*>& use : phi->GetEnvUses()) {
+          HEnvironment* user = use.GetUser();
+          user->SetRawEnvAt(use.GetIndex(), nullptr);
         }
         // Delete it from the instruction list.
         block->RemovePhi(phi, /*ensure_safety=*/ false);
@@ -233,9 +230,8 @@
 
       // Because we're updating the users of this phi, we may have new candidates
       // for elimination. Add phis that use this phi to the worklist.
-      for (HUseIterator<HInstruction*> it(current->GetUses()); !it.Done(); it.Advance()) {
-        HUseListNode<HInstruction*>* use = it.Current();
-        HInstruction* user = use->GetUser();
+      for (const HUseListNode<HInstruction*>& use : current->GetUses()) {
+        HInstruction* user = use.GetUser();
         if (user->IsPhi() && !ContainsElement(visited_phis_in_cycle, user->GetId())) {
           worklist_.push_back(user->AsPhi());
         }
diff --git a/compiler/optimizing/ssa_test.cc b/compiler/optimizing/ssa_test.cc
index 218bd53..4297634 100644
--- a/compiler/optimizing/ssa_test.cc
+++ b/compiler/optimizing/ssa_test.cc
@@ -346,7 +346,7 @@
     "BasicBlock 7, pred: 6\n"
     "  12: Exit\n"
     "BasicBlock 8, pred: 2, 3, succ: 4\n"
-    "  13: Phi(2, 1) [8, 8, 11]\n"
+    "  13: Phi(2, 1) [11, 8, 8]\n"
     "  14: Goto\n";
 
   const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc
index 26f7d0d..2c73fb8 100644
--- a/compiler/utils/arm/assembler_thumb2.cc
+++ b/compiler/utils/arm/assembler_thumb2.cc
@@ -256,7 +256,10 @@
     for (JumpTable& table : jump_tables_) {
       // Bulk ensure capacity, as this may be large.
       size_t orig_size = buffer_.Size();
-      buffer_.ExtendCapacity(orig_size + table.GetSize());
+      size_t required_capacity = orig_size + table.GetSize();
+      if (required_capacity > buffer_.Capacity()) {
+        buffer_.ExtendCapacity(required_capacity);
+      }
 #ifndef NDEBUG
       buffer_.has_ensured_capacity_ = true;
 #endif
diff --git a/compiler/utils/assembler.cc b/compiler/utils/assembler.cc
index c2aa574..e6c3a18 100644
--- a/compiler/utils/assembler.cc
+++ b/compiler/utils/assembler.cc
@@ -47,7 +47,7 @@
 AssemblerBuffer::AssemblerBuffer(ArenaAllocator* arena)
     : arena_(arena) {
   static const size_t kInitialBufferCapacity = 4 * KB;
-  contents_ = arena_->AllocArray<uint8_t>(kInitialBufferCapacity);
+  contents_ = arena_->AllocArray<uint8_t>(kInitialBufferCapacity, kArenaAllocAssembler);
   cursor_ = contents_;
   limit_ = ComputeLimit(contents_, kInitialBufferCapacity);
   fixup_ = nullptr;
@@ -94,6 +94,7 @@
 void AssemblerBuffer::ExtendCapacity(size_t min_capacity) {
   size_t old_size = Size();
   size_t old_capacity = Capacity();
+  DCHECK_GT(min_capacity, old_capacity);
   size_t new_capacity = std::min(old_capacity * 2, old_capacity + 1 * MB);
   new_capacity = std::max(new_capacity, min_capacity);
 
diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h
index f70fe04..5267dc3 100644
--- a/compiler/utils/assembler.h
+++ b/compiler/utils/assembler.h
@@ -178,8 +178,8 @@
   class EnsureCapacity {
    public:
     explicit EnsureCapacity(AssemblerBuffer* buffer) {
-      if (buffer->cursor() >= buffer->limit()) {
-        buffer->ExtendCapacity();
+      if (buffer->cursor() > buffer->limit()) {
+        buffer->ExtendCapacity(buffer->Size() + kMinimumGap);
       }
       // In debug mode, we save the assembler buffer along with the gap
       // size before we start emitting to the buffer. This allows us to
@@ -219,7 +219,9 @@
   class EnsureCapacity {
    public:
     explicit EnsureCapacity(AssemblerBuffer* buffer) {
-      if (buffer->cursor() >= buffer->limit()) buffer->ExtendCapacity();
+      if (buffer->cursor() > buffer->limit()) {
+        buffer->ExtendCapacity(buffer->Size() + kMinimumGap);
+      }
     }
   };
 
@@ -233,7 +235,14 @@
   // Returns the position in the instruction stream.
   int GetPosition() { return  cursor_ - contents_; }
 
-  void ExtendCapacity(size_t min_capacity = 0u);
+  size_t Capacity() const {
+    CHECK_GE(limit_, contents_);
+    return (limit_ - contents_) + kMinimumGap;
+  }
+
+  // Unconditionally increase the capacity.
+  // The provided `min_capacity` must be higher than current `Capacity()`.
+  void ExtendCapacity(size_t min_capacity);
 
  private:
   // The limit is set to kMinimumGap bytes before the end of the data area.
@@ -255,10 +264,6 @@
 
   uint8_t* cursor() const { return cursor_; }
   uint8_t* limit() const { return limit_; }
-  size_t Capacity() const {
-    CHECK_GE(limit_, contents_);
-    return (limit_ - contents_) + kMinimumGap;
-  }
 
   // Process the fixup chain starting at the given fixup. The offset is
   // non-zero for fixups in the body if the preamble is non-empty.
diff --git a/compiler/utils/intrusive_forward_list.h b/compiler/utils/intrusive_forward_list.h
new file mode 100644
index 0000000..ec2c087
--- /dev/null
+++ b/compiler/utils/intrusive_forward_list.h
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_
+#define ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_
+
+#include <stdint.h>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace art {
+
+struct IntrusiveForwardListHook {
+  IntrusiveForwardListHook() : next_hook(nullptr) { }
+  explicit IntrusiveForwardListHook(const IntrusiveForwardListHook* hook) : next_hook(hook) { }
+
+  // Allow copyable values but do not copy the hook, it is not part of the value.
+  IntrusiveForwardListHook(const IntrusiveForwardListHook& other ATTRIBUTE_UNUSED)
+      : next_hook(nullptr) { }
+  IntrusiveForwardListHook& operator=(const IntrusiveForwardListHook& src ATTRIBUTE_UNUSED) {
+    return *this;
+  }
+
+  mutable const IntrusiveForwardListHook* next_hook;
+};
+
+template <typename T, IntrusiveForwardListHook T::* NextPtr = &T::hook>
+class IntrusiveForwardListMemberHook;
+
+template <typename T, typename HookTraits = IntrusiveForwardListMemberHook<T>>
+class IntrusiveForwardList;
+
+template <typename T, typename HookTraits>
+class IntrusiveForwardListIterator : public std::iterator<std::forward_iterator_tag, T> {
+ public:
+  // Construct/copy/destroy (except the private constructor used by IntrusiveForwardList<>).
+  IntrusiveForwardListIterator() : hook_(nullptr) { }
+  IntrusiveForwardListIterator(const IntrusiveForwardListIterator& src) = default;
+  IntrusiveForwardListIterator& operator=(const IntrusiveForwardListIterator& src) = default;
+
+  // Conversion from iterator to const_iterator.
+  template <typename OtherT,
+            typename = typename std::enable_if<std::is_same<T, const OtherT>::value>::type>
+  IntrusiveForwardListIterator(const IntrusiveForwardListIterator<OtherT, HookTraits>& src)
+      : hook_(src.hook_) { }
+
+  // Iteration.
+  IntrusiveForwardListIterator& operator++() {
+    DCHECK(hook_ != nullptr);
+    hook_ = hook_->next_hook;
+    return *this;
+  }
+  IntrusiveForwardListIterator operator++(int) {
+    IntrusiveForwardListIterator tmp(*this);
+    ++*this;
+    return tmp;
+  }
+
+  // Dereference
+  T& operator*() const {
+    DCHECK(hook_ != nullptr);
+    return *HookTraits::GetValue(hook_);
+  }
+  T* operator->() const {
+    return &**this;
+  }
+
+ private:
+  explicit IntrusiveForwardListIterator(const IntrusiveForwardListHook* hook) : hook_(hook) { }
+
+  const IntrusiveForwardListHook* hook_;
+
+  template <typename OtherT, typename OtherTraits>
+  friend class IntrusiveForwardListIterator;
+
+  template <typename OtherT, typename OtherTraits>
+  friend class IntrusiveForwardList;
+
+  template <typename OtherT1, typename OtherT2, typename OtherTraits>
+  friend typename std::enable_if<std::is_same<const OtherT1, const OtherT2>::value, bool>::type
+  operator==(const IntrusiveForwardListIterator<OtherT1, OtherTraits>& lhs,
+             const IntrusiveForwardListIterator<OtherT2, OtherTraits>& rhs);
+};
+
+template <typename T, typename OtherT, typename HookTraits>
+typename std::enable_if<std::is_same<const T, const OtherT>::value, bool>::type operator==(
+    const IntrusiveForwardListIterator<T, HookTraits>& lhs,
+    const IntrusiveForwardListIterator<OtherT, HookTraits>& rhs) {
+  return lhs.hook_ == rhs.hook_;
+}
+
+template <typename T, typename OtherT, typename HookTraits>
+typename std::enable_if<std::is_same<const T, const OtherT>::value, bool>::type operator!=(
+    const IntrusiveForwardListIterator<T, HookTraits>& lhs,
+    const IntrusiveForwardListIterator<OtherT, HookTraits>& rhs) {
+  return !(lhs == rhs);
+}
+
+// Intrusive version of std::forward_list<>. See also slist<> in Boost.Intrusive.
+//
+// This class template provides the same interface as std::forward_list<> as long
+// as the functions are meaningful for an intrusive container; this excludes emplace
+// functions and functions taking an std::initializer_list<> as the container does
+// not construct elements.
+template <typename T, typename HookTraits>
+class IntrusiveForwardList {
+ public:
+  typedef HookTraits hook_traits;
+  typedef       T  value_type;
+  typedef       T& reference;
+  typedef const T& const_reference;
+  typedef       T* pointer;
+  typedef const T* const_pointer;
+  typedef IntrusiveForwardListIterator<      T, hook_traits> iterator;
+  typedef IntrusiveForwardListIterator<const T, hook_traits> const_iterator;
+
+  // Construct/copy/destroy.
+  IntrusiveForwardList() = default;
+  template <typename InputIterator>
+  IntrusiveForwardList(InputIterator first, InputIterator last) : IntrusiveForwardList() {
+    insert_after(before_begin(), first, last);
+  }
+  IntrusiveForwardList(IntrusiveForwardList&& src) : first_(src.first_.next_hook) {
+    src.first_.next_hook = nullptr;
+  }
+  IntrusiveForwardList& operator=(const IntrusiveForwardList& src) = delete;
+  IntrusiveForwardList& operator=(IntrusiveForwardList&& src) {
+    IntrusiveForwardList tmp(std::move(src));
+    tmp.swap(*this);
+    return *this;
+  }
+  ~IntrusiveForwardList() = default;
+
+  // Iterators.
+  iterator before_begin() { return iterator(&first_); }
+  const_iterator before_begin() const { return const_iterator(&first_); }
+  iterator begin() { return iterator(first_.next_hook); }
+  const_iterator begin() const { return const_iterator(first_.next_hook); }
+  iterator end() { return iterator(nullptr); }
+  const_iterator end() const { return const_iterator(nullptr); }
+  const_iterator cbefore_begin() const { return const_iterator(&first_); }
+  const_iterator cbegin() const { return const_iterator(first_.next_hook); }
+  const_iterator cend() const { return const_iterator(nullptr); }
+
+  // Capacity.
+  bool empty() const { return begin() == end(); }
+  size_t max_size() { return static_cast<size_t>(-1); }
+
+  // Element access.
+  reference front() { return *begin(); }
+  const_reference front() const { return *begin(); }
+
+  // Modifiers.
+  template <typename InputIterator>
+  void assign(InputIterator first, InputIterator last) {
+    IntrusiveForwardList tmp(first, last);
+    tmp.swap(*this);
+  }
+  void push_front(value_type& value) {
+    insert_after(before_begin(), value);
+  }
+  void pop_front() {
+    DCHECK(!empty());
+    erase_after(before_begin());
+  }
+  iterator insert_after(const_iterator position, value_type& value) {
+    const IntrusiveForwardListHook* new_hook = hook_traits::GetHook(&value);
+    new_hook->next_hook = position.hook_->next_hook;
+    position.hook_->next_hook = new_hook;
+    return iterator(new_hook);
+  }
+  template <typename InputIterator>
+  iterator insert_after(const_iterator position, InputIterator first, InputIterator last) {
+    while (first != last) {
+      position = insert_after(position, *first++);
+    }
+    return iterator(position.hook_);
+  }
+  iterator erase_after(const_iterator position) {
+    const_iterator last = position;
+    std::advance(last, 2);
+    return erase_after(position, last);
+  }
+  iterator erase_after(const_iterator position, const_iterator last) {
+    DCHECK(position != last);
+    position.hook_->next_hook = last.hook_;
+    return iterator(last.hook_);
+  }
+  void swap(IntrusiveForwardList& other) {
+    std::swap(first_.next_hook, other.first_.next_hook);
+  }
+  void clear() {
+    first_.next_hook = nullptr;
+  }
+
+  // Operations.
+  void splice_after(const_iterator position, IntrusiveForwardList& src) {
+    DCHECK(position != end());
+    splice_after(position, src, src.before_begin(), src.end());
+  }
+  void splice_after(const_iterator position, IntrusiveForwardList&& src) {
+    splice_after(position, src);  // Use l-value overload.
+  }
+  // Splice the element after `i`.
+  void splice_after(const_iterator position, IntrusiveForwardList& src, const_iterator i) {
+    // The standard specifies that this version does nothing if `position == i`
+    // or `position == ++i`. We must handle the latter here because the overload
+    // `splice_after(position, src, first, last)` does not allow `position` inside
+    // the range `(first, last)`.
+    if (++const_iterator(i) == position) {
+      return;
+    }
+    const_iterator last = i;
+    std::advance(last, 2);
+    splice_after(position, src, i, last);
+  }
+  // Splice the element after `i`.
+  void splice_after(const_iterator position, IntrusiveForwardList&& src, const_iterator i) {
+    splice_after(position, src, i);  // Use l-value overload.
+  }
+  // Splice elements between `first` and `last`, i.e. open range `(first, last)`.
+  void splice_after(const_iterator position,
+                    IntrusiveForwardList& src,
+                    const_iterator first,
+                    const_iterator last) {
+    DCHECK(position != end());
+    DCHECK(first != last);
+    if (++const_iterator(first) == last) {
+      // Nothing to do.
+      return;
+    }
+    // If position is just before end() and last is src.end(), we can finish this quickly.
+    if (++const_iterator(position) == end() && last == src.end()) {
+      position.hook_->next_hook = first.hook_->next_hook;
+      first.hook_->next_hook = nullptr;
+      return;
+    }
+    // Otherwise we need to find the position before last to fix up the hook.
+    const_iterator before_last = first;
+    while (++const_iterator(before_last) != last) {
+      ++before_last;
+    }
+    // Detach (first, last).
+    const IntrusiveForwardListHook* first_taken = first.hook_->next_hook;
+    first.hook_->next_hook = last.hook_;
+    // Attach the sequence to the new position.
+    before_last.hook_->next_hook = position.hook_->next_hook;
+    position.hook_->next_hook = first_taken;
+  }
+  // Splice elements between `first` and `last`, i.e. open range `(first, last)`.
+  void splice_after(const_iterator position,
+                    IntrusiveForwardList&& src,
+                    const_iterator first,
+                    const_iterator last) {
+    splice_after(position, src, first, last);  // Use l-value overload.
+  }
+  void remove(const value_type& value) {
+    remove_if([value](const value_type& v) { return value == v; });
+  }
+  template <typename Predicate>
+  void remove_if(Predicate pred) {
+    iterator prev = before_begin();
+    for (iterator current = begin(); current != end(); ++current) {
+      if (pred(*current)) {
+        erase_after(prev);
+        current = prev;
+      } else {
+        prev = current;
+      }
+    }
+  }
+  void unique() {
+    unique(std::equal_to<value_type>());
+  }
+  template <typename BinaryPredicate>
+  void unique(BinaryPredicate pred) {
+    if (!empty()) {
+      iterator prev = begin();
+      iterator current = prev;
+      ++current;
+      for (; current != end(); ++current) {
+        if (pred(*prev, *current)) {
+          erase_after(prev);
+          current = prev;
+        } else {
+          prev = current;
+        }
+      }
+    }
+  }
+  void merge(IntrusiveForwardList& other) {
+    merge(other, std::less<value_type>());
+  }
+  void merge(IntrusiveForwardList&& other) {
+    merge(other);  // Use l-value overload.
+  }
+  template <typename Compare>
+  void merge(IntrusiveForwardList& other, Compare cmp) {
+    iterator prev = before_begin();
+    iterator current = begin();
+    iterator other_prev = other.before_begin();
+    iterator other_current = other.begin();
+    while (current != end() && other_current != other.end()) {
+      if (cmp(*other_current, *current)) {
+        ++other_current;
+        splice_after(prev, other, other_prev);
+        ++prev;
+      } else {
+        prev = current;
+        ++current;
+      }
+      DCHECK(++const_iterator(prev) == current);
+      DCHECK(++const_iterator(other_prev) == other_current);
+    }
+    splice_after(prev, other);
+  }
+  template <typename Compare>
+  void merge(IntrusiveForwardList&& other, Compare cmp) {
+    merge(other, cmp);  // Use l-value overload.
+  }
+  void sort() {
+    sort(std::less<value_type>());
+  }
+  template <typename Compare>
+  void sort(Compare cmp) {
+    size_t n = std::distance(begin(), end());
+    if (n >= 2u) {
+      const_iterator middle = before_begin();
+      std::advance(middle, n / 2u);
+      IntrusiveForwardList second_half;
+      second_half.splice_after(second_half.before_begin(), *this, middle, end());
+      sort(cmp);
+      second_half.sort(cmp);
+      merge(second_half, cmp);
+    }
+  }
+  void reverse() {
+    IntrusiveForwardList reversed;
+    while (!empty()) {
+      value_type& value = front();
+      erase_after(before_begin());
+      reversed.insert_after(reversed.before_begin(), value);
+    }
+    reversed.swap(*this);
+  }
+
+  // Extensions.
+  bool HasExactlyOneElement() const {
+    return !empty() && ++begin() == end();
+  }
+  size_t SizeSlow() const {
+    return std::distance(begin(), end());
+  }
+  bool ContainsNode(const_reference node) const {
+    for (auto&& n : *this) {
+      if (std::addressof(n) == std::addressof(node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+ private:
+  static IntrusiveForwardListHook* ModifiableHook(const IntrusiveForwardListHook* hook) {
+    return const_cast<IntrusiveForwardListHook*>(hook);
+  }
+
+  IntrusiveForwardListHook first_;
+};
+
+template <typename T, typename HookTraits>
+void swap(IntrusiveForwardList<T, HookTraits>& lhs, IntrusiveForwardList<T, HookTraits>& rhs) {
+  lhs.swap(rhs);
+}
+
+template <typename T, typename HookTraits>
+bool operator==(const IntrusiveForwardList<T, HookTraits>& lhs,
+                const IntrusiveForwardList<T, HookTraits>& rhs) {
+  auto lit = lhs.begin();
+  auto rit = rhs.begin();
+  for (; lit != lhs.end() && rit != rhs.end(); ++lit, ++rit) {
+    if (*lit != *rit) {
+      return false;
+    }
+  }
+  return lit == lhs.end() && rit == rhs.end();
+}
+
+template <typename T, typename HookTraits>
+bool operator!=(const IntrusiveForwardList<T, HookTraits>& lhs,
+                const IntrusiveForwardList<T, HookTraits>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <typename T, typename HookTraits>
+bool operator<(const IntrusiveForwardList<T, HookTraits>& lhs,
+               const IntrusiveForwardList<T, HookTraits>& rhs) {
+  return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+template <typename T, typename HookTraits>
+bool operator>(const IntrusiveForwardList<T, HookTraits>& lhs,
+               const IntrusiveForwardList<T, HookTraits>& rhs) {
+  return rhs < lhs;
+}
+
+template <typename T, typename HookTraits>
+bool operator<=(const IntrusiveForwardList<T, HookTraits>& lhs,
+                const IntrusiveForwardList<T, HookTraits>& rhs) {
+  return !(rhs < lhs);
+}
+
+template <typename T, typename HookTraits>
+bool operator>=(const IntrusiveForwardList<T, HookTraits>& lhs,
+                const IntrusiveForwardList<T, HookTraits>& rhs) {
+  return !(lhs < rhs);
+}
+
+template <typename T, IntrusiveForwardListHook T::* NextPtr>
+class IntrusiveForwardListMemberHook {
+ public:
+  static const IntrusiveForwardListHook* GetHook(const T* value) {
+    return &(value->*NextPtr);
+  }
+
+  static T* GetValue(const IntrusiveForwardListHook* hook) {
+    return reinterpret_cast<T*>(
+        reinterpret_cast<uintptr_t>(hook) - OFFSETOF_MEMBERPTR(T, NextPtr));
+  }
+};
+
+}  // namespace art
+
+#endif  // ART_COMPILER_UTILS_INTRUSIVE_FORWARD_LIST_H_
diff --git a/compiler/utils/intrusive_forward_list_test.cc b/compiler/utils/intrusive_forward_list_test.cc
new file mode 100644
index 0000000..517142e
--- /dev/null
+++ b/compiler/utils/intrusive_forward_list_test.cc
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <forward_list>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "intrusive_forward_list.h"
+
+namespace art {
+
+struct IFLTestValue {
+  // Deliberately not explicit.
+  IFLTestValue(int v) : hook(), value(v) { }  // NOLINT(runtime/explicit)
+
+  IntrusiveForwardListHook hook;
+  int value;
+};
+
+bool operator==(const IFLTestValue& lhs, const IFLTestValue& rhs) {
+  return lhs.value == rhs.value;
+}
+
+bool operator<(const IFLTestValue& lhs, const IFLTestValue& rhs) {
+  return lhs.value < rhs.value;
+}
+
+#define ASSERT_LISTS_EQUAL(expected, value)                                   \
+  do {                                                                        \
+    ASSERT_EQ(expected.empty(), value.empty());                               \
+    ASSERT_EQ(std::distance(expected.begin(), expected.end()),                \
+              std::distance(value.begin(), value.end()));                     \
+    ASSERT_TRUE(std::equal(expected.begin(), expected.end(), value.begin())); \
+  } while (false)
+
+TEST(IntrusiveForwardList, IteratorToConstIterator) {
+  IntrusiveForwardList<IFLTestValue> ifl;
+  IntrusiveForwardList<IFLTestValue>::iterator begin = ifl.begin();
+  IntrusiveForwardList<IFLTestValue>::const_iterator cbegin = ifl.cbegin();
+  IntrusiveForwardList<IFLTestValue>::const_iterator converted_begin = begin;
+  ASSERT_TRUE(converted_begin == cbegin);
+}
+
+TEST(IntrusiveForwardList, IteratorOperators) {
+  IntrusiveForwardList<IFLTestValue> ifl;
+  ASSERT_TRUE(ifl.begin() == ifl.cbegin());
+  ASSERT_FALSE(ifl.begin() != ifl.cbegin());
+  ASSERT_TRUE(ifl.end() == ifl.cend());
+  ASSERT_FALSE(ifl.end() != ifl.cend());
+
+  ASSERT_TRUE(ifl.begin() == ifl.end());  // Empty.
+  ASSERT_FALSE(ifl.begin() != ifl.end());  // Empty.
+
+  IFLTestValue value(1);
+  ifl.insert_after(ifl.cbefore_begin(), value);
+
+  ASSERT_FALSE(ifl.begin() == ifl.end());  // Not empty.
+  ASSERT_TRUE(ifl.begin() != ifl.end());  // Not empty.
+}
+
+TEST(IntrusiveForwardList, ConstructRange) {
+  std::forward_list<int> ref({ 1, 2, 7 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+}
+
+TEST(IntrusiveForwardList, Assign) {
+  std::forward_list<int> ref1({ 2, 8, 5 });
+  std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end());
+  IntrusiveForwardList<IFLTestValue> ifl;
+  ifl.assign(storage1.begin(), storage1.end());
+  ASSERT_LISTS_EQUAL(ref1, ifl);
+  std::forward_list<int> ref2({ 7, 1, 3 });
+  std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end());
+  ifl.assign(storage2.begin(), storage2.end());
+  ASSERT_LISTS_EQUAL(ref2, ifl);
+}
+
+TEST(IntrusiveForwardList, PushPop) {
+  IFLTestValue value3(3);
+  IFLTestValue value7(7);
+  std::forward_list<int> ref;
+  IntrusiveForwardList<IFLTestValue> ifl;
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ref.push_front(3);
+  ifl.push_front(value3);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(3, ifl.front());
+  ref.push_front(7);
+  ifl.push_front(value7);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(7, ifl.front());
+  ref.pop_front();
+  ifl.pop_front();
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(3, ifl.front());
+  ref.pop_front();
+  ifl.pop_front();
+  ASSERT_LISTS_EQUAL(ref, ifl);
+}
+
+TEST(IntrusiveForwardList, InsertAfter1) {
+  IFLTestValue value4(4);
+  IFLTestValue value8(8);
+  IFLTestValue value5(5);
+  IFLTestValue value3(3);
+  std::forward_list<int> ref;
+  IntrusiveForwardList<IFLTestValue> ifl;
+
+  auto ref_it = ref.insert_after(ref.before_begin(), 4);
+  auto ifl_it = ifl.insert_after(ifl.before_begin(), value4);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+  CHECK(ref_it == ref.begin());
+  ASSERT_TRUE(ifl_it == ifl.begin());
+
+  ref_it = ref.insert_after(ref.begin(), 8);
+  ifl_it = ifl.insert_after(ifl.begin(), value8);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+  CHECK(ref_it != ref.end());
+  ASSERT_TRUE(ifl_it != ifl.end());
+  CHECK(++ref_it == ref.end());
+  ASSERT_TRUE(++ifl_it == ifl.end());
+
+  ref_it = ref.insert_after(ref.begin(), 5);
+  ifl_it = ifl.insert_after(ifl.begin(), value5);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+
+  ref_it = ref.insert_after(ref_it, 3);
+  ifl_it = ifl.insert_after(ifl_it, value3);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+}
+
+TEST(IntrusiveForwardList, InsertAfter2) {
+  std::forward_list<int> ref;
+  IntrusiveForwardList<IFLTestValue> ifl;
+
+  auto ref_it = ref.insert_after(ref.before_begin(), { 2, 8, 5 });
+  std::vector<IFLTestValue> storage1({ { 2 }, { 8 }, { 5 } });
+  auto ifl_it = ifl.insert_after(ifl.before_begin(), storage1.begin(), storage1.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+
+  std::vector<IFLTestValue> storage2({ { 7 }, { 2 } });
+  ref_it = ref.insert_after(ref.begin(), { 7, 2 });
+  ifl_it = ifl.insert_after(ifl.begin(), storage2.begin(), storage2.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(*ref_it, *ifl_it);
+
+  std::vector<IFLTestValue> storage3({ { 1 }, { 3 }, { 4 }, { 9 } });
+  ref_it = ref.begin();
+  ifl_it = ifl.begin();
+  std::advance(ref_it, std::distance(ref.begin(), ref.end()) - 1);
+  std::advance(ifl_it, std::distance(ifl.begin(), ifl.end()) - 1);
+  ref_it = ref.insert_after(ref_it, { 1, 3, 4, 9 });
+  ifl_it = ifl.insert_after(ifl_it, storage3.begin(), storage3.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+}
+
+TEST(IntrusiveForwardList, EraseAfter1) {
+  std::forward_list<int> ref({ 1, 2, 7, 4, 5 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 5);
+
+  auto ref_it = ref.begin();
+  auto ifl_it = ifl.begin();
+  std::advance(ref_it, 2);
+  std::advance(ifl_it, 2);
+  ref_it = ref.erase_after(ref_it);
+  ifl_it = ifl.erase_after(ifl_it);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 4);
+  CHECK(ref_it != ref.end());
+  ASSERT_TRUE(ifl_it != ifl.end());
+  CHECK(++ref_it == ref.end());
+  ASSERT_TRUE(++ifl_it == ifl.end());
+
+  ref_it = ref.begin();
+  ifl_it = ifl.begin();
+  std::advance(ref_it, 2);
+  std::advance(ifl_it, 2);
+  ref_it = ref.erase_after(ref_it);
+  ifl_it = ifl.erase_after(ifl_it);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 3);
+  CHECK(ref_it == ref.end());
+  ASSERT_TRUE(ifl_it == ifl.end());
+
+  ref_it = ref.erase_after(ref.begin());
+  ifl_it = ifl.erase_after(ifl.begin());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 2);
+  CHECK(ref_it != ref.end());
+  ASSERT_TRUE(ifl_it != ifl.end());
+  CHECK(++ref_it == ref.end());
+  ASSERT_TRUE(++ifl_it == ifl.end());
+
+  ref_it = ref.erase_after(ref.before_begin());
+  ifl_it = ifl.erase_after(ifl.before_begin());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 1);
+  CHECK(ref_it == ref.begin());
+  ASSERT_TRUE(ifl_it == ifl.begin());
+
+  ref_it = ref.erase_after(ref.before_begin());
+  ifl_it = ifl.erase_after(ifl.before_begin());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 0);
+  CHECK(ref_it == ref.begin());
+  ASSERT_TRUE(ifl_it == ifl.begin());
+}
+
+TEST(IntrusiveForwardList, EraseAfter2) {
+  std::forward_list<int> ref({ 1, 2, 7, 4, 5, 3, 2, 8, 9 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 9);
+
+  auto ref_it = ref.begin();
+  auto ifl_it = ifl.begin();
+  std::advance(ref_it, 3);
+  std::advance(ifl_it, 3);
+  ref_it = ref.erase_after(ref.begin(), ref_it);
+  ifl_it = ifl.erase_after(ifl.begin(), ifl_it);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ASSERT_EQ(std::distance(ref.begin(), ref_it), std::distance(ifl.begin(), ifl_it));
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 7);
+
+  ref_it = ref.erase_after(ref_it, ref.end());
+  ifl_it = ifl.erase_after(ifl_it, ifl.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK(ref_it == ref.end());
+  ASSERT_TRUE(ifl_it == ifl.end());
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 2);
+
+  ref_it = ref.erase_after(ref.before_begin(), ref.end());
+  ifl_it = ifl.erase_after(ifl.before_begin(), ifl.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK(ref_it == ref.end());
+  ASSERT_TRUE(ifl_it == ifl.end());
+  CHECK_EQ(std::distance(ref.begin(), ref.end()), 0);
+}
+
+TEST(IntrusiveForwardList, SwapClear) {
+  std::forward_list<int> ref1({ 1, 2, 7 });
+  std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end());
+  IntrusiveForwardList<IFLTestValue> ifl1(storage1.begin(), storage1.end());
+  std::forward_list<int> ref2({ 3, 8, 6 });
+  std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end());
+  IntrusiveForwardList<IFLTestValue> ifl2(storage2.begin(), storage2.end());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  ref1.swap(ref2);
+  ifl1.swap(ifl2);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  ref1.clear();
+  ifl1.clear();
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  swap(ref1, ref2);
+  swap(ifl1, ifl2);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  ref1.clear();
+  ifl1.clear();
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+}
+
+TEST(IntrusiveForwardList, SpliceAfter) {
+  std::forward_list<int> ref1({ 3, 1, 2, 7, 4, 5, 4, 8, 7 });
+  std::forward_list<int> ref2;
+  std::vector<IFLTestValue> storage(ref1.begin(), ref1.end());
+  IntrusiveForwardList<IFLTestValue> ifl1(storage.begin(), storage.end());
+  IntrusiveForwardList<IFLTestValue> ifl2;
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move everything to ref2/ifl2.
+  ref2.splice_after(ref2.before_begin(), ref1);
+  ifl2.splice_after(ifl2.before_begin(), ifl1);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move first element (3) to ref1/ifl1.
+  ref1.splice_after(ref1.before_begin(), ref2, ref2.before_begin());
+  ifl1.splice_after(ifl1.before_begin(), ifl2, ifl2.before_begin());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move second element (2) to ref1/ifl1 after the first element (3).
+  ref1.splice_after(ref1.begin(), ref2, ref2.begin());
+  ifl1.splice_after(ifl1.begin(), ifl2, ifl2.begin());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move everything from ref2/ifl2 between the 2 elements now in ref1/ifl1.
+  ref1.splice_after(ref1.begin(), ref2);
+  ifl1.splice_after(ifl1.begin(), ifl2);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  std::forward_list<int> check({ 3, 1, 7, 4, 5, 4, 8, 7, 2 });
+  ASSERT_LISTS_EQUAL(check, ifl1);
+  ASSERT_TRUE(ifl2.empty());
+
+  // Empty splice_after().
+  ref2.splice_after(
+      ref2.before_begin(), ref1, ref1.before_begin(), ref1.begin());
+  ifl2.splice_after(ifl2.before_begin(), ifl1, ifl1.before_begin(), ifl1.begin());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move { 1, 7 } to ref2/ifl2.
+  auto ref_it = ref1.begin();
+  auto ifl_it = ifl1.begin();
+  std::advance(ref_it, 3);
+  std::advance(ifl_it, 3);
+  ref2.splice_after(ref2.before_begin(), ref1, ref1.begin(), ref_it);
+  ifl2.splice_after(ifl2.before_begin(), ifl1, ifl1.begin(), ifl_it);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  // Move { 8, 7, 2 } to the beginning of ref1/ifl1.
+  ref_it = ref1.begin();
+  ifl_it = ifl1.begin();
+  std::advance(ref_it, 3);
+  std::advance(ifl_it, 3);
+  ref1.splice_after(ref1.before_begin(), ref1, ref_it, ref1.end());
+  ifl1.splice_after(ifl1.before_begin(), ifl1, ifl_it, ifl1.end());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+
+  check.assign({ 8, 7, 2, 3, 4, 5, 4 });
+  ASSERT_LISTS_EQUAL(check, ifl1);
+  check.assign({ 1, 7 });
+  ASSERT_LISTS_EQUAL(check, ifl2);
+
+  // Move all but the first element to ref2/ifl2.
+  ref_it = ref2.begin();
+  ifl_it = ifl2.begin();
+  std::advance(ref_it, 1);
+  std::advance(ifl_it, 1);
+  ref2.splice_after(ref_it, ref1, ref1.begin(), ref1.end());
+  ifl2.splice_after(ifl_it, ifl1, ifl1.begin(), ifl1.end());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+
+  check.assign({8});
+  ASSERT_LISTS_EQUAL(check, ifl1);
+
+  // Move the first element of ref1/ifl1 to the beginning of ref1/ifl1 (do nothing).
+  ref1.splice_after(ref1.before_begin(), ref1, ref1.before_begin());
+  ifl1.splice_after(ifl1.before_begin(), ifl1, ifl1.before_begin());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(check, ifl1);
+
+  // Move the first element of ref2/ifl2 after itself (do nothing).
+  ref1.splice_after(ref1.begin(), ref1, ref1.before_begin());
+  ifl1.splice_after(ifl1.begin(), ifl1, ifl1.before_begin());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(check, ifl1);
+
+  check.assign({ 1, 7, 7, 2, 3, 4, 5, 4 });
+  ASSERT_LISTS_EQUAL(check, ifl2);
+
+  // Move the first element of ref2/ifl2 to the beginning of ref2/ifl2 (do nothing).
+  ref2.splice_after(ref2.before_begin(), ref2, ref2.before_begin());
+  ifl2.splice_after(ifl2.before_begin(), ifl2, ifl2.before_begin());
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  ASSERT_LISTS_EQUAL(check, ifl2);
+
+  // Move the first element of ref2/ifl2 after itself (do nothing).
+  ref2.splice_after(ref2.begin(), ref2, ref2.before_begin());
+  ifl2.splice_after(ifl2.begin(), ifl2, ifl2.before_begin());
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  ASSERT_LISTS_EQUAL(check, ifl2);
+}
+
+TEST(IntrusiveForwardList, Remove) {
+  std::forward_list<int> ref({ 3, 1, 2, 7, 4, 5, 4, 8, 7 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ref.remove(1);
+  ifl.remove(1);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ref.remove(4);
+  ifl.remove(4);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  auto odd = [](IFLTestValue value) { return (value.value & 1) != 0; };  // NOLINT(readability/braces)
+  ref.remove_if(odd);
+  ifl.remove_if(odd);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  auto all = [](IFLTestValue value ATTRIBUTE_UNUSED) { return true; };  // NOLINT(readability/braces)
+  ref.remove_if(all);
+  ifl.remove_if(all);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+}
+
+TEST(IntrusiveForwardList, Unique) {
+  std::forward_list<int> ref({ 3, 1, 1, 2, 3, 3, 7, 7, 4, 4, 5, 7 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  ref.unique();
+  ifl.unique();
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  std::forward_list<int> check({ 3, 1, 2, 3, 7, 4, 5, 7 });
+  ASSERT_LISTS_EQUAL(check, ifl);
+
+  auto bin_pred = [](IFLTestValue lhs, IFLTestValue rhs) {
+    return (lhs.value & ~1) == (rhs.value & ~1);
+  };
+  ref.unique(bin_pred);
+  ifl.unique(bin_pred);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  check.assign({ 3, 1, 2, 7, 4, 7 });
+  ASSERT_LISTS_EQUAL(check, ifl);
+}
+
+TEST(IntrusiveForwardList, Merge) {
+  std::forward_list<int> ref1({ 1, 4, 8, 8, 12 });
+  std::vector<IFLTestValue> storage1(ref1.begin(), ref1.end());
+  IntrusiveForwardList<IFLTestValue> ifl1(storage1.begin(), storage1.end());
+  std::forward_list<int> ref2({ 3, 5, 6, 7, 9 });
+  std::vector<IFLTestValue> storage2(ref2.begin(), ref2.end());
+  IntrusiveForwardList<IFLTestValue> ifl2(storage2.begin(), storage2.end());
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  CHECK(std::is_sorted(ref1.begin(), ref1.end()));
+  CHECK(std::is_sorted(ref2.begin(), ref2.end()));
+  ref1.merge(ref2);
+  ifl1.merge(ifl2);
+  ASSERT_LISTS_EQUAL(ref1, ifl1);
+  ASSERT_LISTS_EQUAL(ref2, ifl2);
+  CHECK(ref2.empty());
+  std::forward_list<int> check({ 1, 3, 4, 5, 6, 7, 8, 8, 9, 12 });
+  ASSERT_LISTS_EQUAL(check, ifl1);
+}
+
+TEST(IntrusiveForwardList, Sort1) {
+  std::forward_list<int> ref({ 2, 9, 8, 3, 7, 4, 1, 5, 3, 0 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK(!std::is_sorted(ref.begin(), ref.end()));
+  ref.sort();
+  ifl.sort();
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  std::forward_list<int> check({ 0, 1, 2, 3, 3, 4, 5, 7, 8, 9 });
+  ASSERT_LISTS_EQUAL(check, ifl);
+}
+
+TEST(IntrusiveForwardList, Sort2) {
+  std::forward_list<int> ref({ 2, 9, 8, 3, 7, 4, 1, 5, 3, 0 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  auto cmp = [](IFLTestValue lhs, IFLTestValue rhs) {
+    return (lhs.value & ~1) < (rhs.value & ~1);
+  };
+  CHECK(!std::is_sorted(ref.begin(), ref.end(), cmp));
+  ref.sort(cmp);
+  ifl.sort(cmp);
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  std::forward_list<int> check({ 1, 0, 2, 3, 3, 4, 5, 7, 9, 8 });
+  ASSERT_LISTS_EQUAL(check, ifl);
+}
+
+TEST(IntrusiveForwardList, Reverse) {
+  std::forward_list<int> ref({ 8, 3, 5, 4, 1, 3 });
+  std::vector<IFLTestValue> storage(ref.begin(), ref.end());
+  IntrusiveForwardList<IFLTestValue> ifl(storage.begin(), storage.end());
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  CHECK(!std::is_sorted(ref.begin(), ref.end()));
+  ref.reverse();
+  ifl.reverse();
+  ASSERT_LISTS_EQUAL(ref, ifl);
+  std::forward_list<int> check({ 3, 1, 4, 5, 3, 8 });
+  ASSERT_LISTS_EQUAL(check, ifl);
+}
+
+}  // namespace art
diff --git a/runtime/arch/x86/thread_x86.cc b/runtime/arch/x86/thread_x86.cc
index 3d19f06..c39d122 100644
--- a/runtime/arch/x86/thread_x86.cc
+++ b/runtime/arch/x86/thread_x86.cc
@@ -45,16 +45,17 @@
   MutexLock mu(nullptr, *Locks::modify_ldt_lock_);
 
   const uintptr_t base = reinterpret_cast<uintptr_t>(this);
-  const size_t limit = kPageSize;
+  const size_t limit = sizeof(Thread);
 
   const int contents = MODIFY_LDT_CONTENTS_DATA;
   const int seg_32bit = 1;
   const int read_exec_only = 0;
-  const int limit_in_pages = 0;
+  const int limit_in_pages = 1;
   const int seg_not_present = 0;
   const int useable = 1;
 
-  int entry_number = -1;
+  int entry_number;
+  uint16_t table_indicator;
 
 #if defined(__APPLE__)
   descriptor_table_entry_t entry;
@@ -77,41 +78,52 @@
   if (entry_number == -1) {
     PLOG(FATAL) << "i386_set_ldt failed";
   }
+
+  table_indicator = 1 << 2;  // LDT
 #else
-  // Read current LDT entries.
-  static_assert(static_cast<size_t>(LDT_ENTRY_SIZE) == sizeof(uint64_t),
-                "LDT_ENTRY_SIZE is different from sizeof(uint64_t).");
-  std::vector<uint64_t> ldt(LDT_ENTRIES);
-  size_t ldt_size(sizeof(uint64_t) * ldt.size());
-  memset(&ldt[0], 0, ldt_size);
-  // TODO: why doesn't this return LDT_ENTRY_SIZE * LDT_ENTRIES for the main thread?
-  syscall(__NR_modify_ldt, 0, &ldt[0], ldt_size);
+  // We use a GDT entry on Linux.
+  user_desc gdt_entry;
+  memset(&gdt_entry, 0, sizeof(gdt_entry));
 
-  // Find the first empty slot.
-  for (entry_number = 0; entry_number < LDT_ENTRIES && ldt[entry_number] != 0; ++entry_number) {
-  }
-  if (entry_number >= LDT_ENTRIES) {
-    LOG(FATAL) << "Failed to find a free LDT slot";
-  }
+  // On Linux, there are 3 TLS GDT entries. We use one of those to to store our segment descriptor
+  // data.
+  //
+  // This entry must be shared, as the kernel only guarantees three TLS entries. For simplicity
+  // (and locality), use this local global, which practically becomes readonly after the first
+  // (startup) thread of the runtime has been initialized (during Runtime::Start()).
+  //
+  // We also share this between all runtimes in the process. This is both for simplicity (one
+  // well-known slot) as well as to avoid the three-slot limitation. Downside is that we cannot
+  // free the slot when it is known that a runtime stops.
+  static unsigned int gdt_entry_number = -1;
 
-  // Update LDT entry.
-  user_desc ldt_entry;
-  memset(&ldt_entry, 0, sizeof(ldt_entry));
-  ldt_entry.entry_number = entry_number;
-  ldt_entry.base_addr = base;
-  ldt_entry.limit = limit;
-  ldt_entry.seg_32bit = seg_32bit;
-  ldt_entry.contents = contents;
-  ldt_entry.read_exec_only = read_exec_only;
-  ldt_entry.limit_in_pages = limit_in_pages;
-  ldt_entry.seg_not_present = seg_not_present;
-  ldt_entry.useable = useable;
-  CHECK_EQ(0, syscall(__NR_modify_ldt, 1, &ldt_entry, sizeof(ldt_entry)));
-  entry_number = ldt_entry.entry_number;
+  if (gdt_entry_number == static_cast<unsigned int>(-1)) {
+    gdt_entry.entry_number = -1;  // Let the kernel choose.
+  } else {
+    gdt_entry.entry_number = gdt_entry_number;
+  }
+  gdt_entry.base_addr = base;
+  gdt_entry.limit = limit;
+  gdt_entry.seg_32bit = seg_32bit;
+  gdt_entry.contents = contents;
+  gdt_entry.read_exec_only = read_exec_only;
+  gdt_entry.limit_in_pages = limit_in_pages;
+  gdt_entry.seg_not_present = seg_not_present;
+  gdt_entry.useable = useable;
+  int rc = syscall(__NR_set_thread_area, &gdt_entry);
+  if (rc != -1) {
+    entry_number = gdt_entry.entry_number;
+    if (gdt_entry_number == static_cast<unsigned int>(-1)) {
+      gdt_entry_number = entry_number;  // Save the kernel-assigned entry number.
+    }
+  } else {
+    PLOG(FATAL) << "set_thread_area failed";
+    UNREACHABLE();
+  }
+  table_indicator = 0;  // GDT
 #endif
 
-  // Change %fs to be new LDT entry.
-  uint16_t table_indicator = 1 << 2;  // LDT
+  // Change %fs to be new DT entry.
   uint16_t rpl = 3;  // Requested privilege level
   uint16_t selector = (entry_number << 3) | table_indicator | rpl;
   __asm__ __volatile__("movw %w0, %%fs"
@@ -163,13 +175,18 @@
   UNUSED(selector);
   // i386_set_ldt(selector >> 3, 0, 1);
 #else
-  user_desc ldt_entry;
-  memset(&ldt_entry, 0, sizeof(ldt_entry));
-  ldt_entry.entry_number = selector >> 3;
-  ldt_entry.contents = MODIFY_LDT_CONTENTS_DATA;
-  ldt_entry.seg_not_present = 1;
-
-  syscall(__NR_modify_ldt, 1, &ldt_entry, sizeof(ldt_entry));
+  // Note if we wanted to clean up the GDT entry, we would do that here, when the *last* thread
+  // is being deleted. But see the comment on gdt_entry_number. Code would look like this:
+  //
+  // user_desc gdt_entry;
+  // memset(&gdt_entry, 0, sizeof(gdt_entry));
+  // gdt_entry.entry_number = selector >> 3;
+  // gdt_entry.contents = MODIFY_LDT_CONTENTS_DATA;
+  // // "Empty" = Delete = seg_not_present==1 && read_exec_only==1.
+  // gdt_entry.seg_not_present = 1;
+  // gdt_entry.read_exec_only = 1;
+  // syscall(__NR_set_thread_area, &gdt_entry);
+  UNUSED(selector);
 #endif
 }
 
diff --git a/runtime/base/macros.h b/runtime/base/macros.h
index dc692d2..7a293c7 100644
--- a/runtime/base/macros.h
+++ b/runtime/base/macros.h
@@ -138,10 +138,10 @@
 #define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f)
 
 #define OFFSETOF_MEMBER(t, f) \
-  (reinterpret_cast<const char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<const char*>(16)) // NOLINT
+  (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT
 
-#define OFFSETOF_VOLATILE_MEMBER(t, f) \
-  (reinterpret_cast<volatile char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<volatile char*>(16)) // NOLINT
+#define OFFSETOF_MEMBERPTR(t, f) \
+  (reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - static_cast<uintptr_t>(16)) // NOLINT
 
 #define PACKED(x) __attribute__ ((__aligned__(x), __packed__))
 
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 17e0339..3dca12a 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -76,7 +76,6 @@
   kReferenceQueueClearedReferencesLock,
   kReferenceProcessorLock,
   kJitDebugInterfaceLock,
-  kJitCodeCacheLock,
   kAllocSpaceLock,
   kBumpPointerSpaceBlockLock,
   kArenaPoolLock,
@@ -89,6 +88,7 @@
   kTracingUniqueMethodsLock,
   kTracingStreamingLock,
   kDeoptimizedMethodsLock,
+  kJitCodeCacheLock,
   kClassLoaderClassesLock,
   kDefaultMutexLevel,
   kMarkSweepLargeObjectLock,
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index d832552..55f68d3 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2362,6 +2362,10 @@
 }
 
 void Dbg::SuspendVM() {
+  // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335.
+  gc::ScopedGCCriticalSection gcs(Thread::Current(),
+                                  gc::kGcCauseDebugger,
+                                  gc::kCollectorTypeDebugger);
   Runtime::Current()->GetThreadList()->SuspendAllForDebugger();
 }
 
@@ -4101,6 +4105,8 @@
   // Suspend other threads if the invoke is not single-threaded.
   if ((pReq->options & JDWP::INVOKE_SINGLE_THREADED) == 0) {
     ScopedThreadSuspension sts(soa.Self(), kWaitingForDebuggerSuspension);
+    // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335.
+    gc::ScopedGCCriticalSection gcs(soa.Self(), gc::kGcCauseDebugger, gc::kCollectorTypeDebugger);
     VLOG(jdwp) << "      Suspending all threads";
     Runtime::Current()->GetThreadList()->SuspendAllForDebugger();
   }
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index 5c5abeb..9f073a6 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -147,6 +147,10 @@
 }
 
 bool FaultManager::HandleFaultByOtherHandlers(int sig, siginfo_t* info, void* context) {
+  if (other_handlers_.empty()) {
+    return false;
+  }
+
   Thread* self = Thread::Current();
 
   DCHECK(self != nullptr);
diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h
index ae41226..4ffc8af 100644
--- a/runtime/gc/collector_type.h
+++ b/runtime/gc/collector_type.h
@@ -44,6 +44,8 @@
   kCollectorTypeInstrumentation,
   // Fake collector for adding or removing application image spaces.
   kCollectorTypeAddRemoveAppImageSpace,
+  // Fake collector used to implement exclusion between GC and debugger.
+  kCollectorTypeDebugger,
   // A homogeneous space compaction collector used in background transition
   // when both foreground and background collector are CMS.
   kCollectorTypeHomogeneousSpaceCompact,
diff --git a/runtime/gc/gc_cause.cc b/runtime/gc/gc_cause.cc
index 679432b..18e5703 100644
--- a/runtime/gc/gc_cause.cc
+++ b/runtime/gc/gc_cause.cc
@@ -35,6 +35,7 @@
     case kGcCauseTrim: return "HeapTrim";
     case kGcCauseInstrumentation: return "Instrumentation";
     case kGcCauseAddRemoveAppImageSpace: return "AddRemoveAppImageSpace";
+    case kGcCauseDebugger: return "Debugger";
     default:
       LOG(FATAL) << "Unreachable";
       UNREACHABLE();
diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h
index c6b505c..ad67eb7 100644
--- a/runtime/gc/gc_cause.h
+++ b/runtime/gc/gc_cause.h
@@ -43,6 +43,8 @@
   kGcCauseInstrumentation,
   // Not a real GC cause, used to add or remove app image spaces.
   kGcCauseAddRemoveAppImageSpace,
+  // Not a real GC cause, used to implement exclusion between GC and debugger.
+  kGcCauseDebugger,
   // GC triggered for background transition when both foreground and background collector are CMS.
   kGcCauseHomogeneousSpaceCompact,
 };
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index a432782..97dbe5d 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -292,7 +292,7 @@
 
         // Pop the shadow frame before calling into compiled code.
         self->PopShadowFrame();
-        ArtInterpreterToCompiledCodeBridge(self, code_item, &shadow_frame, &result);
+        ArtInterpreterToCompiledCodeBridge(self, nullptr, code_item, &shadow_frame, &result);
         // Push the shadow frame back as the caller will expect it.
         self->PushShadowFrame(&shadow_frame);
 
@@ -535,6 +535,10 @@
     return JValue();
   }
 
+  jit::Jit* jit = Runtime::Current()->GetJit();
+  if (jit != nullptr) {
+    jit->NotifyCompiledCodeToInterpreterTransition(self, shadow_frame->GetMethod());
+  }
   return Execute(self, code_item, *shadow_frame, JValue());
 }
 
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 3453abc..12d70c5 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -503,6 +503,7 @@
                                 uint32_t vregC) ALWAYS_INLINE;
 
 void ArtInterpreterToCompiledCodeBridge(Thread* self,
+                                        ArtMethod* caller,
                                         const DexFile::CodeItem* code_item,
                                         ShadowFrame* shadow_frame,
                                         JValue* result)
@@ -530,6 +531,10 @@
   uint16_t arg_offset = (code_item == nullptr)
                             ? 0
                             : code_item->registers_size_ - code_item->ins_size_;
+  jit::Jit* jit = Runtime::Current()->GetJit();
+  if (jit != nullptr && caller != nullptr) {
+    jit->NotifyInterpreterToCompiledCodeTransition(self, caller);
+  }
   method->Invoke(self, shadow_frame->GetVRegArgs(arg_offset),
                  (shadow_frame->NumberOfVRegs() - arg_offset) * sizeof(uint32_t),
                  result, method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty());
@@ -726,7 +731,8 @@
         target->GetEntryPointFromQuickCompiledCode())) {
       ArtInterpreterToInterpreterBridge(self, code_item, new_shadow_frame, result);
     } else {
-      ArtInterpreterToCompiledCodeBridge(self, code_item, new_shadow_frame, result);
+      ArtInterpreterToCompiledCodeBridge(
+          self, shadow_frame.GetMethod(), code_item, new_shadow_frame, result);
     }
   } else {
     UnstartedRuntime::Invoke(self, code_item, new_shadow_frame, result, first_dest_reg);
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index fb98175..e5b89e2 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -635,7 +635,7 @@
         jit->InvokeVirtualOrInterface(
             self, receiver, sf_method, shadow_frame.GetDexPC(), called_method);
       }
-      jit->AddSamples(self, sf_method, 1);
+      jit->AddSamples(self, sf_method, 1, /*with_backedges*/false);
     }
     // TODO: Remove the InvokeVirtualOrInterface instrumentation, as it was only used by the JIT.
     if (type == kVirtual || type == kInterface) {
@@ -681,7 +681,7 @@
     if (jit != nullptr) {
       jit->InvokeVirtualOrInterface(
           self, receiver, shadow_frame.GetMethod(), shadow_frame.GetDexPC(), called_method);
-      jit->AddSamples(self, shadow_frame.GetMethod(), 1);
+      jit->AddSamples(self, shadow_frame.GetMethod(), 1, /*with_backedges*/false);
     }
     instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
     // TODO: Remove the InvokeVirtualOrInterface instrumentation, as it was only used by the JIT.
@@ -1001,8 +1001,11 @@
   return branch_offset <= 0;
 }
 
-void ArtInterpreterToCompiledCodeBridge(Thread* self, const DexFile::CodeItem* code_item,
-                                        ShadowFrame* shadow_frame, JValue* result);
+void ArtInterpreterToCompiledCodeBridge(Thread* self,
+                                        ArtMethod* caller,
+                                        const DexFile::CodeItem* code_item,
+                                        ShadowFrame* shadow_frame,
+                                        JValue* result);
 
 // Explicitly instantiate all DoInvoke functions.
 #define EXPLICIT_DO_INVOKE_TEMPLATE_DECL(_type, _is_range, _do_check)                      \
diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc
index c95af6f..13cfb98 100644
--- a/runtime/interpreter/interpreter_goto_table_impl.cc
+++ b/runtime/interpreter/interpreter_goto_table_impl.cc
@@ -78,7 +78,7 @@
 #define HOTNESS_UPDATE()                                                                       \
   do {                                                                                         \
     if (jit != nullptr) {                                                                      \
-      jit->AddSamples(self, method, 1);                                                        \
+      jit->AddSamples(self, method, 1, /*with_backedges*/ true);                               \
     }                                                                                          \
   } while (false)
 
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index ca1d635..4323d4f 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -89,7 +89,7 @@
 #define HOTNESS_UPDATE()                                                                       \
   do {                                                                                         \
     if (jit != nullptr) {                                                                      \
-      jit->AddSamples(self, method, 1);                                                        \
+      jit->AddSamples(self, method, 1, /*with_backedges*/ true);                               \
     }                                                                                          \
   } while (false)
 
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index e005589..bd1af04 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -689,7 +689,7 @@
   jit::Jit* jit = Runtime::Current()->GetJit();
   if (jit != nullptr) {
     int16_t count = shadow_frame->GetCachedHotnessCountdown() - shadow_frame->GetHotnessCountdown();
-    jit->AddSamples(self, method, count);
+    jit->AddSamples(self, method, count, /*with_backedges*/ true);
   }
   return MterpSetUpHotnessCountdown(method, shadow_frame);
 }
@@ -702,7 +702,7 @@
   uint32_t dex_pc = shadow_frame->GetDexPC();
   jit::Jit* jit = Runtime::Current()->GetJit();
   if ((jit != nullptr) && (offset <= 0)) {
-    jit->AddSamples(self, method, 1);
+    jit->AddSamples(self, method, 1, /*with_backedges*/ true);
   }
   int16_t countdown_value = MterpSetUpHotnessCountdown(method, shadow_frame);
   if (countdown_value == jit::kJitCheckForOSR) {
@@ -722,7 +722,7 @@
   jit::Jit* jit = Runtime::Current()->GetJit();
   if (offset <= 0) {
     // Keep updating hotness in case a compilation request was dropped.  Eventually it will retry.
-    jit->AddSamples(self, method, 1);
+    jit->AddSamples(self, method, 1, /*with_backedges*/ true);
   }
   // Assumes caller has already determined that an OSR check is appropriate.
   return jit::Jit::MaybeDoOnStackReplacement(self, method, dex_pc, offset, result);
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index fe388cd..1f473e4 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -16,11 +16,13 @@
 
 #include "unstarted_runtime.h"
 
+#include <ctype.h>
 #include <errno.h>
 #include <stdlib.h>
 
 #include <cmath>
 #include <limits>
+#include <locale>
 #include <unordered_map>
 
 #include "ScopedLocalRef.h"
@@ -42,6 +44,7 @@
 #include "mirror/object_array-inl.h"
 #include "mirror/string-inl.h"
 #include "nth_caller_visitor.h"
+#include "reflection.h"
 #include "thread.h"
 #include "transaction.h"
 #include "well_known_classes.h"
@@ -70,6 +73,43 @@
   }
 }
 
+// Restricted support for character upper case / lower case. Only support ASCII, where
+// it's easy. Abort the transaction otherwise.
+static void CharacterLowerUpper(Thread* self,
+                                ShadowFrame* shadow_frame,
+                                JValue* result,
+                                size_t arg_offset,
+                                bool to_lower_case) SHARED_REQUIRES(Locks::mutator_lock_) {
+  uint32_t int_value = static_cast<uint32_t>(shadow_frame->GetVReg(arg_offset));
+
+  // Only ASCII (7-bit).
+  if (!isascii(int_value)) {
+    AbortTransactionOrFail(self,
+                           "Only support ASCII characters for toLowerCase/toUpperCase: %u",
+                           int_value);
+    return;
+  }
+
+  std::locale c_locale("C");
+  char char_value = static_cast<char>(int_value);
+
+  if (to_lower_case) {
+    result->SetI(std::tolower(char_value, c_locale));
+  } else {
+    result->SetI(std::toupper(char_value, c_locale));
+  }
+}
+
+void UnstartedRuntime::UnstartedCharacterToLowerCase(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  CharacterLowerUpper(self, shadow_frame, result, arg_offset, true);
+}
+
+void UnstartedRuntime::UnstartedCharacterToUpperCase(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  CharacterLowerUpper(self, shadow_frame, result, arg_offset, false);
+}
+
 // Helper function to deal with class loading in an unstarted runtime.
 static void UnstartedRuntimeFindClass(Thread* self, Handle<mirror::String> className,
                                       Handle<mirror::ClassLoader> class_loader, JValue* result,
@@ -313,6 +353,171 @@
   result->SetL(klass->GetDexFile().GetEnclosingClass(klass));
 }
 
+void UnstartedRuntime::UnstartedClassGetInnerClassFlags(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  StackHandleScope<1> hs(self);
+  Handle<mirror::Class> klass(hs.NewHandle(
+      reinterpret_cast<mirror::Class*>(shadow_frame->GetVRegReference(arg_offset))));
+  const int32_t default_value = shadow_frame->GetVReg(arg_offset + 1);
+  result->SetI(mirror::Class::GetInnerClassFlags(klass, default_value));
+}
+
+static std::unique_ptr<MemMap> FindAndExtractEntry(const std::string& jar_file,
+                                                   const char* entry_name,
+                                                   size_t* size,
+                                                   std::string* error_msg) {
+  CHECK(size != nullptr);
+
+  std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(jar_file.c_str(), error_msg));
+  if (zip_archive == nullptr) {
+    return nullptr;;
+  }
+  std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(entry_name, error_msg));
+  if (zip_entry == nullptr) {
+    return nullptr;
+  }
+  std::unique_ptr<MemMap> tmp_map(
+      zip_entry->ExtractToMemMap(jar_file.c_str(), entry_name, error_msg));
+  if (tmp_map == nullptr) {
+    return nullptr;
+  }
+
+  // OK, from here everything seems fine.
+  *size = zip_entry->GetUncompressedLength();
+  return tmp_map;
+}
+
+static void GetResourceAsStream(Thread* self,
+                                ShadowFrame* shadow_frame,
+                                JValue* result,
+                                size_t arg_offset) SHARED_REQUIRES(Locks::mutator_lock_) {
+  mirror::Object* resource_obj = shadow_frame->GetVRegReference(arg_offset + 1);
+  if (resource_obj == nullptr) {
+    AbortTransactionOrFail(self, "null name for getResourceAsStream");
+    return;
+  }
+  CHECK(resource_obj->IsString());
+  mirror::String* resource_name = resource_obj->AsString();
+
+  std::string resource_name_str = resource_name->ToModifiedUtf8();
+  if (resource_name_str.empty() || resource_name_str == "/") {
+    AbortTransactionOrFail(self,
+                           "Unsupported name %s for getResourceAsStream",
+                           resource_name_str.c_str());
+    return;
+  }
+  const char* resource_cstr = resource_name_str.c_str();
+  if (resource_cstr[0] == '/') {
+    resource_cstr++;
+  }
+
+  Runtime* runtime = Runtime::Current();
+
+  std::vector<std::string> split;
+  Split(runtime->GetBootClassPathString(), ':', &split);
+  if (split.empty()) {
+    AbortTransactionOrFail(self,
+                           "Boot classpath not set or split error:: %s",
+                           runtime->GetBootClassPathString().c_str());
+    return;
+  }
+
+  std::unique_ptr<MemMap> mem_map;
+  size_t map_size;
+  std::string last_error_msg;  // Only store the last message (we could concatenate).
+
+  for (const std::string& jar_file : split) {
+    mem_map = FindAndExtractEntry(jar_file, resource_cstr, &map_size, &last_error_msg);
+    if (mem_map != nullptr) {
+      break;
+    }
+  }
+
+  if (mem_map == nullptr) {
+    // Didn't find it. There's a good chance this will be the same at runtime, but still
+    // conservatively abort the transaction here.
+    AbortTransactionOrFail(self,
+                           "Could not find resource %s. Last error was %s.",
+                           resource_name_str.c_str(),
+                           last_error_msg.c_str());
+    return;
+  }
+
+  StackHandleScope<3> hs(self);
+
+  // Create byte array for content.
+  Handle<mirror::ByteArray> h_array(hs.NewHandle(mirror::ByteArray::Alloc(self, map_size)));
+  if (h_array.Get() == nullptr) {
+    AbortTransactionOrFail(self, "Could not find/create byte array class");
+    return;
+  }
+  // Copy in content.
+  memcpy(h_array->GetData(), mem_map->Begin(), map_size);
+  // Be proactive releasing memory.
+  mem_map.release();
+
+  // Create a ByteArrayInputStream.
+  Handle<mirror::Class> h_class(hs.NewHandle(
+      runtime->GetClassLinker()->FindClass(self,
+                                           "Ljava/io/ByteArrayInputStream;",
+                                           ScopedNullHandle<mirror::ClassLoader>())));
+  if (h_class.Get() == nullptr) {
+    AbortTransactionOrFail(self, "Could not find ByteArrayInputStream class");
+    return;
+  }
+  if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
+    AbortTransactionOrFail(self, "Could not initialize ByteArrayInputStream class");
+    return;
+  }
+
+  Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self)));
+  if (h_obj.Get() == nullptr) {
+    AbortTransactionOrFail(self, "Could not allocate ByteArrayInputStream object");
+    return;
+  }
+
+  auto* cl = Runtime::Current()->GetClassLinker();
+  ArtMethod* constructor = h_class->FindDeclaredDirectMethod(
+      "<init>", "([B)V", cl->GetImagePointerSize());
+  if (constructor == nullptr) {
+    AbortTransactionOrFail(self, "Could not find ByteArrayInputStream constructor");
+    return;
+  }
+
+  uint32_t args[1];
+  args[0] = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_array.Get()));
+  EnterInterpreterFromInvoke(self, constructor, h_obj.Get(), args, nullptr);
+
+  if (self->IsExceptionPending()) {
+    AbortTransactionOrFail(self, "Could not run ByteArrayInputStream constructor");
+    return;
+  }
+
+  result->SetL(h_obj.Get());
+}
+
+void UnstartedRuntime::UnstartedClassLoaderGetResourceAsStream(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  {
+    mirror::Object* this_obj = shadow_frame->GetVRegReference(arg_offset);
+    CHECK(this_obj != nullptr);
+    CHECK(this_obj->IsClassLoader());
+
+    StackHandleScope<1> hs(self);
+    Handle<mirror::Class> this_classloader_class(hs.NewHandle(this_obj->GetClass()));
+
+    if (self->DecodeJObject(WellKnownClasses::java_lang_BootClassLoader) !=
+            this_classloader_class.Get()) {
+      AbortTransactionOrFail(self,
+                            "Unsupported classloader type %s for getResourceAsStream",
+                            PrettyClass(this_classloader_class.Get()).c_str());
+      return;
+    }
+  }
+
+  GetResourceAsStream(self, shadow_frame, result, arg_offset);
+}
+
 void UnstartedRuntime::UnstartedVmClassLoaderFindLoadedClass(
     Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
   mirror::String* class_name = shadow_frame->GetVRegReference(arg_offset + 1)->AsString();
@@ -629,6 +834,22 @@
   result->SetD(floor(shadow_frame->GetVRegDouble(arg_offset)));
 }
 
+void UnstartedRuntime::UnstartedMathSin(
+    Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  result->SetD(sin(shadow_frame->GetVRegDouble(arg_offset)));
+}
+
+void UnstartedRuntime::UnstartedMathCos(
+    Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  result->SetD(cos(shadow_frame->GetVRegDouble(arg_offset)));
+}
+
+void UnstartedRuntime::UnstartedMathPow(
+    Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  result->SetD(pow(shadow_frame->GetVRegDouble(arg_offset),
+                   shadow_frame->GetVRegDouble(arg_offset + 2)));
+}
+
 void UnstartedRuntime::UnstartedObjectHashCode(
     Thread* self ATTRIBUTE_UNUSED, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
   mirror::Object* obj = shadow_frame->GetVRegReference(arg_offset);
@@ -805,112 +1026,6 @@
   UnstartedMemoryPeekArray(Primitive::kPrimByte, self, shadow_frame, arg_offset);
 }
 
-// This allows reading security.properties in an unstarted runtime and initialize Security.
-void UnstartedRuntime::UnstartedSecurityGetSecurityPropertiesReader(
-    Thread* self, ShadowFrame* shadow_frame ATTRIBUTE_UNUSED, JValue* result,
-    size_t arg_offset ATTRIBUTE_UNUSED) {
-  Runtime* runtime = Runtime::Current();
-
-  std::vector<std::string> split;
-  Split(runtime->GetBootClassPathString(), ':', &split);
-  if (split.empty()) {
-    AbortTransactionOrFail(self,
-                           "Boot classpath not set or split error:: %s",
-                           runtime->GetBootClassPathString().c_str());
-    return;
-  }
-  const std::string& source = split[0];
-
-  mirror::String* string_data;
-
-  // Use a block to enclose the I/O and MemMap code so buffers are released early.
-  {
-    std::string error_msg;
-    std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(source.c_str(), &error_msg));
-    if (zip_archive.get() == nullptr) {
-      AbortTransactionOrFail(self,
-                             "Could not open zip file %s: %s",
-                             source.c_str(),
-                             error_msg.c_str());
-      return;
-    }
-    std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find("java/security/security.properties",
-                                                          &error_msg));
-    if (zip_entry.get() == nullptr) {
-      AbortTransactionOrFail(self,
-                             "Could not find security.properties file in %s: %s",
-                             source.c_str(),
-                             error_msg.c_str());
-      return;
-    }
-    std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(source.c_str(),
-                                                           "java/security/security.properties",
-                                                           &error_msg));
-    if (map.get() == nullptr) {
-      AbortTransactionOrFail(self,
-                             "Could not unzip security.properties file in %s: %s",
-                             source.c_str(),
-                             error_msg.c_str());
-      return;
-    }
-
-    uint32_t length = zip_entry->GetUncompressedLength();
-    std::unique_ptr<char[]> tmp(new char[length + 1]);
-    memcpy(tmp.get(), map->Begin(), length);
-    tmp.get()[length] = 0;  // null terminator
-
-    string_data = mirror::String::AllocFromModifiedUtf8(self, tmp.get());
-  }
-
-  if (string_data == nullptr) {
-    AbortTransactionOrFail(self, "Could not create string from file content of %s", source.c_str());
-    return;
-  }
-
-  // Create a StringReader.
-  StackHandleScope<3> hs(self);
-  Handle<mirror::String> h_string(hs.NewHandle(string_data));
-
-  Handle<mirror::Class> h_class(hs.NewHandle(
-      runtime->GetClassLinker()->FindClass(self,
-                                           "Ljava/io/StringReader;",
-                                           ScopedNullHandle<mirror::ClassLoader>())));
-  if (h_class.Get() == nullptr) {
-    AbortTransactionOrFail(self, "Could not find StringReader class");
-    return;
-  }
-
-  if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
-    AbortTransactionOrFail(self, "Could not initialize StringReader class");
-    return;
-  }
-
-  Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self)));
-  if (h_obj.Get() == nullptr) {
-    AbortTransactionOrFail(self, "Could not allocate StringReader object");
-    return;
-  }
-
-  auto* cl = Runtime::Current()->GetClassLinker();
-  ArtMethod* constructor = h_class->FindDeclaredDirectMethod(
-      "<init>", "(Ljava/lang/String;)V", cl->GetImagePointerSize());
-  if (constructor == nullptr) {
-    AbortTransactionOrFail(self, "Could not find StringReader constructor");
-    return;
-  }
-
-  uint32_t args[1];
-  args[0] = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_string.Get()));
-  EnterInterpreterFromInvoke(self, constructor, h_obj.Get(), args, nullptr);
-
-  if (self->IsExceptionPending()) {
-    AbortTransactionOrFail(self, "Could not run StringReader constructor");
-    return;
-  }
-
-  result->SetL(h_obj.Get());
-}
-
 // This allows reading the new style of String objects during compilation.
 void UnstartedRuntime::UnstartedStringGetCharsNoCheck(
     Thread* self, ShadowFrame* shadow_frame, JValue* result ATTRIBUTE_UNUSED, size_t arg_offset) {
@@ -1268,6 +1383,36 @@
   result->SetJ(l);
 }
 
+void UnstartedRuntime::UnstartedMethodInvoke(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset)
+    SHARED_REQUIRES(Locks::mutator_lock_) {
+  JNIEnvExt* env = self->GetJniEnv();
+  ScopedObjectAccessUnchecked soa(self);
+
+  mirror::Object* java_method_obj = shadow_frame->GetVRegReference(arg_offset);
+  ScopedLocalRef<jobject> java_method(env,
+      java_method_obj == nullptr ? nullptr :env->AddLocalReference<jobject>(java_method_obj));
+
+  mirror::Object* java_receiver_obj = shadow_frame->GetVRegReference(arg_offset + 1);
+  ScopedLocalRef<jobject> java_receiver(env,
+      java_receiver_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_receiver_obj));
+
+  mirror::Object* java_args_obj = shadow_frame->GetVRegReference(arg_offset + 2);
+  ScopedLocalRef<jobject> java_args(env,
+      java_args_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_args_obj));
+
+  ScopedLocalRef<jobject> result_jobj(env,
+      InvokeMethod(soa, java_method.get(), java_receiver.get(), java_args.get()));
+
+  result->SetL(self->DecodeJObject(result_jobj.get()));
+
+  // Conservatively flag all exceptions as transaction aborts. This way we don't need to unwrap
+  // InvocationTargetExceptions.
+  if (self->IsExceptionPending()) {
+    AbortTransactionOrFail(self, "Failed Method.invoke");
+  }
+}
+
 
 void UnstartedRuntime::UnstartedJNIVMRuntimeNewUnpaddedArray(
     Thread* self, ArtMethod* method ATTRIBUTE_UNUSED, mirror::Object* receiver ATTRIBUTE_UNUSED,
@@ -1551,7 +1696,13 @@
   if (iter != invoke_handlers_.end()) {
     // Clear out the result in case it's not zeroed out.
     result->SetL(0);
+
+    // Push the shadow frame. This is so the failing method can be seen in abort dumps.
+    self->PushShadowFrame(shadow_frame);
+
     (*iter->second)(self, shadow_frame, result, arg_offset);
+
+    self->PopShadowFrame();
   } else {
     // Not special, continue with regular interpreter execution.
     ArtInterpreterToInterpreterBridge(self, code_item, shadow_frame, result);
diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h
index 3b5bee82..b8553b5 100644
--- a/runtime/interpreter/unstarted_runtime_list.h
+++ b/runtime/interpreter/unstarted_runtime_list.h
@@ -19,6 +19,8 @@
 
 // Methods that intercept available libcore implementations.
 #define UNSTARTED_RUNTIME_DIRECT_LIST(V)    \
+  V(CharacterToLowerCase, "int java.lang.Character.toLowerCase(int)") \
+  V(CharacterToUpperCase, "int java.lang.Character.toUpperCase(int)") \
   V(ClassForName, "java.lang.Class java.lang.Class.forName(java.lang.String)") \
   V(ClassForNameLong, "java.lang.Class java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)") \
   V(ClassClassForName, "java.lang.Class java.lang.Class.classForName(java.lang.String, boolean, java.lang.ClassLoader)") \
@@ -27,6 +29,8 @@
   V(ClassGetDeclaredMethod, "java.lang.reflect.Method java.lang.Class.getDeclaredMethodInternal(java.lang.String, java.lang.Class[])") \
   V(ClassGetDeclaredConstructor, "java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructorInternal(java.lang.Class[])") \
   V(ClassGetEnclosingClass, "java.lang.Class java.lang.Class.getEnclosingClass()") \
+  V(ClassGetInnerClassFlags, "int java.lang.Class.getInnerClassFlags(int)") \
+  V(ClassLoaderGetResourceAsStream, "java.io.InputStream java.lang.ClassLoader.getResourceAsStream(java.lang.String)") \
   V(VmClassLoaderFindLoadedClass, "java.lang.Class java.lang.VMClassLoader.findLoadedClass(java.lang.ClassLoader, java.lang.String)") \
   V(VoidLookupType, "java.lang.Class java.lang.Void.lookupType()") \
   V(SystemArraycopy, "void java.lang.System.arraycopy(java.lang.Object, int, java.lang.Object, int, int)") \
@@ -39,6 +43,9 @@
   V(ThreadLocalGet, "java.lang.Object java.lang.ThreadLocal.get()") \
   V(MathCeil, "double java.lang.Math.ceil(double)") \
   V(MathFloor, "double java.lang.Math.floor(double)") \
+  V(MathSin, "double java.lang.Math.sin(double)") \
+  V(MathCos, "double java.lang.Math.cos(double)") \
+  V(MathPow, "double java.lang.Math.pow(double, double)") \
   V(ObjectHashCode, "int java.lang.Object.hashCode()") \
   V(DoubleDoubleToRawLongBits, "long java.lang.Double.doubleToRawLongBits(double)") \
   V(DexCacheGetDexNative, "com.android.dex.Dex java.lang.DexCache.getDexNative()") \
@@ -47,9 +54,9 @@
   V(MemoryPeekInt, "int libcore.io.Memory.peekIntNative(long)") \
   V(MemoryPeekLong, "long libcore.io.Memory.peekLongNative(long)") \
   V(MemoryPeekByteArray, "void libcore.io.Memory.peekByteArray(long, byte[], int, int)") \
+  V(MethodInvoke, "java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])") \
   V(ReferenceGetReferent, "java.lang.Object java.lang.ref.Reference.getReferent()") \
   V(RuntimeAvailableProcessors, "int java.lang.Runtime.availableProcessors()") \
-  V(SecurityGetSecurityPropertiesReader, "java.io.Reader java.security.Security.getSecurityPropertiesReader()") \
   V(StringGetCharsNoCheck, "void java.lang.String.getCharsNoCheck(int, int, char[], int)") \
   V(StringCharAt, "char java.lang.String.charAt(int)") \
   V(StringSetCharAt, "void java.lang.String.setCharAt(int, char)") \
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index 1b5b665..b26635c 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -17,6 +17,7 @@
 #include "unstarted_runtime.h"
 
 #include <limits>
+#include <locale>
 
 #include "base/casts.h"
 #include "class_linker.h"
@@ -30,6 +31,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change.h"
 #include "thread.h"
+#include "transaction.h"
 
 namespace art {
 namespace interpreter {
@@ -182,6 +184,16 @@
       EXPECT_EQ(expect_int64t, result_int64t) << result.GetD() << " vs " << test_pairs[i][1];
     }
   }
+
+  // Prepare for aborts. Aborts assume that the exception class is already resolved, as the
+  // loading code doesn't work under transactions.
+  void PrepareForAborts() SHARED_REQUIRES(Locks::mutator_lock_) {
+    mirror::Object* result = Runtime::Current()->GetClassLinker()->FindClass(
+        Thread::Current(),
+        Transaction::kAbortExceptionSignature,
+        ScopedNullHandle<mirror::ClassLoader>());
+    CHECK(result != nullptr);
+  }
 };
 
 TEST_F(UnstartedRuntimeTest, MemoryPeekByte) {
@@ -689,5 +701,166 @@
   ShadowFrame::DeleteDeoptimizedFrame(tmp);
 }
 
+TEST_F(UnstartedRuntimeTest, ToLowerUpper) {
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+
+  ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+  std::locale c_locale("C");
+
+  // Check ASCII.
+  for (uint32_t i = 0; i < 128; ++i) {
+    bool c_upper = std::isupper(static_cast<char>(i), c_locale);
+    bool c_lower = std::islower(static_cast<char>(i), c_locale);
+    EXPECT_FALSE(c_upper && c_lower) << i;
+
+    // Check toLowerCase.
+    {
+      JValue result;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+      ASSERT_FALSE(self->IsExceptionPending());
+      uint32_t lower_result = static_cast<uint32_t>(result.GetI());
+      if (c_lower) {
+        EXPECT_EQ(i, lower_result);
+      } else if (c_upper) {
+        EXPECT_EQ(static_cast<uint32_t>(std::tolower(static_cast<char>(i), c_locale)),
+                  lower_result);
+      } else {
+        EXPECT_EQ(i, lower_result);
+      }
+    }
+
+    // Check toUpperCase.
+    {
+      JValue result2;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      UnstartedCharacterToUpperCase(self, tmp, &result2, 0);
+      ASSERT_FALSE(self->IsExceptionPending());
+      uint32_t upper_result = static_cast<uint32_t>(result2.GetI());
+      if (c_upper) {
+        EXPECT_EQ(i, upper_result);
+      } else if (c_lower) {
+        EXPECT_EQ(static_cast<uint32_t>(std::toupper(static_cast<char>(i), c_locale)),
+                  upper_result);
+      } else {
+        EXPECT_EQ(i, upper_result);
+      }
+    }
+  }
+
+  // Check abort for other things. Can't test all.
+
+  PrepareForAborts();
+
+  for (uint32_t i = 128; i < 256; ++i) {
+    {
+      JValue result;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      Transaction transaction;
+      Runtime::Current()->EnterTransactionMode(&transaction);
+      UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+      Runtime::Current()->ExitTransactionMode();
+      ASSERT_TRUE(self->IsExceptionPending());
+      ASSERT_TRUE(transaction.IsAborted());
+    }
+    {
+      JValue result;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      Transaction transaction;
+      Runtime::Current()->EnterTransactionMode(&transaction);
+      UnstartedCharacterToUpperCase(self, tmp, &result, 0);
+      Runtime::Current()->ExitTransactionMode();
+      ASSERT_TRUE(self->IsExceptionPending());
+      ASSERT_TRUE(transaction.IsAborted());
+    }
+  }
+  for (uint64_t i = 256; i <= std::numeric_limits<uint32_t>::max(); i <<= 1) {
+    {
+      JValue result;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      Transaction transaction;
+      Runtime::Current()->EnterTransactionMode(&transaction);
+      UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+      Runtime::Current()->ExitTransactionMode();
+      ASSERT_TRUE(self->IsExceptionPending());
+      ASSERT_TRUE(transaction.IsAborted());
+    }
+    {
+      JValue result;
+      tmp->SetVReg(0, static_cast<int32_t>(i));
+      Transaction transaction;
+      Runtime::Current()->EnterTransactionMode(&transaction);
+      UnstartedCharacterToUpperCase(self, tmp, &result, 0);
+      Runtime::Current()->ExitTransactionMode();
+      ASSERT_TRUE(self->IsExceptionPending());
+      ASSERT_TRUE(transaction.IsAborted());
+    }
+  }
+
+  ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
+TEST_F(UnstartedRuntimeTest, Sin) {
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+
+  ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+  // Test an important value, PI/6. That's the one we see in practice.
+  constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365);
+  tmp->SetVRegLong(0, static_cast<int64_t>(lvalue));
+
+  JValue result;
+  UnstartedMathSin(self, tmp, &result, 0);
+
+  const uint64_t lresult = static_cast<uint64_t>(result.GetJ());
+  EXPECT_EQ(UINT64_C(0x3fdfffffffffffff), lresult);
+
+  ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
+TEST_F(UnstartedRuntimeTest, Cos) {
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+
+  ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+  // Test an important value, PI/6. That's the one we see in practice.
+  constexpr uint64_t lvalue = UINT64_C(0x3fe0c152382d7365);
+  tmp->SetVRegLong(0, static_cast<int64_t>(lvalue));
+
+  JValue result;
+  UnstartedMathCos(self, tmp, &result, 0);
+
+  const uint64_t lresult = static_cast<uint64_t>(result.GetJ());
+  EXPECT_EQ(UINT64_C(0x3febb67ae8584cab), lresult);
+
+  ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
+TEST_F(UnstartedRuntimeTest, Pow) {
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+
+  ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+  // Test an important pair.
+  constexpr uint64_t lvalue1 = UINT64_C(0x4079000000000000);
+  constexpr uint64_t lvalue2 = UINT64_C(0xbfe6db6dc0000000);
+
+  tmp->SetVRegLong(0, static_cast<int64_t>(lvalue1));
+  tmp->SetVRegLong(2, static_cast<int64_t>(lvalue2));
+
+  JValue result;
+  UnstartedMathPow(self, tmp, &result, 0);
+
+  const uint64_t lresult = static_cast<uint64_t>(result.GetJ());
+  EXPECT_EQ(UINT64_C(0x3f8c5c51326aa7ee), lresult);
+
+  ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
 }  // namespace interpreter
 }  // namespace art
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 558e443..2a66847 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -97,8 +97,9 @@
       LOG(FATAL) << "Priority thread weight cannot be 0.";
     }
   } else {
-    jit_options->priority_thread_weight_ =
-        std::max(jit_options->compile_threshold_ / 2000, static_cast<size_t>(1));
+    jit_options->priority_thread_weight_ = std::max(
+        jit_options->warmup_threshold_ / Jit::kDefaultPriorityThreadWeightRatio,
+        static_cast<size_t>(1));
   }
 
   return jit_options;
@@ -154,6 +155,8 @@
   jit->warm_method_threshold_ = options->GetWarmupThreshold();
   jit->osr_method_threshold_ = options->GetOsrThreshold();
   jit->priority_thread_weight_ = options->GetPriorityThreadWeight();
+  jit->transition_weight_ = std::max(
+      jit->warm_method_threshold_ / kDefaultTransitionRatio, static_cast<size_t>(1));
 
   jit->CreateThreadPool();
 
@@ -240,8 +243,17 @@
   if (!code_cache_->NotifyCompilationOf(method_to_compile, self, osr)) {
     return false;
   }
+
+  VLOG(jit) << "Compiling method "
+            << PrettyMethod(method_to_compile)
+            << " osr=" << std::boolalpha << osr;
   bool success = jit_compile_method_(jit_compiler_handle_, method_to_compile, self, osr);
   code_cache_->DoneCompiling(method_to_compile, self, osr);
+  if (!success) {
+    VLOG(jit) << "Failed to compile method "
+              << PrettyMethod(method_to_compile)
+              << " osr=" << std::boolalpha << osr;
+  }
   return success;
 }
 
@@ -520,15 +532,9 @@
   void Run(Thread* self) OVERRIDE {
     ScopedObjectAccess soa(self);
     if (kind_ == kCompile) {
-      VLOG(jit) << "JitCompileTask compiling method " << PrettyMethod(method_);
-      if (!Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ false)) {
-        VLOG(jit) << "Failed to compile method " << PrettyMethod(method_);
-      }
+      Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ false);
     } else if (kind_ == kCompileOsr) {
-      VLOG(jit) << "JitCompileTask compiling method osr " << PrettyMethod(method_);
-      if (!Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ true)) {
-        VLOG(jit) << "Failed to compile method osr " << PrettyMethod(method_);
-      }
+      Runtime::Current()->GetJit()->CompileMethod(method_, self, /* osr */ true);
     } else {
       DCHECK(kind_ == kAllocateProfile);
       if (ProfilingInfo::Create(self, method_, /* retry_allocation */ true)) {
@@ -549,7 +555,7 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask);
 };
 
-void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count) {
+void Jit::AddSamples(Thread* self, ArtMethod* method, uint16_t count, bool with_backedges) {
   if (thread_pool_ == nullptr) {
     // Should only see this when shutting down.
     DCHECK(Runtime::Current()->IsShuttingDown(self));
@@ -573,7 +579,8 @@
   }
   int32_t new_count = starting_count + count;   // int32 here to avoid wrap-around;
   if (starting_count < warm_method_threshold_) {
-    if (new_count >= warm_method_threshold_) {
+    if ((new_count >= warm_method_threshold_) &&
+        (method->GetProfilingInfo(sizeof(void*)) == nullptr)) {
       bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false);
       if (success) {
         VLOG(jit) << "Start profiling " << PrettyMethod(method);
@@ -595,14 +602,19 @@
     // Avoid jumping more than one state at a time.
     new_count = std::min(new_count, hot_method_threshold_ - 1);
   } else if (starting_count < hot_method_threshold_) {
-    if (new_count >= hot_method_threshold_) {
+    if ((new_count >= hot_method_threshold_) &&
+        !code_cache_->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) {
       DCHECK(thread_pool_ != nullptr);
       thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile));
     }
     // Avoid jumping more than one state at a time.
     new_count = std::min(new_count, osr_method_threshold_ - 1);
   } else if (starting_count < osr_method_threshold_) {
-    if (new_count >= osr_method_threshold_) {
+    if (!with_backedges) {
+      // If the samples don't contain any back edge, we don't increment the hotness.
+      return;
+    }
+    if ((new_count >= osr_method_threshold_) &&  !code_cache_->IsOsrCompiled(method)) {
       DCHECK(thread_pool_ != nullptr);
       thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompileOsr));
     }
@@ -623,14 +635,11 @@
   ProfilingInfo* profiling_info = method->GetProfilingInfo(sizeof(void*));
   // Update the entrypoint if the ProfilingInfo has one. The interpreter will call it
   // instead of interpreting the method.
-  // We avoid doing this if exit stubs are installed to not mess with the instrumentation.
-  // TODO(ngeoffray): Clean up instrumentation and code cache interactions.
-  if ((profiling_info != nullptr) &&
-      (profiling_info->GetSavedEntryPoint() != nullptr) &&
-      !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) {
-    method->SetEntryPointFromQuickCompiledCode(profiling_info->GetSavedEntryPoint());
+  if ((profiling_info != nullptr) && (profiling_info->GetSavedEntryPoint() != nullptr)) {
+    Runtime::Current()->GetInstrumentation()->UpdateMethodsCode(
+        method, profiling_info->GetSavedEntryPoint());
   } else {
-    AddSamples(thread, method, 1);
+    AddSamples(thread, method, 1, /* with_backedges */false);
   }
 }
 
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 96f9608..ff3acf6 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -43,6 +43,8 @@
  public:
   static constexpr bool kStressMode = kIsDebugBuild;
   static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 10000;
+  static constexpr size_t kDefaultPriorityThreadWeightRatio = 1000;
+  static constexpr size_t kDefaultTransitionRatio = 100;
 
   virtual ~Jit();
   static Jit* Create(JitOptions* options, std::string* error_msg);
@@ -92,7 +94,7 @@
   void MethodEntered(Thread* thread, ArtMethod* method)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
-  void AddSamples(Thread* self, ArtMethod* method, uint16_t samples)
+  void AddSamples(Thread* self, ArtMethod* method, uint16_t samples, bool with_backedges)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   void InvokeVirtualOrInterface(Thread* thread,
@@ -102,6 +104,16 @@
                                 ArtMethod* callee)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  void NotifyInterpreterToCompiledCodeTransition(Thread* self, ArtMethod* caller)
+      SHARED_REQUIRES(Locks::mutator_lock_) {
+    AddSamples(self, caller, transition_weight_, false);
+  }
+
+  void NotifyCompiledCodeToInterpreterTransition(Thread* self, ArtMethod* callee)
+      SHARED_REQUIRES(Locks::mutator_lock_) {
+    AddSamples(self, callee, transition_weight_, false);
+  }
+
   // Starts the profile saver if the config options allow profile recording.
   // The profile will be stored in the specified `filename` and will contain
   // information collected from the given `code_paths` (a set of dex locations).
@@ -175,6 +187,7 @@
   uint16_t warm_method_threshold_;
   uint16_t osr_method_threshold_;
   uint16_t priority_thread_weight_;
+  uint16_t transition_weight_;
   std::unique_ptr<ThreadPool> thread_pool_;
 
   DISALLOW_COPY_AND_ASSIGN(Jit);
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 820ae6a..752d4ba 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -354,8 +354,7 @@
     if (osr) {
       number_of_osr_compilations_++;
       osr_code_map_.Put(method, code_ptr);
-    } else if (!Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) {
-      // TODO(ngeoffray): Clean up instrumentation and code cache interactions.
+    } else {
       Runtime::Current()->GetInstrumentation()->UpdateMethodsCode(
           method, method_header->GetEntryPoint());
     }
@@ -366,7 +365,7 @@
     }
     last_update_time_ns_.StoreRelease(NanoTime());
     VLOG(jit)
-        << "JIT added (osr = " << std::boolalpha << osr << std::noboolalpha << ") "
+        << "JIT added (osr=" << std::boolalpha << osr << std::noboolalpha << ") "
         << PrettyMethod(method) << "@" << method
         << " ccache_size=" << PrettySize(CodeCacheSizeLocked()) << ": "
         << " dcache_size=" << PrettySize(DataCacheSizeLocked()) << ": "
@@ -634,10 +633,7 @@
       bool next_collection_will_be_full = ShouldDoFullCollection();
 
       // Start polling the liveness of compiled code to prepare for the next full collection.
-      // We avoid doing this if exit stubs are installed to not mess with the instrumentation.
-      // TODO(ngeoffray): Clean up instrumentation and code cache interactions.
-      if (!Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled() &&
-          next_collection_will_be_full) {
+      if (next_collection_will_be_full) {
         // Save the entry point of methods we have compiled, and update the entry
         // point of those methods to the interpreter. If the method is invoked, the
         // interpreter will update its entry point to the compiled code and call it.
@@ -645,7 +641,8 @@
           const void* entry_point = info->GetMethod()->GetEntryPointFromQuickCompiledCode();
           if (ContainsPc(entry_point)) {
             info->SetSavedEntryPoint(entry_point);
-            info->GetMethod()->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
+            Runtime::Current()->GetInstrumentation()->UpdateMethodsCode(
+                info->GetMethod(), GetQuickToInterpreterBridge());
           }
         }
 
@@ -905,15 +902,18 @@
   return last_update_time_ns_.LoadAcquire();
 }
 
+bool JitCodeCache::IsOsrCompiled(ArtMethod* method) {
+  MutexLock mu(Thread::Current(), lock_);
+  return osr_code_map_.find(method) != osr_code_map_.end();
+}
+
 bool JitCodeCache::NotifyCompilationOf(ArtMethod* method, Thread* self, bool osr) {
   if (!osr && ContainsPc(method->GetEntryPointFromQuickCompiledCode())) {
-    VLOG(jit) << PrettyMethod(method) << " is already compiled";
     return false;
   }
 
   MutexLock mu(self, lock_);
   if (osr && (osr_code_map_.find(method) != osr_code_map_.end())) {
-    VLOG(jit) << PrettyMethod(method) << " is already osr compiled";
     return false;
   }
 
@@ -928,7 +928,6 @@
   }
 
   if (info->IsMethodBeingCompiled(osr)) {
-    VLOG(jit) << PrettyMethod(method) << " is already being compiled";
     return false;
   }
 
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 9f18c70..f31cc51 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -186,6 +186,8 @@
 
   void Dump(std::ostream& os) REQUIRES(!lock_);
 
+  bool IsOsrCompiled(ArtMethod* method) REQUIRES(!lock_);
+
  private:
   // Take ownership of maps.
   JitCodeCache(MemMap* code_map,
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index c718466..04ba8df 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -2286,16 +2286,16 @@
 // Test the offset computation of JNIEnvExt offsets. b/26071368.
 TEST_F(JniInternalTest, JNIEnvExtOffsets) {
   EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie),
-            JNIEnvExt::LocalRefCookieOffset(sizeof(void*)).Int32Value());
+            JNIEnvExt::LocalRefCookieOffset(sizeof(void*)).Uint32Value());
 
-  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self), JNIEnvExt::SelfOffset(sizeof(void*)).Int32Value());
+  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self), JNIEnvExt::SelfOffset(sizeof(void*)).Uint32Value());
 
   // segment_state_ is private in the IndirectReferenceTable. So this test isn't as good as we'd
   // hope it to be.
-  int32_t segment_state_now =
+  uint32_t segment_state_now =
       OFFSETOF_MEMBER(JNIEnvExt, locals) +
-      IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Int32Value();
-  int32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Int32Value();
+      IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Uint32Value();
+  uint32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Uint32Value();
   EXPECT_EQ(segment_state_now, segment_state_computed);
 }
 
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 42f003d..b4a23ba 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -1165,5 +1165,16 @@
     mirror::Class* klass,
     mirror::ObjectArray<mirror::Class>* args);
 
+int32_t Class::GetInnerClassFlags(Handle<Class> h_this, int32_t default_value) {
+  if (h_this->IsProxyClass() || h_this->GetDexCache() == nullptr) {
+    return default_value;
+  }
+  uint32_t flags;
+  if (!h_this->GetDexFile().GetInnerClassFlags(h_this, &flags)) {
+    return default_value;
+  }
+  return flags;
+}
+
 }  // namespace mirror
 }  // namespace art
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 57c3590..5b6ded1 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -1223,6 +1223,9 @@
       Thread* self, Handle<mirror::ObjectArray<mirror::Class>> args, size_t pointer_size)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  static int32_t GetInnerClassFlags(Handle<Class> h_this, int32_t default_value)
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
   // Used to initialize a class in the allocation code path to ensure it is guarded by a StoreStore
   // fence.
   class InitializeClassVisitor {
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index 0e7f7f3..6285542 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -33,7 +33,7 @@
 namespace mirror {
 
 inline uint32_t String::ClassSize(size_t pointer_size) {
-  uint32_t vtable_entries = Object::kVTableLength + 52;
+  uint32_t vtable_entries = Object::kVTableLength + 56;
   return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size);
 }
 
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 6290cb2..3680c78 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -215,6 +215,26 @@
   obj_ = GcRoot<mirror::Object>(object);
 }
 
+std::string Monitor::PrettyContentionInfo(const std::string& owner_name,
+                                          pid_t owner_tid,
+                                          ArtMethod* owners_method,
+                                          uint32_t owners_dex_pc,
+                                          size_t num_waiters) {
+  const char* owners_filename;
+  int32_t owners_line_number = 0;
+  if (owners_method != nullptr) {
+    TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number);
+  }
+  std::ostringstream oss;
+  oss << "monitor contention with owner " << owner_name << " (" << owner_tid << ")";
+  if (owners_method != nullptr) {
+    oss << " owner method=" << PrettyMethod(owners_method);
+    oss << " from " << owners_filename << ":" << owners_line_number;
+  }
+  oss << " waiters=" << num_waiters;
+  return oss.str();
+}
+
 void Monitor::Lock(Thread* self) {
   MutexLock mu(self, monitor_lock_);
   while (true) {
@@ -242,36 +262,83 @@
     monitor_lock_.Unlock(self);  // Let go of locks in order.
     self->SetMonitorEnterObject(GetObject());
     {
+      uint32_t original_owner_thread_id = 0u;
       ScopedThreadStateChange tsc(self, kBlocked);  // Change to blocked and give up mutator_lock_.
-      // Reacquire monitor_lock_ without mutator_lock_ for Wait.
-      MutexLock mu2(self, monitor_lock_);
-      if (owner_ != nullptr) {  // Did the owner_ give the lock up?
-        if (ATRACE_ENABLED()) {
-          std::string name;
-          owner_->GetThreadName(name);
-          ATRACE_BEGIN(("Contended on monitor with owner " + name).c_str());
+      {
+        // Reacquire monitor_lock_ without mutator_lock_ for Wait.
+        MutexLock mu2(self, monitor_lock_);
+        if (owner_ != nullptr) {  // Did the owner_ give the lock up?
+          original_owner_thread_id = owner_->GetThreadId();
+          if (ATRACE_ENABLED()) {
+            std::ostringstream oss;
+            std::string name;
+            owner_->GetThreadName(name);
+            oss << PrettyContentionInfo(name,
+                                        owner_->GetTid(),
+                                        owners_method,
+                                        owners_dex_pc,
+                                        num_waiters);
+            // Add info for contending thread.
+            uint32_t pc;
+            ArtMethod* m = self->GetCurrentMethod(&pc);
+            const char* filename;
+            int32_t line_number;
+            TranslateLocation(m, pc, &filename, &line_number);
+            oss << " blocking from " << (filename != nullptr ? filename : "null")
+                << ":" << line_number;
+            ATRACE_BEGIN(oss.str().c_str());
+          }
+          monitor_contenders_.Wait(self);  // Still contended so wait.
         }
-        monitor_contenders_.Wait(self);  // Still contended so wait.
+      }
+      if (original_owner_thread_id != 0u) {
         // Woken from contention.
         if (log_contention) {
-          uint64_t wait_ms = MilliTime() - wait_start_ms;
-          uint32_t sample_percent;
-          if (wait_ms >= lock_profiling_threshold_) {
-            sample_percent = 100;
-          } else {
-            sample_percent = 100 * wait_ms / lock_profiling_threshold_;
-          }
-          if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) {
-            const char* owners_filename;
-            int32_t owners_line_number;
-            TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number);
-            if (wait_ms > kLongWaitMs && owners_method != nullptr) {
-              LOG(WARNING) << "Long monitor contention event with owner method="
-                  << PrettyMethod(owners_method) << " from " << owners_filename << ":"
-                  << owners_line_number << " waiters=" << num_waiters << " for "
-                  << PrettyDuration(MsToNs(wait_ms));
+          uint32_t original_owner_tid = 0;
+          std::string original_owner_name;
+          {
+            MutexLock mu2(Thread::Current(), *Locks::thread_list_lock_);
+            // Re-find the owner in case the thread got killed.
+            Thread* original_owner = Runtime::Current()->GetThreadList()->FindThreadByThreadId(
+                original_owner_thread_id);
+            // Do not do any work that requires the mutator lock.
+            if (original_owner != nullptr) {
+              original_owner_tid = original_owner->GetTid();
+              original_owner->GetThreadName(original_owner_name);
             }
-            LogContentionEvent(self, wait_ms, sample_percent, owners_filename, owners_line_number);
+          }
+
+          if (original_owner_tid != 0u) {
+            uint64_t wait_ms = MilliTime() - wait_start_ms;
+            uint32_t sample_percent;
+            if (wait_ms >= lock_profiling_threshold_) {
+              sample_percent = 100;
+            } else {
+              sample_percent = 100 * wait_ms / lock_profiling_threshold_;
+            }
+            if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) {
+              if (wait_ms > kLongWaitMs && owners_method != nullptr) {
+                // TODO: We should maybe check that original_owner is still a live thread.
+                LOG(WARNING) << "Long "
+                    << PrettyContentionInfo(original_owner_name,
+                                            original_owner_tid,
+                                            owners_method,
+                                            owners_dex_pc,
+                                            num_waiters)
+                    << " for " << PrettyDuration(MsToNs(wait_ms));
+              }
+              const char* owners_filename;
+              int32_t owners_line_number;
+              TranslateLocation(owners_method,
+                                owners_dex_pc,
+                                &owners_filename,
+                                &owners_line_number);
+              LogContentionEvent(self,
+                                 wait_ms,
+                                 sample_percent,
+                                 owners_filename,
+                                 owners_line_number);
+            }
           }
         }
         ATRACE_END();
@@ -311,25 +378,34 @@
   return oss.str();
 }
 
-void Monitor::FailedUnlock(mirror::Object* o, Thread* expected_owner, Thread* found_owner,
+void Monitor::FailedUnlock(mirror::Object* o,
+                           uint32_t expected_owner_thread_id,
+                           uint32_t found_owner_thread_id,
                            Monitor* monitor) {
-  Thread* current_owner = nullptr;
+  // Acquire thread list lock so threads won't disappear from under us.
   std::string current_owner_string;
   std::string expected_owner_string;
   std::string found_owner_string;
+  uint32_t current_owner_thread_id = 0u;
   {
-    // TODO: isn't this too late to prevent threads from disappearing?
-    // Acquire thread list lock so threads won't disappear from under us.
     MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+    ThreadList* const thread_list = Runtime::Current()->GetThreadList();
+    Thread* expected_owner = thread_list->FindThreadByThreadId(expected_owner_thread_id);
+    Thread* found_owner = thread_list->FindThreadByThreadId(found_owner_thread_id);
+
     // Re-read owner now that we hold lock.
-    current_owner = (monitor != nullptr) ? monitor->GetOwner() : nullptr;
+    Thread* current_owner = (monitor != nullptr) ? monitor->GetOwner() : nullptr;
+    if (current_owner != nullptr) {
+      current_owner_thread_id = current_owner->GetThreadId();
+    }
     // Get short descriptions of the threads involved.
     current_owner_string = ThreadToString(current_owner);
-    expected_owner_string = ThreadToString(expected_owner);
-    found_owner_string = ThreadToString(found_owner);
+    expected_owner_string = expected_owner != nullptr ? ThreadToString(expected_owner) : "unnamed";
+    found_owner_string = found_owner != nullptr ? ThreadToString(found_owner) : "unnamed";
   }
-  if (current_owner == nullptr) {
-    if (found_owner == nullptr) {
+
+  if (current_owner_thread_id == 0u) {
+    if (found_owner_thread_id == 0u) {
       ThrowIllegalMonitorStateExceptionF("unlock of unowned monitor on object of type '%s'"
                                          " on thread '%s'",
                                          PrettyTypeOf(o).c_str(),
@@ -343,7 +419,7 @@
                                          expected_owner_string.c_str());
     }
   } else {
-    if (found_owner == nullptr) {
+    if (found_owner_thread_id == 0u) {
       // Race: originally there was no owner, there is now
       ThrowIllegalMonitorStateExceptionF("unlock of monitor owned by '%s' on object of type '%s'"
                                          " (originally believed to be unowned) on thread '%s'",
@@ -351,7 +427,7 @@
                                          PrettyTypeOf(o).c_str(),
                                          expected_owner_string.c_str());
     } else {
-      if (found_owner != current_owner) {
+      if (found_owner_thread_id != current_owner_thread_id) {
         // Race: originally found and current owner have changed
         ThrowIllegalMonitorStateExceptionF("unlock of monitor originally owned by '%s' (now"
                                            " owned by '%s') on object of type '%s' on thread '%s'",
@@ -372,27 +448,31 @@
 
 bool Monitor::Unlock(Thread* self) {
   DCHECK(self != nullptr);
-  MutexLock mu(self, monitor_lock_);
-  Thread* owner = owner_;
-  if (owner == self) {
-    // We own the monitor, so nobody else can be in here.
-    if (lock_count_ == 0) {
-      owner_ = nullptr;
-      locking_method_ = nullptr;
-      locking_dex_pc_ = 0;
-      // Wake a contender.
-      monitor_contenders_.Signal(self);
-    } else {
-      --lock_count_;
+  uint32_t owner_thread_id = 0u;
+  {
+    MutexLock mu(self, monitor_lock_);
+    Thread* owner = owner_;
+    if (owner != nullptr) {
+      owner_thread_id = owner->GetThreadId();
     }
-  } else {
-    // We don't own this, so we're not allowed to unlock it.
-    // The JNI spec says that we should throw IllegalMonitorStateException
-    // in this case.
-    FailedUnlock(GetObject(), self, owner, this);
-    return false;
+    if (owner == self) {
+      // We own the monitor, so nobody else can be in here.
+      if (lock_count_ == 0) {
+        owner_ = nullptr;
+        locking_method_ = nullptr;
+        locking_dex_pc_ = 0;
+        // Wake a contender.
+        monitor_contenders_.Signal(self);
+      } else {
+        --lock_count_;
+      }
+      return true;
+    }
   }
-  return true;
+  // We don't own this, so we're not allowed to unlock it.
+  // The JNI spec says that we should throw IllegalMonitorStateException in this case.
+  FailedUnlock(GetObject(), self->GetThreadId(), owner_thread_id, this);
+  return false;
 }
 
 void Monitor::Wait(Thread* self, int64_t ms, int32_t ns,
@@ -769,16 +849,13 @@
       case LockWord::kHashCode:
         // Fall-through.
       case LockWord::kUnlocked:
-        FailedUnlock(h_obj.Get(), self, nullptr, nullptr);
+        FailedUnlock(h_obj.Get(), self->GetThreadId(), 0u, nullptr);
         return false;  // Failure.
       case LockWord::kThinLocked: {
         uint32_t thread_id = self->GetThreadId();
         uint32_t owner_thread_id = lock_word.ThinLockOwner();
         if (owner_thread_id != thread_id) {
-          // TODO: there's a race here with the owner dying while we unlock.
-          Thread* owner =
-              Runtime::Current()->GetThreadList()->FindThreadByThreadId(lock_word.ThinLockOwner());
-          FailedUnlock(h_obj.Get(), self, owner, nullptr);
+          FailedUnlock(h_obj.Get(), thread_id, owner_thread_id, nullptr);
           return false;  // Failure.
         } else {
           // We own the lock, decrease the recursion count.
@@ -1072,8 +1149,10 @@
   return owner_ != nullptr;
 }
 
-void Monitor::TranslateLocation(ArtMethod* method, uint32_t dex_pc,
-                                const char** source_file, int32_t* line_number) const {
+void Monitor::TranslateLocation(ArtMethod* method,
+                                uint32_t dex_pc,
+                                const char** source_file,
+                                int32_t* line_number) {
   // If method is null, location is unknown
   if (method == nullptr) {
     *source_file = "";
diff --git a/runtime/monitor.h b/runtime/monitor.h
index ae9b3cc..8c7496b 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -185,9 +185,12 @@
                           const char* owner_filename, int32_t owner_line_number)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
-  static void FailedUnlock(mirror::Object* obj, Thread* expected_owner, Thread* found_owner,
+  static void FailedUnlock(mirror::Object* obj,
+                           uint32_t expected_owner_thread_id,
+                           uint32_t found_owner_thread_id,
                            Monitor* mon)
-      REQUIRES(!Locks::thread_list_lock_)
+      REQUIRES(!Locks::thread_list_lock_,
+               !monitor_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   void Lock(Thread* self)
@@ -208,6 +211,13 @@
       REQUIRES(!monitor_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  static std::string PrettyContentionInfo(const std::string& owner_name,
+                                          pid_t owner_tid,
+                                          ArtMethod* owners_method,
+                                          uint32_t owners_dex_pc,
+                                          size_t num_waiters)
+      REQUIRES(!Locks::thread_list_lock_)
+      SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Wait on a monitor until timeout, interrupt, or notification.  Used for Object.wait() and
   // (somewhat indirectly) Thread.sleep() and Thread.join().
@@ -233,8 +243,9 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Translates the provided method and pc into its declaring class' source file and line number.
-  void TranslateLocation(ArtMethod* method, uint32_t pc,
-                         const char** source_file, int32_t* line_number) const
+  static void TranslateLocation(ArtMethod* method, uint32_t pc,
+                                const char** source_file,
+                                int32_t* line_number)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   uint32_t GetOwnerThreadId() REQUIRES(!monitor_lock_);
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index c1899af..6b7ca40 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -517,14 +517,7 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
-  if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
-    return defaultValue;
-  }
-  uint32_t flags;
-  if (!klass->GetDexFile().GetInnerClassFlags(klass, &flags)) {
-    return defaultValue;
-  }
-  return flags;
+  return mirror::Class::GetInnerClassFlags(klass, defaultValue);
 }
 
 static jstring Class_getInnerClassName(JNIEnv* env, jobject javaThis) {
diff --git a/runtime/thread.h b/runtime/thread.h
index 2218b5a..ed42e46 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -312,6 +312,7 @@
    */
   static int GetNativePriority();
 
+  // Guaranteed to be non-zero.
   uint32_t GetThreadId() const {
     return tls32_.thin_lock_thread_id;
   }
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index a9ce056..1e5264c 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -923,12 +923,9 @@
   }
 }
 
-Thread* ThreadList::FindThreadByThreadId(uint32_t thin_lock_id) {
-  Thread* self = Thread::Current();
-  MutexLock mu(self, *Locks::thread_list_lock_);
+Thread* ThreadList::FindThreadByThreadId(uint32_t thread_id) {
   for (const auto& thread : list_) {
-    if (thread->GetThreadId() == thin_lock_id) {
-      CHECK(thread == self || thread->IsSuspended());
+    if (thread->GetThreadId() == thread_id) {
       return thread;
     }
   }
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index f97ecd3..df81ad1 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -89,8 +89,8 @@
                !Locks::thread_list_lock_,
                !Locks::thread_suspend_count_lock_);
 
-  // Find an already suspended thread (or self) by its id.
-  Thread* FindThreadByThreadId(uint32_t thin_lock_id);
+  // Find an existing thread (or self) by its thread id (not tid).
+  Thread* FindThreadByThreadId(uint32_t thread_id) REQUIRES(Locks::thread_list_lock_);
 
   // Run a checkpoint on threads, running threads are not suspended but run the checkpoint inside
   // of the suspend check. Returns how many checkpoints that are expected to run, including for
diff --git a/test/567-checker-compare/src/Main.java b/test/567-checker-compare/src/Main.java
index f95ff1a..8587950 100644
--- a/test/567-checker-compare/src/Main.java
+++ b/test/567-checker-compare/src/Main.java
@@ -16,6 +16,32 @@
 
 public class Main {
 
+  public static boolean doThrow = false;
+
+  /// CHECK-START: void Main.$opt$noinline$testReplaceInputWithItself(int) intrinsics_recognition (after)
+  /// CHECK-DAG:     <<ArgX:i\d+>>   ParameterValue
+  /// CHECK-DAG:     <<Method:[ij]\d+>> CurrentMethod
+  /// CHECK-DAG:     <<Zero:i\d+>>   IntConstant 0
+  /// CHECK-DAG:     <<Cmp:i\d+>>    InvokeStaticOrDirect [<<ArgX>>,<<Zero>>,<<Method>>] intrinsic:IntegerCompare
+  /// CHECK-DAG:                     GreaterThanOrEqual [<<Cmp>>,<<Zero>>]
+
+  /// CHECK-START: void Main.$opt$noinline$testReplaceInputWithItself(int) instruction_simplifier (after)
+  /// CHECK-DAG:     <<ArgX:i\d+>>   ParameterValue
+  /// CHECK-DAG:     <<Zero:i\d+>>   IntConstant 0
+  /// CHECK-DAG:                     GreaterThanOrEqual [<<ArgX>>,<<Zero>>]
+
+  public static void $opt$noinline$testReplaceInputWithItself(int x) {
+    if (doThrow) { throw new Error(); }
+
+    // The instruction simplifier first replaces Integer.compare(x, 0) with Compare HIR
+    // and then merges the Compare into the GreaterThanOrEqual. This is a regression
+    // test that to check that it is allowed to replace the second input of the
+    // GreaterThanOrEqual, i.e. <<Zero>>, with the very same instruction.
+    if (Integer.compare(x, 0) < 0) {
+      System.out.println("OOOPS");
+    }
+  }
+
   /// CHECK-START: int Main.compareBooleans(boolean, boolean) intrinsics_recognition (after)
   /// CHECK-DAG:     <<Method:[ij]\d+>> CurrentMethod
   /// CHECK-DAG:     <<Zero:i\d+>>   IntConstant 0
@@ -890,6 +916,8 @@
 
 
   public static void main(String args[]) {
+    $opt$noinline$testReplaceInputWithItself(42);
+
     testCompareBooleans();
     testCompareBytes();
     testCompareShorts();
diff --git a/test/570-checker-osr/expected.txt b/test/570-checker-osr/expected.txt
index 25fb220..65447be 100644
--- a/test/570-checker-osr/expected.txt
+++ b/test/570-checker-osr/expected.txt
@@ -3,3 +3,4 @@
 200000
 300000
 400000
+b28210356 passed.
diff --git a/test/570-checker-osr/osr.cc b/test/570-checker-osr/osr.cc
index 09e97ea..2a5b2c9 100644
--- a/test/570-checker-osr/osr.cc
+++ b/test/570-checker-osr/osr.cc
@@ -20,15 +20,17 @@
 #include "jit/profiling_info.h"
 #include "oat_quick_method_header.h"
 #include "scoped_thread_state_change.h"
+#include "ScopedUtfChars.h"
 #include "stack_map.h"
 
 namespace art {
 
 class OsrVisitor : public StackVisitor {
  public:
-  explicit OsrVisitor(Thread* thread)
+  explicit OsrVisitor(Thread* thread, const char* method_name)
       SHARED_REQUIRES(Locks::mutator_lock_)
       : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        method_name_(method_name),
         in_osr_method_(false),
         in_interpreter_(false) {}
 
@@ -36,13 +38,7 @@
     ArtMethod* m = GetMethod();
     std::string m_name(m->GetName());
 
-    if ((m_name.compare("$noinline$returnInt") == 0) ||
-        (m_name.compare("$noinline$returnFloat") == 0) ||
-        (m_name.compare("$noinline$returnDouble") == 0) ||
-        (m_name.compare("$noinline$returnLong") == 0) ||
-        (m_name.compare("$noinline$deopt") == 0) ||
-        (m_name.compare("$noinline$inlineCache") == 0) ||
-        (m_name.compare("$noinline$stackOverflow") == 0)) {
+    if (m_name.compare(method_name_) == 0) {
       const OatQuickMethodHeader* header =
           Runtime::Current()->GetJit()->GetCodeCache()->LookupOsrMethodHeader(m);
       if (header != nullptr && header == GetCurrentOatQuickMethodHeader()) {
@@ -55,74 +51,89 @@
     return true;
   }
 
+  const char* const method_name_;
   bool in_osr_method_;
   bool in_interpreter_;
 };
 
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInOsrCode(JNIEnv*, jclass) {
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInOsrCode(JNIEnv* env,
+                                                            jclass,
+                                                            jstring method_name) {
   jit::Jit* jit = Runtime::Current()->GetJit();
   if (jit == nullptr) {
     // Just return true for non-jit configurations to stop the infinite loop.
     return JNI_TRUE;
   }
+  ScopedUtfChars chars(env, method_name);
+  CHECK(chars.c_str() != nullptr);
   ScopedObjectAccess soa(Thread::Current());
-  OsrVisitor visitor(soa.Self());
+  OsrVisitor visitor(soa.Self(), chars.c_str());
   visitor.WalkStack();
   return visitor.in_osr_method_;
 }
 
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_ensureInInterpreter(JNIEnv*, jclass) {
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInInterpreter(JNIEnv* env,
+                                                                jclass,
+                                                                jstring method_name) {
   if (!Runtime::Current()->UseJit()) {
     // The return value is irrelevant if we're not using JIT.
     return false;
   }
+  ScopedUtfChars chars(env, method_name);
+  CHECK(chars.c_str() != nullptr);
   ScopedObjectAccess soa(Thread::Current());
-  OsrVisitor visitor(soa.Self());
+  OsrVisitor visitor(soa.Self(), chars.c_str());
   visitor.WalkStack();
   return visitor.in_interpreter_;
 }
 
 class ProfilingInfoVisitor : public StackVisitor {
  public:
-  explicit ProfilingInfoVisitor(Thread* thread)
+  explicit ProfilingInfoVisitor(Thread* thread, const char* method_name)
       SHARED_REQUIRES(Locks::mutator_lock_)
-      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {}
+      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        method_name_(method_name) {}
 
   bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) {
     ArtMethod* m = GetMethod();
     std::string m_name(m->GetName());
 
-    if ((m_name.compare("$noinline$inlineCache") == 0) ||
-        (m_name.compare("$noinline$stackOverflow") == 0)) {
+    if (m_name.compare(method_name_) == 0) {
       ProfilingInfo::Create(Thread::Current(), m, /* retry_allocation */ true);
       return false;
     }
     return true;
   }
+
+  const char* const method_name_;
 };
 
-extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasProfilingInfo(JNIEnv*, jclass) {
+extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasProfilingInfo(JNIEnv* env,
+                                                                   jclass,
+                                                                   jstring method_name) {
   if (!Runtime::Current()->UseJit()) {
     return;
   }
+  ScopedUtfChars chars(env, method_name);
+  CHECK(chars.c_str() != nullptr);
   ScopedObjectAccess soa(Thread::Current());
-  ProfilingInfoVisitor visitor(soa.Self());
+  ProfilingInfoVisitor visitor(soa.Self(), chars.c_str());
   visitor.WalkStack();
 }
 
 class OsrCheckVisitor : public StackVisitor {
  public:
-  explicit OsrCheckVisitor(Thread* thread)
+  OsrCheckVisitor(Thread* thread, const char* method_name)
       SHARED_REQUIRES(Locks::mutator_lock_)
-      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames) {}
+      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        method_name_(method_name) {}
 
   bool VisitFrame() SHARED_REQUIRES(Locks::mutator_lock_) {
     ArtMethod* m = GetMethod();
     std::string m_name(m->GetName());
 
     jit::Jit* jit = Runtime::Current()->GetJit();
-    if ((m_name.compare("$noinline$inlineCache") == 0) ||
-        (m_name.compare("$noinline$stackOverflow") == 0)) {
+    if (m_name.compare(method_name_) == 0) {
       while (jit->GetCodeCache()->LookupOsrMethodHeader(m) == nullptr) {
         // Sleep to yield to the compiler thread.
         sleep(0);
@@ -133,14 +144,20 @@
     }
     return true;
   }
+
+  const char* const method_name_;
 };
 
-extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasOsrCode(JNIEnv*, jclass) {
+extern "C" JNIEXPORT void JNICALL Java_Main_ensureHasOsrCode(JNIEnv* env,
+                                                             jclass,
+                                                             jstring method_name) {
   if (!Runtime::Current()->UseJit()) {
     return;
   }
+  ScopedUtfChars chars(env, method_name);
+  CHECK(chars.c_str() != nullptr);
   ScopedObjectAccess soa(Thread::Current());
-  OsrCheckVisitor visitor(soa.Self());
+  OsrCheckVisitor visitor(soa.Self(), chars.c_str());
   visitor.WalkStack();
 }
 
diff --git a/test/570-checker-osr/src/Main.java b/test/570-checker-osr/src/Main.java
index 6514334..200b54a 100644
--- a/test/570-checker-osr/src/Main.java
+++ b/test/570-checker-osr/src/Main.java
@@ -63,6 +63,9 @@
 
     $noinline$stackOverflow(new Main(), /* isSecondInvocation */ false);
     $noinline$stackOverflow(new SubMain(), /* isSecondInvocation */ true);
+
+    $opt$noinline$testOsrInlineLoop(null);
+    System.out.println("b28210356 passed.");
   }
 
   public static int $noinline$returnInt() {
@@ -70,7 +73,7 @@
     int i = 0;
     for (; i < 100000; ++i) {
     }
-    while (!ensureInOsrCode()) {}
+    while (!isInOsrCode("$noinline$returnInt")) {}
     System.out.println(i);
     return 53;
   }
@@ -80,7 +83,7 @@
     int i = 0;
     for (; i < 200000; ++i) {
     }
-    while (!ensureInOsrCode()) {}
+    while (!isInOsrCode("$noinline$returnFloat")) {}
     System.out.println(i);
     return 42.2f;
   }
@@ -90,7 +93,7 @@
     int i = 0;
     for (; i < 300000; ++i) {
     }
-    while (!ensureInOsrCode()) {}
+    while (!isInOsrCode("$noinline$returnDouble")) {}
     System.out.println(i);
     return Double.longBitsToDouble(0xF000000000001111L);
   }
@@ -100,7 +103,7 @@
     int i = 0;
     for (; i < 400000; ++i) {
     }
-    while (!ensureInOsrCode()) {}
+    while (!isInOsrCode("$noinline$returnLong")) {}
     System.out.println(i);
     return 0xFFFF000000001111L;
   }
@@ -110,22 +113,22 @@
     int i = 0;
     for (; i < 100000; ++i) {
     }
-    while (!ensureInOsrCode()) {}
+    while (!isInOsrCode("$noinline$deopt")) {}
     DeoptimizationController.startDeoptimization();
   }
 
   public static Class $noinline$inlineCache(Main m, boolean isSecondInvocation) {
     // If we are running in non-JIT mode, or were unlucky enough to get this method
     // already JITted, just return the expected value.
-    if (!ensureInInterpreter()) {
+    if (!isInInterpreter("$noinline$inlineCache")) {
       return SubMain.class;
     }
 
-    ensureHasProfilingInfo();
+    ensureHasProfilingInfo("$noinline$inlineCache");
 
     // Ensure that we have OSR code to jump to.
     if (isSecondInvocation) {
-      ensureHasOsrCode();
+      ensureHasOsrCode("$noinline$inlineCache");
     }
 
     // This call will be optimized in the OSR compiled code
@@ -137,7 +140,7 @@
     // code we are jumping to will have wrongly optimize other as being a
     // 'Main'.
     if (isSecondInvocation) {
-      while (!ensureInOsrCode()) {}
+      while (!isInOsrCode("$noinline$inlineCache")) {}
     }
 
     // We used to wrongly optimize this call and assume 'other' was a 'Main'.
@@ -159,16 +162,16 @@
   public static void $noinline$stackOverflow(Main m, boolean isSecondInvocation) {
     // If we are running in non-JIT mode, or were unlucky enough to get this method
     // already JITted, just return the expected value.
-    if (!ensureInInterpreter()) {
+    if (!isInInterpreter("$noinline$stackOverflow")) {
       return;
     }
 
     // We need a ProfilingInfo object to populate the 'otherInlineCache' call.
-    ensureHasProfilingInfo();
+    ensureHasProfilingInfo("$noinline$stackOverflow");
 
     if (isSecondInvocation) {
       // Ensure we have an OSR code and we jump to it.
-      while (!ensureInOsrCode()) {}
+      while (!isInOsrCode("$noinline$stackOverflow")) {}
     }
 
     for (int i = 0; i < (isSecondInvocation ? 10000000 : 1); ++i) {
@@ -179,10 +182,46 @@
     }
   }
 
-  public static native boolean ensureInInterpreter();
-  public static native boolean ensureInOsrCode();
-  public static native void ensureHasProfilingInfo();
-  public static native void ensureHasOsrCode();
+  public static void $opt$noinline$testOsrInlineLoop(String[] args) {
+    // Regression test for inlining a method with a loop to a method without a loop in OSR mode.
+    if (doThrow) throw new Error();
+    assertIntEquals(12, $opt$inline$testRemoveSuspendCheck(12, 5));
+    // Since we cannot have a loop directly in this method, we need to force the OSR
+    // compilation from native code.
+    ensureHasProfilingInfo("$opt$noinline$testOsrInlineLoop");
+    ensureHasOsrCode("$opt$noinline$testOsrInlineLoop");
+  }
+
+  public static int $opt$inline$testRemoveSuspendCheck(int x, int y) {
+    // For this test we need an inlined loop and have DCE re-run loop analysis
+    // after inlining.
+    while (y > 0) {
+      while ($opt$inline$inlineFalse() || !$opt$inline$inlineTrue()) {
+        x++;
+      }
+      y--;
+    }
+    return x;
+  }
+
+  public static boolean $opt$inline$inlineTrue() {
+    return true;
+  }
+
+  public static boolean $opt$inline$inlineFalse() {
+    return false;
+  }
+
+  public static void assertIntEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  public static native boolean isInOsrCode(String methodName);
+  public static native boolean isInInterpreter(String methodName);
+  public static native void ensureHasProfilingInfo(String methodName);
+  public static native void ensureHasOsrCode(String methodName);
 
   public static boolean doThrow = false;
 }
diff --git a/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali b/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali
index 8e01084..366c7b9 100644
--- a/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali
+++ b/test/594-checker-irreducible-linorder/smali/IrreducibleLoop.smali
@@ -40,6 +40,11 @@
    invoke-static {v0}, Ljava/lang/System;->exit(I)V
    goto :body1
 
+   # Trivially dead code to ensure linear order verification skips removed blocks (b/28252537).
+   :dead_code
+   nop
+   goto :dead_code
+
    :header
    mul-int/2addr p3, p3
    if-eqz p1, :body2
diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh
index 354fcef..b6a19b7 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -28,18 +28,6 @@
   exit 1
 fi
 
-if [ "x$ART_USE_READ_BARRIER" = xtrue ]; then
-  # For the moment, skip JDWP tests when read barriers are enabled, as
-  # they sometimes exhibit a deadlock issue with the concurrent
-  # copying collector in the read barrier configuration, between the
-  # HeapTaskDeamon and the JDWP thread (b/25800335).
-  #
-  # TODO: Re-enable the JDWP tests when this deadlock issue is fixed.
-  echo "JDWP tests are temporarily disabled in the read barrier configuration because of"
-  echo "a deadlock issue (b/25800335)."
-  exit 0
-fi
-
 art="/data/local/tmp/system/bin/art"
 art_debugee="sh /data/local/tmp/system/bin/art"
 args=$@