Merge "Revert "Revert "ARM: VIXL32: Use DontCare for SetFlags + fix for GenerateFrameEntry."""
diff --git a/Android.mk b/Android.mk
index b2716cd..cf3a9e7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -435,8 +435,9 @@
 # Phony target for only building what go/lem requires on target.
 .PHONY: build-art-target-golem
 # Also include libartbenchmark, we always include it when running golem.
+# libstdc++ is needed when building for ART_TARGET_LINUX.
 ART_TARGET_SHARED_LIBRARY_BENCHMARK := $(TARGET_OUT_SHARED_LIBRARIES)/libartbenchmark.so
-build-art-target-golem: dex2oat dalvikvm patchoat linker \
+build-art-target-golem: dex2oat dalvikvm patchoat linker libstdc++ \
                         $(TARGET_OUT)/etc/public.libraries.txt \
                         $(ART_TARGET_DEX_DEPENDENCIES) \
                         $(ART_TARGET_SHARED_LIBRARY_DEPENDENCIES) \
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index a2bab80..e62bdb5 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1188,13 +1188,12 @@
   CHECK_NE(image_classes_->size(), 0U);
 }
 
-static void MaybeAddToImageClasses(Handle<mirror::Class> c,
+static void MaybeAddToImageClasses(Thread* self,
+                                   ObjPtr<mirror::Class> klass,
                                    std::unordered_set<std::string>* image_classes)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  Thread* self = Thread::Current();
+  DCHECK_EQ(self, Thread::Current());
   StackHandleScope<1> hs(self);
-  // Make a copy of the handle so that we don't clobber it doing Assign.
-  MutableHandle<mirror::Class> klass(hs.NewHandle(c.Get()));
   std::string temp;
   const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
   while (!klass->IsObjectClass()) {
@@ -1205,19 +1204,16 @@
       break;
     }
     VLOG(compiler) << "Adding " << descriptor << " to image classes";
-    for (size_t i = 0; i < klass->NumDirectInterfaces(); ++i) {
-      StackHandleScope<1> hs2(self);
-      // May cause thread suspension.
-      MaybeAddToImageClasses(hs2.NewHandle(mirror::Class::GetDirectInterface(self, klass, i)),
-                             image_classes);
+    for (size_t i = 0, num_interfaces = klass->NumDirectInterfaces(); i != num_interfaces; ++i) {
+      ObjPtr<mirror::Class> interface = mirror::Class::GetDirectInterface(self, klass, i);
+      DCHECK(interface != nullptr);
+      MaybeAddToImageClasses(self, interface, image_classes);
     }
-    for (auto& m : c->GetVirtualMethods(pointer_size)) {
-      StackHandleScope<1> hs2(self);
-      MaybeAddToImageClasses(hs2.NewHandle(m.GetDeclaringClass()), image_classes);
+    for (auto& m : klass->GetVirtualMethods(pointer_size)) {
+      MaybeAddToImageClasses(self, m.GetDeclaringClass(), image_classes);
     }
     if (klass->IsArrayClass()) {
-      StackHandleScope<1> hs2(self);
-      MaybeAddToImageClasses(hs2.NewHandle(klass->GetComponentType()), image_classes);
+      MaybeAddToImageClasses(self, klass->GetComponentType(), image_classes);
     }
     klass.Assign(klass->GetSuperClass());
   }
@@ -1268,8 +1264,10 @@
     for (Handle<mirror::Class> klass_root : image_classes_) {
       VisitClinitClassesObject(klass_root.Get());
     }
+    Thread* self = Thread::Current();
+    ScopedAssertNoThreadSuspension ants(__FUNCTION__);
     for (Handle<mirror::Class> h_klass : to_insert_) {
-      MaybeAddToImageClasses(h_klass, image_class_descriptors_);
+      MaybeAddToImageClasses(self, h_klass.Get(), image_class_descriptors_);
     }
   }
 
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 0fb2a8b..a47e711 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -838,21 +838,73 @@
   return true;
 }
 
-class ImageWriter::NonImageClassesVisitor : public ClassVisitor {
+class ImageWriter::PruneClassesVisitor : public ClassVisitor {
  public:
-  explicit NonImageClassesVisitor(ImageWriter* image_writer) : image_writer_(image_writer) {}
+  PruneClassesVisitor(ImageWriter* image_writer, ObjPtr<mirror::ClassLoader> class_loader)
+      : image_writer_(image_writer),
+        class_loader_(class_loader),
+        classes_to_prune_(),
+        defined_class_count_(0u) { }
 
-  bool operator()(ObjPtr<Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+  bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
     if (!image_writer_->KeepClass(klass.Ptr())) {
       classes_to_prune_.insert(klass.Ptr());
+      if (klass->GetClassLoader() == class_loader_) {
+        ++defined_class_count_;
+      }
     }
     return true;
   }
 
-  std::unordered_set<mirror::Class*> classes_to_prune_;
+  size_t Prune() REQUIRES_SHARED(Locks::mutator_lock_) {
+    ClassTable* class_table =
+        Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(class_loader_);
+    for (mirror::Class* klass : classes_to_prune_) {
+      std::string storage;
+      const char* descriptor = klass->GetDescriptor(&storage);
+      bool result = class_table->Remove(descriptor);
+      DCHECK(result);
+      DCHECK(!class_table->Remove(descriptor)) << descriptor;
+    }
+    return defined_class_count_;
+  }
+
+ private:
   ImageWriter* const image_writer_;
+  const ObjPtr<mirror::ClassLoader> class_loader_;
+  std::unordered_set<mirror::Class*> classes_to_prune_;
+  size_t defined_class_count_;
 };
 
+class ImageWriter::PruneClassLoaderClassesVisitor : public ClassLoaderVisitor {
+ public:
+  explicit PruneClassLoaderClassesVisitor(ImageWriter* image_writer)
+      : image_writer_(image_writer), removed_class_count_(0) {}
+
+  virtual void Visit(ObjPtr<mirror::ClassLoader> class_loader) OVERRIDE
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    PruneClassesVisitor classes_visitor(image_writer_, class_loader);
+    ClassTable* class_table =
+        Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(class_loader);
+    class_table->Visit(classes_visitor);
+    removed_class_count_ += classes_visitor.Prune();
+  }
+
+  size_t GetRemovedClassCount() const {
+    return removed_class_count_;
+  }
+
+ private:
+  ImageWriter* const image_writer_;
+  size_t removed_class_count_;
+};
+
+void ImageWriter::VisitClassLoaders(ClassLoaderVisitor* visitor) {
+  WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
+  visitor->Visit(nullptr);  // Visit boot class loader.
+  Runtime::Current()->GetClassLinker()->VisitClassLoaders(visitor);
+}
+
 void ImageWriter::PruneNonImageClasses() {
   Runtime* runtime = Runtime::Current();
   ClassLinker* class_linker = runtime->GetClassLinker();
@@ -862,21 +914,11 @@
   // path dex caches.
   class_linker->ClearClassTableStrongRoots();
 
-  // Make a list of classes we would like to prune.
-  NonImageClassesVisitor visitor(this);
-  class_linker->VisitClasses(&visitor);
-
   // Remove the undesired classes from the class roots.
-  VLOG(compiler) << "Pruning " << visitor.classes_to_prune_.size() << " classes";
-  for (mirror::Class* klass : visitor.classes_to_prune_) {
-    std::string temp;
-    const char* name = klass->GetDescriptor(&temp);
-    VLOG(compiler) << "Pruning class " << name;
-    if (!compile_app_image_) {
-      DCHECK(IsBootClassLoaderClass(klass));
-    }
-    bool result = class_linker->RemoveClass(name, klass->GetClassLoader());
-    DCHECK(result);
+  {
+    PruneClassLoaderClassesVisitor class_loader_visitor(this);
+    VisitClassLoaders(&class_loader_visitor);
+    VLOG(compiler) << "Pruned " << class_loader_visitor.GetRemovedClassCount() << " classes";
   }
 
   // Clear references to removed classes from the DexCaches.
@@ -1508,8 +1550,10 @@
     }
     // Calculate the size of the class table.
     ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
-    DCHECK_EQ(image_info.class_table_->NumZygoteClasses(), 0u);
-    if (image_info.class_table_->NumNonZygoteClasses() != 0u) {
+    CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
+    mirror::ClassLoader* class_loader = compile_app_image_ ? *class_loaders_.begin() : nullptr;
+    DCHECK_EQ(image_info.class_table_->NumZygoteClasses(class_loader), 0u);
+    if (image_info.class_table_->NumNonZygoteClasses(class_loader) != 0u) {
       image_info.class_table_bytes_ += image_info.class_table_->WriteToMemory(nullptr);
     }
   }
@@ -1854,8 +1898,10 @@
     // above comment for intern tables.
     ClassTable temp_class_table;
     temp_class_table.ReadFromMemory(class_table_memory_ptr);
-    CHECK_EQ(temp_class_table.NumZygoteClasses(), table->NumNonZygoteClasses() +
-             table->NumZygoteClasses());
+    CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
+    mirror::ClassLoader* class_loader = compile_app_image_ ? *class_loaders_.begin() : nullptr;
+    CHECK_EQ(temp_class_table.NumZygoteClasses(class_loader),
+             table->NumNonZygoteClasses(class_loader) + table->NumZygoteClasses(class_loader));
     UnbufferedRootVisitor visitor(&root_visitor, RootInfo(kRootUnknown));
     temp_class_table.VisitRoots(visitor);
   }
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 24fad46..c537483 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -50,6 +50,7 @@
 }  // namespace space
 }  // namespace gc
 
+class ClassLoaderVisitor;
 class ClassTable;
 
 static constexpr int kInvalidFd = -1;
@@ -373,6 +374,9 @@
   void ComputeLazyFieldsForImageClasses()
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Visit all class loaders.
+  void VisitClassLoaders(ClassLoaderVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Remove unwanted classes from various roots.
   void PruneNonImageClasses() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -588,7 +592,8 @@
   class FixupVisitor;
   class GetRootsVisitor;
   class NativeLocationVisitor;
-  class NonImageClassesVisitor;
+  class PruneClassesVisitor;
+  class PruneClassLoaderClassesVisitor;
   class VisitReferencesVisitor;
 
   DISALLOW_COPY_AND_ASSIGN(ImageWriter);
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index c617a6a..3a3d2a9 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -613,6 +613,509 @@
   DISALLOW_COPY_AND_ASSIGN(ArraySetSlowPathARMVIXL);
 };
 
+// Slow path marking an object reference `ref` during a read
+// barrier. The field `obj.field` in the object `obj` holding this
+// reference does not get updated by this slow path after marking (see
+// ReadBarrierMarkAndUpdateFieldSlowPathARM below for that).
+//
+// This means that after the execution of this slow path, `ref` will
+// always be up-to-date, but `obj.field` may not; i.e., after the
+// flip, `ref` will be a to-space reference, but `obj.field` will
+// probably still be a from-space reference (unless it gets updated by
+// another thread, or if another thread installed another object
+// reference (different from `ref`) in `obj.field`).
+class ReadBarrierMarkSlowPathARMVIXL : public SlowPathCodeARMVIXL {
+ public:
+  ReadBarrierMarkSlowPathARMVIXL(HInstruction* instruction,
+                                 Location ref,
+                                 Location entrypoint = Location::NoLocation())
+      : SlowPathCodeARMVIXL(instruction), ref_(ref), entrypoint_(entrypoint) {
+    DCHECK(kEmitCompilerReadBarrier);
+  }
+
+  const char* GetDescription() const OVERRIDE { return "ReadBarrierMarkSlowPathARMVIXL"; }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    LocationSummary* locations = instruction_->GetLocations();
+    vixl32::Register ref_reg = RegisterFrom(ref_);
+    DCHECK(locations->CanCall());
+    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg.GetCode())) << ref_reg;
+    DCHECK(instruction_->IsInstanceFieldGet() ||
+           instruction_->IsStaticFieldGet() ||
+           instruction_->IsArrayGet() ||
+           instruction_->IsArraySet() ||
+           instruction_->IsLoadClass() ||
+           instruction_->IsLoadString() ||
+           instruction_->IsInstanceOf() ||
+           instruction_->IsCheckCast() ||
+           (instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()) ||
+           (instruction_->IsInvokeStaticOrDirect() && instruction_->GetLocations()->Intrinsified()))
+        << "Unexpected instruction in read barrier marking slow path: "
+        << instruction_->DebugName();
+    // The read barrier instrumentation of object ArrayGet
+    // instructions does not support the HIntermediateAddress
+    // instruction.
+    DCHECK(!(instruction_->IsArrayGet() &&
+             instruction_->AsArrayGet()->GetArray()->IsIntermediateAddress()));
+
+    __ Bind(GetEntryLabel());
+    // No need to save live registers; it's taken care of by the
+    // entrypoint. Also, there is no need to update the stack mask,
+    // as this runtime call will not trigger a garbage collection.
+    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
+    DCHECK(!ref_reg.Is(sp));
+    DCHECK(!ref_reg.Is(lr));
+    DCHECK(!ref_reg.Is(pc));
+    // IP is used internally by the ReadBarrierMarkRegX entry point
+    // as a temporary, it cannot be the entry point's input/output.
+    DCHECK(!ref_reg.Is(ip));
+    DCHECK(ref_reg.IsRegister()) << ref_reg;
+    // "Compact" slow path, saving two moves.
+    //
+    // Instead of using the standard runtime calling convention (input
+    // and output in R0):
+    //
+    //   R0 <- ref
+    //   R0 <- ReadBarrierMark(R0)
+    //   ref <- R0
+    //
+    // we just use rX (the register containing `ref`) as input and output
+    // of a dedicated entrypoint:
+    //
+    //   rX <- ReadBarrierMarkRegX(rX)
+    //
+    if (entrypoint_.IsValid()) {
+      arm_codegen->ValidateInvokeRuntimeWithoutRecordingPcInfo(instruction_, this);
+      __ Blx(RegisterFrom(entrypoint_));
+    } else {
+      int32_t entry_point_offset =
+          CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg.GetCode());
+      // This runtime call does not require a stack map.
+      arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
+    }
+    __ B(GetExitLabel());
+  }
+
+ private:
+  // The location (register) of the marked object reference.
+  const Location ref_;
+
+  // The location of the entrypoint if already loaded.
+  const Location entrypoint_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathARMVIXL);
+};
+
+// Slow path marking an object reference `ref` during a read barrier,
+// and if needed, atomically updating the field `obj.field` in the
+// object `obj` holding this reference after marking (contrary to
+// ReadBarrierMarkSlowPathARM above, which never tries to update
+// `obj.field`).
+//
+// This means that after the execution of this slow path, both `ref`
+// and `obj.field` will be up-to-date; i.e., after the flip, both will
+// hold the same to-space reference (unless another thread installed
+// another object reference (different from `ref`) in `obj.field`).
+class ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL : public SlowPathCodeARMVIXL {
+ public:
+  ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL(HInstruction* instruction,
+                                               Location ref,
+                                               vixl32::Register obj,
+                                               Location field_offset,
+                                               vixl32::Register temp1,
+                                               vixl32::Register temp2)
+      : SlowPathCodeARMVIXL(instruction),
+        ref_(ref),
+        obj_(obj),
+        field_offset_(field_offset),
+        temp1_(temp1),
+        temp2_(temp2) {
+    DCHECK(kEmitCompilerReadBarrier);
+  }
+
+  const char* GetDescription() const OVERRIDE {
+    return "ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL";
+  }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    LocationSummary* locations = instruction_->GetLocations();
+    vixl32::Register ref_reg = RegisterFrom(ref_);
+    DCHECK(locations->CanCall());
+    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg.GetCode())) << ref_reg;
+    // This slow path is only used by the UnsafeCASObject intrinsic.
+    DCHECK((instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()))
+        << "Unexpected instruction in read barrier marking and field updating slow path: "
+        << instruction_->DebugName();
+    DCHECK(instruction_->GetLocations()->Intrinsified());
+    DCHECK_EQ(instruction_->AsInvoke()->GetIntrinsic(), Intrinsics::kUnsafeCASObject);
+    DCHECK(field_offset_.IsRegisterPair()) << field_offset_;
+
+    __ Bind(GetEntryLabel());
+
+    // Save the old reference.
+    // Note that we cannot use IP to save the old reference, as IP is
+    // used internally by the ReadBarrierMarkRegX entry point, and we
+    // need the old reference after the call to that entry point.
+    DCHECK(!temp1_.Is(ip));
+    __ Mov(temp1_, ref_reg);
+
+    // No need to save live registers; it's taken care of by the
+    // entrypoint. Also, there is no need to update the stack mask,
+    // as this runtime call will not trigger a garbage collection.
+    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
+    DCHECK(!ref_reg.Is(sp));
+    DCHECK(!ref_reg.Is(lr));
+    DCHECK(!ref_reg.Is(pc));
+    // IP is used internally by the ReadBarrierMarkRegX entry point
+    // as a temporary, it cannot be the entry point's input/output.
+    DCHECK(!ref_reg.Is(ip));
+    DCHECK(ref_reg.IsRegister()) << ref_reg;
+    // "Compact" slow path, saving two moves.
+    //
+    // Instead of using the standard runtime calling convention (input
+    // and output in R0):
+    //
+    //   R0 <- ref
+    //   R0 <- ReadBarrierMark(R0)
+    //   ref <- R0
+    //
+    // we just use rX (the register containing `ref`) as input and output
+    // of a dedicated entrypoint:
+    //
+    //   rX <- ReadBarrierMarkRegX(rX)
+    //
+    int32_t entry_point_offset =
+        CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg.GetCode());
+    // This runtime call does not require a stack map.
+    arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
+
+    // If the new reference is different from the old reference,
+    // update the field in the holder (`*(obj_ + field_offset_)`).
+    //
+    // Note that this field could also hold a different object, if
+    // another thread had concurrently changed it. In that case, the
+    // LDREX/SUBS/ITNE sequence of instructions in the compare-and-set
+    // (CAS) operation below would abort the CAS, leaving the field
+    // as-is.
+    vixl32::Label done;
+    __ Cmp(temp1_, ref_reg);
+    __ B(eq, &done);
+
+    // Update the the holder's field atomically.  This may fail if
+    // mutator updates before us, but it's OK.  This is achieved
+    // using a strong compare-and-set (CAS) operation with relaxed
+    // memory synchronization ordering, where the expected value is
+    // the old reference and the desired value is the new reference.
+
+    UseScratchRegisterScope temps(arm_codegen->GetVIXLAssembler());
+    // Convenience aliases.
+    vixl32::Register base = obj_;
+    // The UnsafeCASObject intrinsic uses a register pair as field
+    // offset ("long offset"), of which only the low part contains
+    // data.
+    vixl32::Register offset = LowRegisterFrom(field_offset_);
+    vixl32::Register expected = temp1_;
+    vixl32::Register value = ref_reg;
+    vixl32::Register tmp_ptr = temps.Acquire();       // Pointer to actual memory.
+    vixl32::Register tmp = temp2_;                    // Value in memory.
+
+    __ Add(tmp_ptr, base, offset);
+
+    if (kPoisonHeapReferences) {
+      arm_codegen->GetAssembler()->PoisonHeapReference(expected);
+      if (value.Is(expected)) {
+        // Do not poison `value`, as it is the same register as
+        // `expected`, which has just been poisoned.
+      } else {
+        arm_codegen->GetAssembler()->PoisonHeapReference(value);
+      }
+    }
+
+    // do {
+    //   tmp = [r_ptr] - expected;
+    // } while (tmp == 0 && failure([r_ptr] <- r_new_value));
+
+    vixl32::Label loop_head, exit_loop;
+    __ Bind(&loop_head);
+
+    __ Ldrex(tmp, MemOperand(tmp_ptr));
+
+    __ Subs(tmp, tmp, expected);
+
+    {
+      AssemblerAccurateScope aas(arm_codegen->GetVIXLAssembler(),
+                                 2 * kMaxInstructionSizeInBytes,
+                                 CodeBufferCheckScope::kMaximumSize);
+
+      __ it(ne);
+      __ clrex(ne);
+    }
+
+    __ B(ne, &exit_loop);
+
+    __ Strex(tmp, value, MemOperand(tmp_ptr));
+    __ Cmp(tmp, 1);
+    __ B(eq, &loop_head);
+
+    __ Bind(&exit_loop);
+
+    if (kPoisonHeapReferences) {
+      arm_codegen->GetAssembler()->UnpoisonHeapReference(expected);
+      if (value.Is(expected)) {
+        // Do not unpoison `value`, as it is the same register as
+        // `expected`, which has just been unpoisoned.
+      } else {
+        arm_codegen->GetAssembler()->UnpoisonHeapReference(value);
+      }
+    }
+
+    __ Bind(&done);
+    __ B(GetExitLabel());
+  }
+
+ private:
+  // The location (register) of the marked object reference.
+  const Location ref_;
+  // The register containing the object holding the marked object reference field.
+  const vixl32::Register obj_;
+  // The location of the offset of the marked reference field within `obj_`.
+  Location field_offset_;
+
+  const vixl32::Register temp1_;
+  const vixl32::Register temp2_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL);
+};
+
+// Slow path generating a read barrier for a heap reference.
+class ReadBarrierForHeapReferenceSlowPathARMVIXL : public SlowPathCodeARMVIXL {
+ public:
+  ReadBarrierForHeapReferenceSlowPathARMVIXL(HInstruction* instruction,
+                                             Location out,
+                                             Location ref,
+                                             Location obj,
+                                             uint32_t offset,
+                                             Location index)
+      : SlowPathCodeARMVIXL(instruction),
+        out_(out),
+        ref_(ref),
+        obj_(obj),
+        offset_(offset),
+        index_(index) {
+    DCHECK(kEmitCompilerReadBarrier);
+    // If `obj` is equal to `out` or `ref`, it means the initial object
+    // has been overwritten by (or after) the heap object reference load
+    // to be instrumented, e.g.:
+    //
+    //   __ LoadFromOffset(kLoadWord, out, out, offset);
+    //   codegen_->GenerateReadBarrierSlow(instruction, out_loc, out_loc, out_loc, offset);
+    //
+    // In that case, we have lost the information about the original
+    // object, and the emitted read barrier cannot work properly.
+    DCHECK(!obj.Equals(out)) << "obj=" << obj << " out=" << out;
+    DCHECK(!obj.Equals(ref)) << "obj=" << obj << " ref=" << ref;
+  }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
+    LocationSummary* locations = instruction_->GetLocations();
+    vixl32::Register reg_out = RegisterFrom(out_);
+    DCHECK(locations->CanCall());
+    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(reg_out.GetCode()));
+    DCHECK(instruction_->IsInstanceFieldGet() ||
+           instruction_->IsStaticFieldGet() ||
+           instruction_->IsArrayGet() ||
+           instruction_->IsInstanceOf() ||
+           instruction_->IsCheckCast() ||
+           (instruction_->IsInvokeVirtual()) && instruction_->GetLocations()->Intrinsified())
+        << "Unexpected instruction in read barrier for heap reference slow path: "
+        << instruction_->DebugName();
+    // The read barrier instrumentation of object ArrayGet
+    // instructions does not support the HIntermediateAddress
+    // instruction.
+    DCHECK(!(instruction_->IsArrayGet() &&
+             instruction_->AsArrayGet()->GetArray()->IsIntermediateAddress()));
+
+    __ Bind(GetEntryLabel());
+    SaveLiveRegisters(codegen, locations);
+
+    // We may have to change the index's value, but as `index_` is a
+    // constant member (like other "inputs" of this slow path),
+    // introduce a copy of it, `index`.
+    Location index = index_;
+    if (index_.IsValid()) {
+      // Handle `index_` for HArrayGet and UnsafeGetObject/UnsafeGetObjectVolatile intrinsics.
+      if (instruction_->IsArrayGet()) {
+        // Compute the actual memory offset and store it in `index`.
+        vixl32::Register index_reg = RegisterFrom(index_);
+        DCHECK(locations->GetLiveRegisters()->ContainsCoreRegister(index_reg.GetCode()));
+        if (codegen->IsCoreCalleeSaveRegister(index_reg.GetCode())) {
+          // We are about to change the value of `index_reg` (see the
+          // calls to art::arm::Thumb2Assembler::Lsl and
+          // art::arm::Thumb2Assembler::AddConstant below), but it has
+          // not been saved by the previous call to
+          // art::SlowPathCode::SaveLiveRegisters, as it is a
+          // callee-save register --
+          // art::SlowPathCode::SaveLiveRegisters does not consider
+          // callee-save registers, as it has been designed with the
+          // assumption that callee-save registers are supposed to be
+          // handled by the called function.  So, as a callee-save
+          // register, `index_reg` _would_ eventually be saved onto
+          // the stack, but it would be too late: we would have
+          // changed its value earlier.  Therefore, we manually save
+          // it here into another freely available register,
+          // `free_reg`, chosen of course among the caller-save
+          // registers (as a callee-save `free_reg` register would
+          // exhibit the same problem).
+          //
+          // Note we could have requested a temporary register from
+          // the register allocator instead; but we prefer not to, as
+          // this is a slow path, and we know we can find a
+          // caller-save register that is available.
+          vixl32::Register free_reg = FindAvailableCallerSaveRegister(codegen);
+          __ Mov(free_reg, index_reg);
+          index_reg = free_reg;
+          index = LocationFrom(index_reg);
+        } else {
+          // The initial register stored in `index_` has already been
+          // saved in the call to art::SlowPathCode::SaveLiveRegisters
+          // (as it is not a callee-save register), so we can freely
+          // use it.
+        }
+        // Shifting the index value contained in `index_reg` by the scale
+        // factor (2) cannot overflow in practice, as the runtime is
+        // unable to allocate object arrays with a size larger than
+        // 2^26 - 1 (that is, 2^28 - 4 bytes).
+        __ Lsl(index_reg, index_reg, TIMES_4);
+        static_assert(
+            sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
+            "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
+        __ Add(index_reg, index_reg, offset_);
+      } else {
+        // In the case of the UnsafeGetObject/UnsafeGetObjectVolatile
+        // intrinsics, `index_` is not shifted by a scale factor of 2
+        // (as in the case of ArrayGet), as it is actually an offset
+        // to an object field within an object.
+        DCHECK(instruction_->IsInvoke()) << instruction_->DebugName();
+        DCHECK(instruction_->GetLocations()->Intrinsified());
+        DCHECK((instruction_->AsInvoke()->GetIntrinsic() == Intrinsics::kUnsafeGetObject) ||
+               (instruction_->AsInvoke()->GetIntrinsic() == Intrinsics::kUnsafeGetObjectVolatile))
+            << instruction_->AsInvoke()->GetIntrinsic();
+        DCHECK_EQ(offset_, 0U);
+        DCHECK(index_.IsRegisterPair());
+        // UnsafeGet's offset location is a register pair, the low
+        // part contains the correct offset.
+        index = index_.ToLow();
+      }
+    }
+
+    // We're moving two or three locations to locations that could
+    // overlap, so we need a parallel move resolver.
+    InvokeRuntimeCallingConventionARMVIXL calling_convention;
+    HParallelMove parallel_move(codegen->GetGraph()->GetArena());
+    parallel_move.AddMove(ref_,
+                          LocationFrom(calling_convention.GetRegisterAt(0)),
+                          Primitive::kPrimNot,
+                          nullptr);
+    parallel_move.AddMove(obj_,
+                          LocationFrom(calling_convention.GetRegisterAt(1)),
+                          Primitive::kPrimNot,
+                          nullptr);
+    if (index.IsValid()) {
+      parallel_move.AddMove(index,
+                            LocationFrom(calling_convention.GetRegisterAt(2)),
+                            Primitive::kPrimInt,
+                            nullptr);
+      codegen->GetMoveResolver()->EmitNativeCode(&parallel_move);
+    } else {
+      codegen->GetMoveResolver()->EmitNativeCode(&parallel_move);
+      __ Mov(calling_convention.GetRegisterAt(2), offset_);
+    }
+    arm_codegen->InvokeRuntime(kQuickReadBarrierSlow, instruction_, instruction_->GetDexPc(), this);
+    CheckEntrypointTypes<
+        kQuickReadBarrierSlow, mirror::Object*, mirror::Object*, mirror::Object*, uint32_t>();
+    arm_codegen->Move32(out_, LocationFrom(r0));
+
+    RestoreLiveRegisters(codegen, locations);
+    __ B(GetExitLabel());
+  }
+
+  const char* GetDescription() const OVERRIDE {
+    return "ReadBarrierForHeapReferenceSlowPathARMVIXL";
+  }
+
+ private:
+  vixl32::Register FindAvailableCallerSaveRegister(CodeGenerator* codegen) {
+    uint32_t ref = RegisterFrom(ref_).GetCode();
+    uint32_t obj = RegisterFrom(obj_).GetCode();
+    for (uint32_t i = 0, e = codegen->GetNumberOfCoreRegisters(); i < e; ++i) {
+      if (i != ref && i != obj && !codegen->IsCoreCalleeSaveRegister(i)) {
+        return vixl32::Register(i);
+      }
+    }
+    // We shall never fail to find a free caller-save register, as
+    // there are more than two core caller-save registers on ARM
+    // (meaning it is possible to find one which is different from
+    // `ref` and `obj`).
+    DCHECK_GT(codegen->GetNumberOfCoreCallerSaveRegisters(), 2u);
+    LOG(FATAL) << "Could not find a free caller-save register";
+    UNREACHABLE();
+  }
+
+  const Location out_;
+  const Location ref_;
+  const Location obj_;
+  const uint32_t offset_;
+  // An additional location containing an index to an array.
+  // Only used for HArrayGet and the UnsafeGetObject &
+  // UnsafeGetObjectVolatile intrinsics.
+  const Location index_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadBarrierForHeapReferenceSlowPathARMVIXL);
+};
+
+// Slow path generating a read barrier for a GC root.
+class ReadBarrierForRootSlowPathARMVIXL : public SlowPathCodeARMVIXL {
+ public:
+  ReadBarrierForRootSlowPathARMVIXL(HInstruction* instruction, Location out, Location root)
+      : SlowPathCodeARMVIXL(instruction), out_(out), root_(root) {
+    DCHECK(kEmitCompilerReadBarrier);
+  }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    LocationSummary* locations = instruction_->GetLocations();
+    vixl32::Register reg_out = RegisterFrom(out_);
+    DCHECK(locations->CanCall());
+    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(reg_out.GetCode()));
+    DCHECK(instruction_->IsLoadClass() || instruction_->IsLoadString())
+        << "Unexpected instruction in read barrier for GC root slow path: "
+        << instruction_->DebugName();
+
+    __ Bind(GetEntryLabel());
+    SaveLiveRegisters(codegen, locations);
+
+    InvokeRuntimeCallingConventionARMVIXL calling_convention;
+    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
+    arm_codegen->Move32(LocationFrom(calling_convention.GetRegisterAt(0)), root_);
+    arm_codegen->InvokeRuntime(kQuickReadBarrierForRootSlow,
+                               instruction_,
+                               instruction_->GetDexPc(),
+                               this);
+    CheckEntrypointTypes<kQuickReadBarrierForRootSlow, mirror::Object*, GcRoot<mirror::Object>*>();
+    arm_codegen->Move32(out_, LocationFrom(r0));
+
+    RestoreLiveRegisters(codegen, locations);
+    __ B(GetExitLabel());
+  }
+
+  const char* GetDescription() const OVERRIDE { return "ReadBarrierForRootSlowPathARMVIXL"; }
+
+ private:
+  const Location out_;
+  const Location root_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadBarrierForRootSlowPathARMVIXL);
+};
 
 inline vixl32::Condition ARMCondition(IfCondition cond) {
   switch (cond) {
@@ -4021,7 +4524,14 @@
     case Primitive::kPrimNot: {
       // /* HeapReference<Object> */ out = *(base + offset)
       if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
-        TODO_VIXL32(FATAL);
+        Location temp_loc = locations->GetTemp(0);
+        // Note that a potential implicit null check is handled in this
+        // CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier call.
+        codegen_->GenerateFieldLoadWithBakerReadBarrier(
+            instruction, out, base, offset, temp_loc, /* needs_null_check */ true);
+        if (is_volatile) {
+          codegen_->GenerateMemoryBarrier(MemBarrierKind::kLoadAny);
+        }
       } else {
         GetAssembler()->LoadFromOffset(kLoadWord, RegisterFrom(out), base, offset);
         codegen_->MaybeRecordImplicitNullCheck(instruction);
@@ -4348,7 +4858,7 @@
                                                        LocationSummary::kCallOnSlowPath :
                                                        LocationSummary::kNoCall);
   if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
-    TODO_VIXL32(FATAL);
+    locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
   }
   locations->SetInAt(0, Location::RequiresRegister());
   locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
@@ -4372,7 +4882,6 @@
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitArrayGet(HArrayGet* instruction) {
-  UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
   LocationSummary* locations = instruction->GetLocations();
   Location obj_loc = locations->InAt(0);
   vixl32::Register obj = InputRegisterAt(instruction, 0);
@@ -4384,8 +4893,6 @@
                                         instruction->IsStringCharAt();
   HInstruction* array_instr = instruction->GetArray();
   bool has_intermediate_address = array_instr->IsIntermediateAddress();
-  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
-  DCHECK(!(has_intermediate_address && kEmitCompilerReadBarrier));
 
   switch (type) {
     case Primitive::kPrimBoolean:
@@ -4426,6 +4933,7 @@
           GetAssembler()->LoadFromOffset(load_type, RegisterFrom(out_loc), obj, full_offset);
         }
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
 
         if (has_intermediate_address) {
@@ -4454,19 +4962,27 @@
         } else {
           codegen_->LoadFromShiftedRegOffset(type, out_loc, temp, RegisterFrom(index));
         }
-        temps.Release(temp);
       }
       break;
     }
 
     case Primitive::kPrimNot: {
+      // The read barrier instrumentation of object ArrayGet
+      // instructions does not support the HIntermediateAddress
+      // instruction.
+      DCHECK(!(has_intermediate_address && kEmitCompilerReadBarrier));
+
       static_assert(
           sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
           "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
       // /* HeapReference<Object> */ out =
       //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
       if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
-        TODO_VIXL32(FATAL);
+        Location temp = locations->GetTemp(0);
+        // Note that a potential implicit null check is handled in this
+        // CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier call.
+        codegen_->GenerateArrayLoadWithBakerReadBarrier(
+            instruction, out_loc, obj, data_offset, index, temp, /* needs_null_check */ true);
       } else {
         vixl32::Register out = OutputRegister(instruction);
         if (index.IsConstant()) {
@@ -4484,6 +5000,7 @@
           // reference, if heap poisoning is enabled).
           codegen_->MaybeGenerateReadBarrierSlow(instruction, out_loc, out_loc, obj_loc, offset);
         } else {
+          UseScratchRegisterScope temps(GetVIXLAssembler());
           vixl32::Register temp = temps.Acquire();
 
           if (has_intermediate_address) {
@@ -4499,7 +5016,7 @@
             __ Add(temp, obj, data_offset);
           }
           codegen_->LoadFromShiftedRegOffset(type, out_loc, temp, RegisterFrom(index));
-          temps.Release(temp);
+          temps.Close();
           // TODO(VIXL): Use a scope to ensure that we record the pc position immediately after the
           // load instruction. Practically, everything is fine because the helper and VIXL, at the
           // time of writing, do generate the store instruction last.
@@ -4520,10 +5037,10 @@
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
         GetAssembler()->LoadFromOffset(kLoadWordPair, LowRegisterFrom(out_loc), obj, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, obj, Operand(RegisterFrom(index), vixl32::LSL, TIMES_8));
         GetAssembler()->LoadFromOffset(kLoadWordPair, LowRegisterFrom(out_loc), temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4534,10 +5051,10 @@
         size_t offset = (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
         GetAssembler()->LoadSFromOffset(out, obj, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, obj, Operand(RegisterFrom(index), vixl32::LSL, TIMES_4));
         GetAssembler()->LoadSFromOffset(out, temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4547,10 +5064,10 @@
         size_t offset = (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
         GetAssembler()->LoadDFromOffset(DRegisterFrom(out_loc), obj, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, obj, Operand(RegisterFrom(index), vixl32::LSL, TIMES_8));
         GetAssembler()->LoadDFromOffset(DRegisterFrom(out_loc), temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4598,7 +5115,6 @@
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitArraySet(HArraySet* instruction) {
-  UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
   LocationSummary* locations = instruction->GetLocations();
   vixl32::Register array = InputRegisterAt(instruction, 0);
   Location index = locations->InAt(1);
@@ -4611,8 +5127,6 @@
   Location value_loc = locations->InAt(2);
   HInstruction* array_instr = instruction->GetArray();
   bool has_intermediate_address = array_instr->IsIntermediateAddress();
-  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
-  DCHECK(!(has_intermediate_address && kEmitCompilerReadBarrier));
 
   switch (value_type) {
     case Primitive::kPrimBoolean:
@@ -4627,6 +5141,7 @@
         StoreOperandType store_type = GetStoreOperandType(value_type);
         GetAssembler()->StoreToOffset(store_type, RegisterFrom(value_loc), array, full_offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
 
         if (has_intermediate_address) {
@@ -4642,7 +5157,6 @@
           __ Add(temp, array, data_offset);
         }
         codegen_->StoreToShiftedRegOffset(value_type, value_loc, temp, RegisterFrom(index));
-        temps.Release(temp);
       }
       break;
     }
@@ -4661,10 +5175,10 @@
           GetAssembler()->StoreToOffset(kStoreWord, value, array, offset);
         } else {
           DCHECK(index.IsRegister()) << index;
+          UseScratchRegisterScope temps(GetVIXLAssembler());
           vixl32::Register temp = temps.Acquire();
           __ Add(temp, array, data_offset);
           codegen_->StoreToShiftedRegOffset(value_type, value_loc, temp, RegisterFrom(index));
-          temps.Release(temp);
         }
         // TODO(VIXL): Use a scope to ensure we record the pc info immediately after the preceding
         // store instruction.
@@ -4697,10 +5211,10 @@
             GetAssembler()->StoreToOffset(kStoreWord, value, array, offset);
           } else {
             DCHECK(index.IsRegister()) << index;
+            UseScratchRegisterScope temps(GetVIXLAssembler());
             vixl32::Register temp = temps.Acquire();
             __ Add(temp, array, data_offset);
             codegen_->StoreToShiftedRegOffset(value_type, value_loc, temp, RegisterFrom(index));
-            temps.Release(temp);
           }
           // TODO(VIXL): Use a scope to ensure we record the pc info immediately after the preceding
           // store instruction.
@@ -4772,13 +5286,13 @@
       } else {
         DCHECK(index.IsRegister()) << index;
 
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, array, data_offset);
         codegen_->StoreToShiftedRegOffset(value_type,
                                           LocationFrom(source),
                                           temp,
                                           RegisterFrom(index));
-        temps.Release(temp);
       }
 
       if (!may_need_runtime_call_for_type_check) {
@@ -4807,10 +5321,10 @@
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
         GetAssembler()->StoreToOffset(kStoreWordPair, LowRegisterFrom(value), array, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, array, Operand(RegisterFrom(index), vixl32::LSL, TIMES_8));
         GetAssembler()->StoreToOffset(kStoreWordPair, LowRegisterFrom(value), temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4822,10 +5336,10 @@
         size_t offset = (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
         GetAssembler()->StoreSToOffset(SRegisterFrom(value), array, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, array, Operand(RegisterFrom(index), vixl32::LSL, TIMES_4));
         GetAssembler()->StoreSToOffset(SRegisterFrom(value), temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4837,10 +5351,10 @@
         size_t offset = (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
         GetAssembler()->StoreDToOffset(DRegisterFrom(value), array, offset);
       } else {
+        UseScratchRegisterScope temps(GetVIXLAssembler());
         vixl32::Register temp = temps.Acquire();
         __ Add(temp, array, Operand(RegisterFrom(index), vixl32::LSL, TIMES_8));
         GetAssembler()->StoreDToOffset(DRegisterFrom(value), temp, data_offset);
-        temps.Release(temp);
       }
       break;
     }
@@ -4883,8 +5397,6 @@
 }
 
 void LocationsBuilderARMVIXL::VisitIntermediateAddress(HIntermediateAddress* instruction) {
-  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
-  DCHECK(!kEmitCompilerReadBarrier);
   LocationSummary* locations =
       new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
 
@@ -4898,9 +5410,6 @@
   vixl32::Register first = InputRegisterAt(instruction, 0);
   Location second = instruction->GetLocations()->InAt(1);
 
-  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
-  DCHECK(!kEmitCompilerReadBarrier);
-
   if (second.IsRegister()) {
     __ Add(out, first, RegisterFrom(second));
   } else {
@@ -4992,7 +5501,7 @@
     DCHECK_EQ(slow_path->GetSuccessor(), successor);
   }
 
-  UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
+  UseScratchRegisterScope temps(GetVIXLAssembler());
   vixl32::Register temp = temps.Acquire();
   GetAssembler()->LoadFromOffset(
       kLoadUnsignedHalfword, temp, tr, Thread::ThreadFlagsOffset<kArmPointerSize>().Int32Value());
@@ -5303,7 +5812,7 @@
       : LocationSummary::kNoCall;
   LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(cls, call_kind);
   if (kUseBakerReadBarrier && requires_read_barrier && !cls->NeedsEnvironment()) {
-      TODO_VIXL32(FATAL);
+    locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
   }
 
   HLoadClass::LoadKind load_kind = cls->GetLoadKind();
@@ -6350,14 +6859,30 @@
 }
 
 void InstructionCodeGeneratorARMVIXL::GenerateReferenceLoadOneRegister(
-    HInstruction* instruction ATTRIBUTE_UNUSED,
+    HInstruction* instruction,
     Location out,
     uint32_t offset,
-    Location maybe_temp ATTRIBUTE_UNUSED,
-    ReadBarrierOption read_barrier_option ATTRIBUTE_UNUSED) {
+    Location maybe_temp,
+    ReadBarrierOption read_barrier_option) {
   vixl32::Register out_reg = RegisterFrom(out);
-  if (kEmitCompilerReadBarrier) {
-    TODO_VIXL32(FATAL);
+  if (read_barrier_option == kWithReadBarrier) {
+    CHECK(kEmitCompilerReadBarrier);
+    DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+    if (kUseBakerReadBarrier) {
+      // Load with fast path based Baker's read barrier.
+      // /* HeapReference<Object> */ out = *(out + offset)
+      codegen_->GenerateFieldLoadWithBakerReadBarrier(
+          instruction, out, out_reg, offset, maybe_temp, /* needs_null_check */ false);
+    } else {
+      // Load with slow path based read barrier.
+      // Save the value of `out` into `maybe_temp` before overwriting it
+      // in the following move operation, as we will need it for the
+      // read barrier below.
+      __ Mov(RegisterFrom(maybe_temp), out_reg);
+      // /* HeapReference<Object> */ out = *(out + offset)
+      GetAssembler()->LoadFromOffset(kLoadWord, out_reg, out_reg, offset);
+      codegen_->GenerateReadBarrierSlow(instruction, out, out, maybe_temp, offset);
+    }
   } else {
     // Plain load with no read barrier.
     // /* HeapReference<Object> */ out = *(out + offset)
@@ -6367,16 +6892,28 @@
 }
 
 void InstructionCodeGeneratorARMVIXL::GenerateReferenceLoadTwoRegisters(
-    HInstruction* instruction ATTRIBUTE_UNUSED,
+    HInstruction* instruction,
     Location out,
     Location obj,
     uint32_t offset,
-    Location maybe_temp ATTRIBUTE_UNUSED,
-    ReadBarrierOption read_barrier_option ATTRIBUTE_UNUSED) {
+    Location maybe_temp,
+    ReadBarrierOption read_barrier_option) {
   vixl32::Register out_reg = RegisterFrom(out);
   vixl32::Register obj_reg = RegisterFrom(obj);
-  if (kEmitCompilerReadBarrier) {
-    TODO_VIXL32(FATAL);
+  if (read_barrier_option == kWithReadBarrier) {
+    CHECK(kEmitCompilerReadBarrier);
+    if (kUseBakerReadBarrier) {
+      DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+      // Load with fast path based Baker's read barrier.
+      // /* HeapReference<Object> */ out = *(obj + offset)
+      codegen_->GenerateFieldLoadWithBakerReadBarrier(
+          instruction, out, obj_reg, offset, maybe_temp, /* needs_null_check */ false);
+    } else {
+      // Load with slow path based read barrier.
+      // /* HeapReference<Object> */ out = *(obj + offset)
+      GetAssembler()->LoadFromOffset(kLoadWord, out_reg, obj_reg, offset);
+      codegen_->GenerateReadBarrierSlow(instruction, out, out, obj, offset);
+    }
   } else {
     // Plain load with no read barrier.
     // /* HeapReference<Object> */ out = *(obj + offset)
@@ -6386,14 +6923,61 @@
 }
 
 void InstructionCodeGeneratorARMVIXL::GenerateGcRootFieldLoad(
-    HInstruction* instruction ATTRIBUTE_UNUSED,
+    HInstruction* instruction,
     Location root,
     vixl32::Register obj,
     uint32_t offset,
     ReadBarrierOption read_barrier_option) {
   vixl32::Register root_reg = RegisterFrom(root);
   if (read_barrier_option == kWithReadBarrier) {
-    TODO_VIXL32(FATAL);
+    DCHECK(kEmitCompilerReadBarrier);
+    if (kUseBakerReadBarrier) {
+      // Fast path implementation of art::ReadBarrier::BarrierForRoot when
+      // Baker's read barrier are used:
+      //
+      //   root = obj.field;
+      //   temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+      //   if (temp != null) {
+      //     root = temp(root)
+      //   }
+
+      // /* GcRoot<mirror::Object> */ root = *(obj + offset)
+      GetAssembler()->LoadFromOffset(kLoadWord, root_reg, obj, offset);
+      static_assert(
+          sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
+          "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
+          "have different sizes.");
+      static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
+                    "art::mirror::CompressedReference<mirror::Object> and int32_t "
+                    "have different sizes.");
+
+      // Slow path marking the GC root `root`.
+      Location temp = LocationFrom(lr);
+      SlowPathCodeARMVIXL* slow_path =
+          new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathARMVIXL(
+              instruction,
+              root,
+              /*entrypoint*/ temp);
+      codegen_->AddSlowPath(slow_path);
+
+      // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+      const int32_t entry_point_offset =
+          CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(root.reg());
+      // Loading the entrypoint does not require a load acquire since it is only changed when
+      // threads are suspended or running a checkpoint.
+      GetAssembler()->LoadFromOffset(kLoadWord, RegisterFrom(temp), tr, entry_point_offset);
+      // The entrypoint is null when the GC is not marking, this prevents one load compared to
+      // checking GetIsGcMarking.
+      __ CompareAndBranchIfNonZero(RegisterFrom(temp), slow_path->GetEntryLabel());
+      __ Bind(slow_path->GetExitLabel());
+    } else {
+      // GC root loaded through a slow path for read barriers other
+      // than Baker's.
+      // /* GcRoot<mirror::Object>* */ root = obj + offset
+      __ Add(root_reg, obj, offset);
+      // /* mirror::Object* */ root = root->Read()
+      codegen_->GenerateReadBarrierForRootSlow(instruction, root, root);
+    }
   } else {
     // Plain GC root load with no read barrier.
     // /* GcRoot<mirror::Object> */ root = *(obj + offset)
@@ -6403,53 +6987,217 @@
   }
 }
 
-void CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier(
-    HInstruction* instruction ATTRIBUTE_UNUSED,
-    Location ref ATTRIBUTE_UNUSED,
-    vixl::aarch32::Register obj ATTRIBUTE_UNUSED,
-    uint32_t offset ATTRIBUTE_UNUSED,
-    Location temp ATTRIBUTE_UNUSED,
-    bool needs_null_check ATTRIBUTE_UNUSED) {
-  TODO_VIXL32(FATAL);
+void CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
+                                                                 Location ref,
+                                                                 vixl32::Register obj,
+                                                                 uint32_t offset,
+                                                                 Location temp,
+                                                                 bool needs_null_check) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // /* HeapReference<Object> */ ref = *(obj + offset)
+  Location no_index = Location::NoLocation();
+  ScaleFactor no_scale_factor = TIMES_1;
+  GenerateReferenceLoadWithBakerReadBarrier(
+      instruction, ref, obj, offset, no_index, no_scale_factor, temp, needs_null_check);
 }
 
-void CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier(
-    HInstruction* instruction ATTRIBUTE_UNUSED,
-    Location ref ATTRIBUTE_UNUSED,
-    vixl::aarch32::Register obj ATTRIBUTE_UNUSED,
-    uint32_t offset ATTRIBUTE_UNUSED,
-    Location index ATTRIBUTE_UNUSED,
-    ScaleFactor scale_factor ATTRIBUTE_UNUSED,
-    Location temp ATTRIBUTE_UNUSED,
-    bool needs_null_check ATTRIBUTE_UNUSED,
-    bool always_update_field ATTRIBUTE_UNUSED,
-    vixl::aarch32::Register* temp2 ATTRIBUTE_UNUSED) {
-  TODO_VIXL32(FATAL);
+void CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
+                                                                 Location ref,
+                                                                 vixl32::Register obj,
+                                                                 uint32_t data_offset,
+                                                                 Location index,
+                                                                 Location temp,
+                                                                 bool needs_null_check) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  static_assert(
+      sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
+      "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
+  // /* HeapReference<Object> */ ref =
+  //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
+  ScaleFactor scale_factor = TIMES_4;
+  GenerateReferenceLoadWithBakerReadBarrier(
+      instruction, ref, obj, data_offset, index, scale_factor, temp, needs_null_check);
 }
 
-void CodeGeneratorARMVIXL::GenerateReadBarrierSlow(HInstruction* instruction ATTRIBUTE_UNUSED,
-                                                   Location out ATTRIBUTE_UNUSED,
-                                                   Location ref ATTRIBUTE_UNUSED,
-                                                   Location obj ATTRIBUTE_UNUSED,
-                                                   uint32_t offset ATTRIBUTE_UNUSED,
-                                                   Location index ATTRIBUTE_UNUSED) {
-  TODO_VIXL32(FATAL);
+void CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier(HInstruction* instruction,
+                                                                     Location ref,
+                                                                     vixl32::Register obj,
+                                                                     uint32_t offset,
+                                                                     Location index,
+                                                                     ScaleFactor scale_factor,
+                                                                     Location temp,
+                                                                     bool needs_null_check,
+                                                                     bool always_update_field,
+                                                                     vixl32::Register* temp2) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // In slow path based read barriers, the read barrier call is
+  // inserted after the original load. However, in fast path based
+  // Baker's read barriers, we need to perform the load of
+  // mirror::Object::monitor_ *before* the original reference load.
+  // This load-load ordering is required by the read barrier.
+  // The fast path/slow path (for Baker's algorithm) should look like:
+  //
+  //   uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
+  //   lfence;  // Load fence or artificial data dependency to prevent load-load reordering
+  //   HeapReference<Object> ref = *src;  // Original reference load.
+  //   bool is_gray = (rb_state == ReadBarrier::GrayState());
+  //   if (is_gray) {
+  //     ref = ReadBarrier::Mark(ref);  // Performed by runtime entrypoint slow path.
+  //   }
+  //
+  // Note: the original implementation in ReadBarrier::Barrier is
+  // slightly more complex as it performs additional checks that we do
+  // not do here for performance reasons.
+
+  vixl32::Register ref_reg = RegisterFrom(ref);
+  vixl32::Register temp_reg = RegisterFrom(temp);
+  uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
+
+  // /* int32_t */ monitor = obj->monitor_
+  GetAssembler()->LoadFromOffset(kLoadWord, temp_reg, obj, monitor_offset);
+  if (needs_null_check) {
+    MaybeRecordImplicitNullCheck(instruction);
+  }
+  // /* LockWord */ lock_word = LockWord(monitor)
+  static_assert(sizeof(LockWord) == sizeof(int32_t),
+                "art::LockWord and int32_t have different sizes.");
+
+  // Introduce a dependency on the lock_word including the rb_state,
+  // which shall prevent load-load reordering without using
+  // a memory barrier (which would be more expensive).
+  // `obj` is unchanged by this operation, but its value now depends
+  // on `temp_reg`.
+  __ Add(obj, obj, Operand(temp_reg, ShiftType::LSR, 32));
+
+  // The actual reference load.
+  if (index.IsValid()) {
+    // Load types involving an "index": ArrayGet,
+    // UnsafeGetObject/UnsafeGetObjectVolatile and UnsafeCASObject
+    // intrinsics.
+    // /* HeapReference<Object> */ ref = *(obj + offset + (index << scale_factor))
+    if (index.IsConstant()) {
+      size_t computed_offset =
+          (Int32ConstantFrom(index) << scale_factor) + offset;
+      GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, obj, computed_offset);
+    } else {
+      // Handle the special case of the
+      // UnsafeGetObject/UnsafeGetObjectVolatile and UnsafeCASObject
+      // intrinsics, which use a register pair as index ("long
+      // offset"), of which only the low part contains data.
+      vixl32::Register index_reg = index.IsRegisterPair()
+          ? LowRegisterFrom(index)
+          : RegisterFrom(index);
+      UseScratchRegisterScope temps(GetVIXLAssembler());
+      const vixl32::Register temp3 = temps.Acquire();
+      __ Add(temp3, obj, Operand(index_reg, ShiftType::LSL, scale_factor));
+      GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, temp3, offset);
+    }
+  } else {
+    // /* HeapReference<Object> */ ref = *(obj + offset)
+    GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, obj, offset);
+  }
+
+  // Object* ref = ref_addr->AsMirrorPtr()
+  GetAssembler()->MaybeUnpoisonHeapReference(ref_reg);
+
+  // Slow path marking the object `ref` when it is gray.
+  SlowPathCodeARMVIXL* slow_path;
+  if (always_update_field) {
+    DCHECK(temp2 != nullptr);
+    // ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL only supports address
+    // of the form `obj + field_offset`, where `obj` is a register and
+    // `field_offset` is a register pair (of which only the lower half
+    // is used). Thus `offset` and `scale_factor` above are expected
+    // to be null in this code path.
+    DCHECK_EQ(offset, 0u);
+    DCHECK_EQ(scale_factor, ScaleFactor::TIMES_1);
+    slow_path = new (GetGraph()->GetArena()) ReadBarrierMarkAndUpdateFieldSlowPathARMVIXL(
+        instruction, ref, obj, /* field_offset */ index, temp_reg, *temp2);
+  } else {
+    slow_path = new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathARMVIXL(instruction, ref);
+  }
+  AddSlowPath(slow_path);
+
+  // if (rb_state == ReadBarrier::GrayState())
+  //   ref = ReadBarrier::Mark(ref);
+  // Given the numeric representation, it's enough to check the low bit of the
+  // rb_state. We do that by shifting the bit out of the lock word with LSRS
+  // which can be a 16-bit instruction unlike the TST immediate.
+  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
+  __ Lsrs(temp_reg, temp_reg, LockWord::kReadBarrierStateShift + 1);
+  __ B(cs, slow_path->GetEntryLabel());  // Carry flag is the last bit shifted out by LSRS.
+  __ Bind(slow_path->GetExitLabel());
 }
 
-void CodeGeneratorARMVIXL::MaybeGenerateReadBarrierSlow(HInstruction* instruction ATTRIBUTE_UNUSED,
+void CodeGeneratorARMVIXL::GenerateReadBarrierSlow(HInstruction* instruction,
+                                                   Location out,
+                                                   Location ref,
+                                                   Location obj,
+                                                   uint32_t offset,
+                                                   Location index) {
+  DCHECK(kEmitCompilerReadBarrier);
+
+  // Insert a slow path based read barrier *after* the reference load.
+  //
+  // If heap poisoning is enabled, the unpoisoning of the loaded
+  // reference will be carried out by the runtime within the slow
+  // path.
+  //
+  // Note that `ref` currently does not get unpoisoned (when heap
+  // poisoning is enabled), which is alright as the `ref` argument is
+  // not used by the artReadBarrierSlow entry point.
+  //
+  // TODO: Unpoison `ref` when it is used by artReadBarrierSlow.
+  SlowPathCodeARMVIXL* slow_path = new (GetGraph()->GetArena())
+      ReadBarrierForHeapReferenceSlowPathARMVIXL(instruction, out, ref, obj, offset, index);
+  AddSlowPath(slow_path);
+
+  __ B(slow_path->GetEntryLabel());
+  __ Bind(slow_path->GetExitLabel());
+}
+
+void CodeGeneratorARMVIXL::MaybeGenerateReadBarrierSlow(HInstruction* instruction,
                                                         Location out,
-                                                        Location ref ATTRIBUTE_UNUSED,
-                                                        Location obj ATTRIBUTE_UNUSED,
-                                                        uint32_t offset ATTRIBUTE_UNUSED,
-                                                        Location index ATTRIBUTE_UNUSED) {
+                                                        Location ref,
+                                                        Location obj,
+                                                        uint32_t offset,
+                                                        Location index) {
   if (kEmitCompilerReadBarrier) {
+    // Baker's read barriers shall be handled by the fast path
+    // (CodeGeneratorARM::GenerateReferenceLoadWithBakerReadBarrier).
     DCHECK(!kUseBakerReadBarrier);
-    TODO_VIXL32(FATAL);
+    // If heap poisoning is enabled, unpoisoning will be taken care of
+    // by the runtime within the slow path.
+    GenerateReadBarrierSlow(instruction, out, ref, obj, offset, index);
   } else if (kPoisonHeapReferences) {
     GetAssembler()->UnpoisonHeapReference(RegisterFrom(out));
   }
 }
 
+void CodeGeneratorARMVIXL::GenerateReadBarrierForRootSlow(HInstruction* instruction,
+                                                          Location out,
+                                                          Location root) {
+  DCHECK(kEmitCompilerReadBarrier);
+
+  // Insert a slow path based read barrier *after* the GC root load.
+  //
+  // Note that GC roots are not affected by heap poisoning, so we do
+  // not need to do anything special for this here.
+  SlowPathCodeARMVIXL* slow_path =
+      new (GetGraph()->GetArena()) ReadBarrierForRootSlowPathARMVIXL(instruction, out, root);
+  AddSlowPath(slow_path);
+
+  __ B(slow_path->GetEntryLabel());
+  __ Bind(slow_path->GetExitLabel());
+}
+
 // Check if the desired_dispatch_info is supported. If it is, return it,
 // otherwise return a fall-back info that should be used instead.
 HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARMVIXL::GetSupportedInvokeStaticOrDirectDispatch(
@@ -6819,7 +7567,7 @@
   if (num_entries <= kPackedSwitchCompareJumpThreshold ||
       !codegen_->GetAssembler()->GetVIXLAssembler()->IsUsingT32()) {
     // Create a series of compare/jumps.
-    UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
+    UseScratchRegisterScope temps(GetVIXLAssembler());
     vixl32::Register temp_reg = temps.Acquire();
     // Note: It is fine for the below AddConstantSetFlags() using IP register to temporarily store
     // the immediate, because IP is used as the destination register. For the other
@@ -6867,7 +7615,7 @@
     __ Cmp(key_reg, num_entries - 1);
     __ B(hi, codegen_->GetLabelOf(default_block));
 
-    UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
+    UseScratchRegisterScope temps(GetVIXLAssembler());
     vixl32::Register jump_offset = temps.Acquire();
 
     // Load jump offset from the table.
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index b7ba8dd..5ec3da4 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -576,7 +576,15 @@
                                              uint32_t offset,
                                              Location temp,
                                              bool needs_null_check);
-
+  // Fast path implementation of ReadBarrier::Barrier for a heap
+  // reference array load when Baker's read barriers are used.
+  void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
+                                             Location ref,
+                                             vixl::aarch32::Register obj,
+                                             uint32_t data_offset,
+                                             Location index,
+                                             Location temp,
+                                             bool needs_null_check);
   // Factored implementation, used by GenerateFieldLoadWithBakerReadBarrier,
   // GenerateArrayLoadWithBakerReadBarrier and some intrinsics.
   //
@@ -634,6 +642,19 @@
                                     uint32_t offset,
                                     Location index = Location::NoLocation());
 
+  // Generate a read barrier for a GC root within `instruction` using
+  // a slow path.
+  //
+  // A read barrier for an object reference GC root is implemented as
+  // a call to the artReadBarrierForRootSlow runtime entry point,
+  // which is passed the value in location `root`:
+  //
+  //   mirror::Object* artReadBarrierForRootSlow(GcRoot<mirror::Object>* root);
+  //
+  // The `out` location contains the value returned by
+  // artReadBarrierForRootSlow.
+  void GenerateReadBarrierForRootSlow(HInstruction* instruction, Location out, Location root);
+
   void GenerateNop() OVERRIDE;
 
   void GenerateImplicitNullCheck(HNullCheck* instruction) OVERRIDE;
diff --git a/compiler/optimizing/induction_var_analysis.cc b/compiler/optimizing/induction_var_analysis.cc
index 1561502..c240c67 100644
--- a/compiler/optimizing/induction_var_analysis.cc
+++ b/compiler/optimizing/induction_var_analysis.cc
@@ -296,21 +296,22 @@
       update = SolveAddSub(
           loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kSub, true);
     } else if (instruction->IsMul()) {
-      update = SolveGeo(
+      update = SolveOp(
           loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kMul);
     } else if (instruction->IsDiv()) {
-      update = SolveGeo(
+      update = SolveOp(
           loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kDiv);
     } else if (instruction->IsRem()) {
-      update = SolveGeo(
-          loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kNop);
+      update = SolveOp(
+          loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kRem);
     } else if (instruction->IsShl()) {
       HInstruction* mulc = GetMultConstantForShift(loop, instruction);
       if (mulc != nullptr) {
-        update = SolveGeo(loop, phi, instruction, instruction->InputAt(0), mulc, kMul);
+        update = SolveOp(loop, phi, instruction, instruction->InputAt(0), mulc, kMul);
       }
     } else if (instruction->IsXor()) {
-      update = SolveXor(loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1));
+      update = SolveOp(
+          loop, phi, instruction, instruction->InputAt(0), instruction->InputAt(1), kXor);
     } else if (instruction->IsEqual()) {
       update = SolveTest(loop, phi, instruction, 0);
     } else if (instruction->IsNotEqual()) {
@@ -334,6 +335,7 @@
         FALLTHROUGH_INTENDED;
       case kPolynomial:
       case kGeometric:
+      case kWrapAround:
         // Classify first phi and then the rest of the cycle "on-demand".
         // Statements are scanned in order.
         AssignInfo(loop, phi, induction);
@@ -402,14 +404,15 @@
                                                                             InductionOp op) {
   // Transfer over an addition or subtraction: any invariant, linear, polynomial, geometric,
   // wrap-around, or periodic can be combined with an invariant to yield a similar result.
-  // Even two linear inputs can be combined. Other combinations fail.
+  // Two linear or two polynomial inputs can be combined too. Other combinations fail.
   if (a != nullptr && b != nullptr) {
     type_ = Narrowest(type_, Narrowest(a->type, b->type));
     if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
       return CreateInvariantOp(op, a, b);
-    } else if (a->induction_class == kLinear && b->induction_class == kLinear) {
-      return CreateInduction(kLinear,
-                             kNop,
+    } else if ((a->induction_class == kLinear && b->induction_class == kLinear) ||
+               (a->induction_class == kPolynomial && b->induction_class == kPolynomial)) {
+      return CreateInduction(a->induction_class,
+                             a->operation,
                              TransferAddSub(a->op_a, b->op_a, op),
                              TransferAddSub(a->op_b, b->op_b, op),
                              /*fetch*/ nullptr,
@@ -555,18 +558,33 @@
                                                                          HInstruction* y,
                                                                          InductionOp op,
                                                                          bool is_first_call) {
-  // Solve within a cycle over an addition or subtraction: adding or subtracting an
-  // invariant value, seeded from phi, keeps adding to the stride of the linear induction.
+  // Solve within a cycle over an addition or subtraction.
   InductionInfo* b = LookupInfo(loop, y);
-  if (b != nullptr && b->induction_class == kInvariant) {
-    if (x == entry_phi) {
-      return (op == kAdd) ? b : CreateInvariantOp(kNeg, nullptr, b);
-    }
-    auto it = cycle_.find(x);
-    if (it != cycle_.end()) {
-      InductionInfo* a = it->second;
-      if (a->induction_class == kInvariant) {
-        return CreateInvariantOp(op, a, b);
+  if (b != nullptr) {
+    if (b->induction_class == kInvariant) {
+      // Adding or subtracting an invariant value, seeded from phi,
+      // keeps adding to the stride of the linear induction.
+      if (x == entry_phi) {
+        return (op == kAdd) ? b : CreateInvariantOp(kNeg, nullptr, b);
+      }
+      auto it = cycle_.find(x);
+      if (it != cycle_.end()) {
+        InductionInfo* a = it->second;
+        if (a->induction_class == kInvariant) {
+          return CreateInvariantOp(op, a, b);
+        }
+      }
+    } else if (op == kAdd && b->induction_class == kLinear) {
+      // Solve within a tight cycle that adds a term that is already classified as a linear
+      // induction for a polynomial induction k = k + i (represented as sum over linear terms).
+      if (x == entry_phi && entry_phi->InputCount() == 2 && instruction == entry_phi->InputAt(1)) {
+        InductionInfo* initial = LookupInfo(loop, entry_phi->InputAt(0));
+        return CreateInduction(kPolynomial,
+                               kNop,
+                               b,
+                               initial,
+                               /*fetch*/ nullptr,
+                               type_);
       }
     }
   }
@@ -593,58 +611,63 @@
       }
     }
   }
-
   return nullptr;
 }
 
-HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolveGeo(HLoopInformation* loop,
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolveOp(HLoopInformation* loop,
                                                                       HInstruction* entry_phi,
                                                                       HInstruction* instruction,
                                                                       HInstruction* x,
                                                                       HInstruction* y,
                                                                       InductionOp op) {
-  // Solve within a tight cycle that is formed by exactly two instructions, one phi and
-  // one update, for a geometric induction of the form k = k * c.
-  if (x == entry_phi && entry_phi->InputCount() == 2 && instruction == entry_phi->InputAt(1)) {
-    InductionInfo* initial = LookupInfo(loop, entry_phi->InputAt(0));
-    InductionInfo* b = LookupInfo(loop, y);
-    if (b != nullptr && b->induction_class == kInvariant && b->operation == kFetch) {
-      return CreateInduction(kGeometric,
-                             op,
-                             initial,
-                             CreateConstant(0, type_),
-                             b->fetch,
-                             type_);
-    }
-  }
-  return nullptr;
-}
-
-HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::SolveXor(HLoopInformation* loop,
-                                                                      HInstruction* entry_phi,
-                                                                      HInstruction* instruction,
-                                                                      HInstruction* x,
-                                                                      HInstruction* y) {
-  // Solve within a tight cycle on periodic idiom of the form x = c ^ x or x = x ^ c.
+  // Solve within a tight cycle for a binary operation k = k op c or, for some op, k = c op k.
   if (entry_phi->InputCount() == 2 && instruction == entry_phi->InputAt(1)) {
-    InductionInfo* initial = LookupInfo(loop, entry_phi->InputAt(0));
-    InductionInfo* a = LookupInfo(loop, x);
-    if (a != nullptr && a->induction_class == kInvariant && entry_phi == y) {
-      return CreateInduction(kPeriodic,
-                             kNop,
-                             CreateInvariantOp(kXor, a, initial),
-                             initial,
-                             /*fetch*/ nullptr,
-                             type_);
-    }
+    InductionInfo* c = nullptr;
     InductionInfo* b = LookupInfo(loop, y);
     if (b != nullptr && b->induction_class == kInvariant && entry_phi == x) {
-      return CreateInduction(kPeriodic,
-                             kNop,
-                             CreateInvariantOp(kXor, initial, b),
-                             initial,
-                             /*fetch*/ nullptr,
-                             type_);
+      c = b;
+    } else if (op != kDiv && op != kRem) {
+      InductionInfo* a = LookupInfo(loop, x);
+      if (a != nullptr && a->induction_class == kInvariant && entry_phi == y) {
+        c = a;
+      }
+    }
+    // Found suitable operand left or right?
+    if (c != nullptr) {
+      InductionInfo* initial = LookupInfo(loop, entry_phi->InputAt(0));
+      switch (op) {
+        case kMul:
+        case kDiv:
+          // Restrict base of geometric induction to direct fetch.
+          if (c->operation == kFetch) {
+            return CreateInduction(kGeometric,
+                                   op,
+                                   initial,
+                                   CreateConstant(0, type_),
+                                   c->fetch,
+                                   type_);
+          };
+          break;
+        case kRem:
+          // Idiomatic MOD wrap-around induction.
+          return CreateInduction(kWrapAround,
+                                 kNop,
+                                 initial,
+                                 CreateInvariantOp(kRem, initial, c),
+                                 /*fetch*/ nullptr,
+                                 type_);
+        case kXor:
+          // Idiomatic XOR periodic induction.
+          return CreateInduction(kPeriodic,
+                                 kNop,
+                                 CreateInvariantOp(kXor, initial, c),
+                                 initial,
+                                 /*fetch*/ nullptr,
+                                 type_);
+        default:
+          CHECK(false) << op;
+          break;
+      }
     }
   }
   return nullptr;
@@ -659,9 +682,9 @@
   HInstruction* x = instruction->InputAt(0);
   HInstruction* y = instruction->InputAt(1);
   if (IsExact(LookupInfo(loop, x), &value) && value == opposite_value) {
-    return SolveXor(loop, entry_phi, instruction, graph_->GetIntConstant(1), y);
+    return SolveOp(loop, entry_phi, instruction, graph_->GetIntConstant(1), y, kXor);
   } else if (IsExact(LookupInfo(loop, y), &value) && value == opposite_value) {
-    return SolveXor(loop, entry_phi, instruction, x, graph_->GetIntConstant(1));
+    return SolveOp(loop, entry_phi, instruction, x, graph_->GetIntConstant(1), kXor);
   }
   return nullptr;
 }
@@ -1018,7 +1041,7 @@
 HInstruction* HInductionVarAnalysis::GetMultConstantForShift(HLoopInformation* loop,
                                                              HInstruction* instruction) {
   // Obtain the constant needed to treat shift as equivalent multiplication. This yields an
-  // existing instruction if the constants is already there. Otherwise, this has a side effect
+  // existing instruction if the constant is already there. Otherwise, this has a side effect
   // on the HIR. The restriction on the shift factor avoids generating a negative constant
   // (viz. 1 << 31 and 1L << 63 set the sign bit). The code assumes that generalization for
   // shift factors outside [0,32) and [0,64) ranges is done by earlier simplification.
@@ -1102,6 +1125,7 @@
         case kNeg:   inv += " - ";  break;
         case kMul:   inv += " * ";  break;
         case kDiv:   inv += " / ";  break;
+        case kRem:   inv += " % ";  break;
         case kXor:   inv += " ^ ";  break;
         case kLT:    inv += " < ";  break;
         case kLE:    inv += " <= "; break;
@@ -1118,32 +1142,30 @@
       return inv;
     } else {
       if (info->induction_class == kLinear) {
+        DCHECK(info->operation == kNop);
         return "(" + InductionToString(info->op_a) + " * i + " +
                      InductionToString(info->op_b) + "):" +
                      Primitive::PrettyDescriptor(info->type);
       } else if (info->induction_class == kPolynomial) {
-        return "poly( sum_i(" + InductionToString(info->op_a) + ") + " +
+        DCHECK(info->operation == kNop);
+        return "poly(sum_lt(" + InductionToString(info->op_a) + ") + " +
                                 InductionToString(info->op_b) + "):" +
                                 Primitive::PrettyDescriptor(info->type);
       } else if (info->induction_class == kGeometric) {
+        DCHECK(info->operation == kMul || info->operation == kDiv);
         DCHECK(info->fetch != nullptr);
-        if (info->operation == kNop) {
-         return "geo(" + InductionToString(info->op_a) + " mod_i " +
-                         FetchToString(info->fetch) + " + " +
-                         InductionToString(info->op_b) + "):" +
-                         Primitive::PrettyDescriptor(info->type);
-        } else {
-         return "geo(" + InductionToString(info->op_a) + " * " +
-                         FetchToString(info->fetch) +
-                         (info->operation == kMul ? " ^ i + " : " ^ -i + ") +
-                         InductionToString(info->op_b) + "):" +
-                         Primitive::PrettyDescriptor(info->type);
-        }
+        return "geo(" + InductionToString(info->op_a) + " * " +
+                        FetchToString(info->fetch) +
+                        (info->operation == kMul ? " ^ i + " : " ^ -i + ") +
+                        InductionToString(info->op_b) + "):" +
+                        Primitive::PrettyDescriptor(info->type);
       } else if (info->induction_class == kWrapAround) {
+        DCHECK(info->operation == kNop);
         return "wrap(" + InductionToString(info->op_a) + ", " +
                          InductionToString(info->op_b) + "):" +
                          Primitive::PrettyDescriptor(info->type);
       } else if (info->induction_class == kPeriodic) {
+        DCHECK(info->operation == kNop);
         return "periodic(" + InductionToString(info->op_a) + ", " +
                              InductionToString(info->op_b) + "):" +
                              Primitive::PrettyDescriptor(info->type);
diff --git a/compiler/optimizing/induction_var_analysis.h b/compiler/optimizing/induction_var_analysis.h
index 94afc71..4720f2d 100644
--- a/compiler/optimizing/induction_var_analysis.h
+++ b/compiler/optimizing/induction_var_analysis.h
@@ -65,6 +65,7 @@
     kNeg,
     kMul,
     kDiv,
+    kRem,
     kXor,
     kFetch,
     // Trip-counts.
@@ -85,10 +86,10 @@
    *         op: a + b, a - b, -b, a * b, a / b, a % b, a ^ b, fetch
    *   (2) linear:
    *         nop: a * i + b
-   *   (3) polynomial:                        // TODO: coming soon
-   *         nop: sum_i(a) + b, for linear a
+   *   (3) polynomial:
+   *         nop: sum_lt(a) + b, for linear a
    *   (4) geometric:
-   *         op: a * fetch^i + b, a * fetch^-i + b, a mod_i fetch + b
+   *         op: a * fetch^i + b, a * fetch^-i + b
    *   (5) wrap-around
    *         nop: a, then defined by b
    *   (6) periodic
@@ -177,17 +178,12 @@
                              HInstruction* y,
                              InductionOp op,
                              bool is_first_call);  // possibly swaps x and y to try again
-  InductionInfo* SolveGeo(HLoopInformation* loop,
-                          HInstruction* entry_phi,
-                          HInstruction* instruction,
-                          HInstruction* x,
-                          HInstruction* y,
-                          InductionOp op);
-  InductionInfo* SolveXor(HLoopInformation* loop,
-                          HInstruction* entry_phi,
-                          HInstruction* instruction,
-                          HInstruction* x,
-                          HInstruction* y);
+  InductionInfo* SolveOp(HLoopInformation* loop,
+                         HInstruction* entry_phi,
+                         HInstruction* instruction,
+                         HInstruction* x,
+                         HInstruction* y,
+                         InductionOp op);
   InductionInfo* SolveTest(HLoopInformation* loop,
                            HInstruction* entry_phi,
                            HInstruction* instruction,
diff --git a/compiler/optimizing/induction_var_analysis_test.cc b/compiler/optimizing/induction_var_analysis_test.cc
index 2199c8e..2d182f6 100644
--- a/compiler/optimizing/induction_var_analysis_test.cc
+++ b/compiler/optimizing/induction_var_analysis_test.cc
@@ -85,6 +85,7 @@
     constant0_ = graph_->GetIntConstant(0);
     constant1_ = graph_->GetIntConstant(1);
     constant2_ = graph_->GetIntConstant(2);
+    constant7_ = graph_->GetIntConstant(7);
     constant100_ = graph_->GetIntConstant(100);
     float_constant0_ = graph_->GetFloatConstant(0.0f);
     return_->AddInstruction(new (&allocator_) HReturnVoid());
@@ -193,6 +194,7 @@
   HInstruction* constant0_;
   HInstruction* constant1_;
   HInstruction* constant2_;
+  HInstruction* constant7_;
   HInstruction* constant100_;
   HInstruction* float_constant0_;
 
@@ -378,6 +380,135 @@
   EXPECT_TRUE(HaveSameInduction(store->InputAt(1), inc2));
 }
 
+TEST_F(InductionVarAnalysisTest, AddLinear) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //   t1 = i + i;
+  //   t2 = 7 + i;
+  //   t3 = t1 + t2;
+  // }
+  BuildLoopNest(1);
+
+  HInstruction* add1 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, basic_[0], basic_[0]), 0);
+  HInstruction* add2 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, constant7_, basic_[0]), 0);
+  HInstruction* add3 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, add1, add2), 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ("((1) * i + (0)):PrimInt", GetInductionInfo(basic_[0], 0).c_str());
+  EXPECT_STREQ("(((1) + (1)) * i + (0)):PrimInt", GetInductionInfo(add1, 0).c_str());
+  EXPECT_STREQ("((1) * i + (7)):PrimInt", GetInductionInfo(add2, 0).c_str());
+  EXPECT_STREQ("((((1) + (1)) + (1)) * i + (7)):PrimInt", GetInductionInfo(add3, 0).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindPolynomialInduction) {
+  // Setup:
+  // k = 1;
+  // for (int i = 0; i < 100; i++) {
+  //   t = i * 2;
+  //   t = 100 + t
+  //   k = t + k;  // polynomial
+  // }
+  BuildLoopNest(1);
+  HPhi* k_header = InsertLoopPhi(0, 0);
+  k_header->AddInput(constant1_);
+
+  HInstruction* mul = InsertInstruction(
+      new (&allocator_) HMul(Primitive::kPrimInt, basic_[0], constant2_), 0);
+  HInstruction* add = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, constant100_, mul), 0);
+  HInstruction* pol = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, add, k_header), 0);
+  k_header->AddInput(pol);
+  PerformInductionVarAnalysis();
+
+  // Note, only the phi in the cycle and the base linear induction are classified.
+  EXPECT_STREQ("poly(sum_lt(((2) * i + (100)):PrimInt) + (1)):PrimInt",
+               GetInductionInfo(k_header, 0).c_str());
+  EXPECT_STREQ("((2) * i + (100)):PrimInt", GetInductionInfo(add, 0).c_str());
+  EXPECT_STREQ("", GetInductionInfo(pol, 0).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindPolynomialInductionAndDerived) {
+  // Setup:
+  // k = 1;
+  // for (int i = 0; i < 100; i++) {
+  //   t = k + 100;
+  //   t = k - 1;
+  //   t = - t
+  //   t = k * 2;
+  //   t = k << 2;
+  //   k = k + i;  // polynomial
+  // }
+  BuildLoopNest(1);
+  HPhi* k_header = InsertLoopPhi(0, 0);
+  k_header->AddInput(constant1_);
+
+  HInstruction* add = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant100_), 0);
+  HInstruction* sub = InsertInstruction(
+      new (&allocator_) HSub(Primitive::kPrimInt, k_header, constant1_), 0);
+  HInstruction* neg = InsertInstruction(
+      new (&allocator_) HNeg(Primitive::kPrimInt, sub), 0);
+  HInstruction* mul = InsertInstruction(
+      new (&allocator_) HMul(Primitive::kPrimInt, k_header, constant2_), 0);
+  HInstruction* shl = InsertInstruction(
+      new (&allocator_) HShl(Primitive::kPrimInt, k_header, constant2_), 0);
+  HInstruction* pol = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, k_header, basic_[0]), 0);
+  k_header->AddInput(pol);
+  PerformInductionVarAnalysis();
+
+  // Note, only the phi in the cycle and derived are classified.
+  EXPECT_STREQ("poly(sum_lt(((1) * i + (0)):PrimInt) + (1)):PrimInt",
+               GetInductionInfo(k_header, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt(((1) * i + (0)):PrimInt) + ((1) + (100))):PrimInt",
+               GetInductionInfo(add, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt(((1) * i + (0)):PrimInt) + ((1) - (1))):PrimInt",
+               GetInductionInfo(sub, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt((( - (1)) * i + (0)):PrimInt) + ((1) - (1))):PrimInt",
+               GetInductionInfo(neg, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt(((2) * i + (0)):PrimInt) + (2)):PrimInt",
+               GetInductionInfo(mul, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt(((4) * i + (0)):PrimInt) + (4)):PrimInt",
+               GetInductionInfo(shl, 0).c_str());
+  EXPECT_STREQ("", GetInductionInfo(pol, 0).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, AddPolynomial) {
+  // Setup:
+  // k = 7;
+  // for (int i = 0; i < 100; i++) {
+  //   t = k + k;
+  //   t = t + k;
+  //   k = k + i
+  // }
+  BuildLoopNest(1);
+  HPhi* k_header = InsertLoopPhi(0, 0);
+  k_header->AddInput(constant7_);
+
+  HInstruction* add1 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, k_header, k_header), 0);
+  HInstruction* add2 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, add1, k_header), 0);
+  HInstruction* add3 = InsertInstruction(
+      new (&allocator_) HAdd(Primitive::kPrimInt, k_header, basic_[0]), 0);
+  k_header->AddInput(add3);
+  PerformInductionVarAnalysis();
+
+  // Note, only the phi in the cycle and added-derived are classified.
+  EXPECT_STREQ("poly(sum_lt(((1) * i + (0)):PrimInt) + (7)):PrimInt",
+               GetInductionInfo(k_header, 0).c_str());
+  EXPECT_STREQ("poly(sum_lt((((1) + (1)) * i + (0)):PrimInt) + ((7) + (7))):PrimInt",
+               GetInductionInfo(add1, 0).c_str());
+  EXPECT_STREQ(
+      "poly(sum_lt(((((1) + (1)) + (1)) * i + (0)):PrimInt) + (((7) + (7)) + (7))):PrimInt",
+      GetInductionInfo(add2, 0).c_str());
+  EXPECT_STREQ("", GetInductionInfo(add3, 0).c_str());
+}
+
 TEST_F(InductionVarAnalysisTest, FindGeometricMulInduction) {
   // Setup:
   // k = 1;
@@ -481,20 +612,20 @@
   EXPECT_STREQ("", GetInductionInfo(div, 0).c_str());
 }
 
-TEST_F(InductionVarAnalysisTest, FindGeometricRemInductionAndDerived) {
+TEST_F(InductionVarAnalysisTest, FindRemWrapAroundInductionAndDerived) {
   // Setup:
-  // k = 1;
+  // k = 100;
   // for (int i = 0; i < 100; i++) {
   //   t = k + 100;
   //   t = k - 1;
   //   t = -t
   //   t = k * 2;
   //   t = k << 2;
-  //   k = k % 100;  // geometric (% 100)
+  //   k = k % 7;
   // }
   BuildLoopNest(1);
   HPhi* k_header = InsertLoopPhi(0, 0);
-  k_header->AddInput(constant1_);
+  k_header->AddInput(constant100_);
 
   HInstruction* add = InsertInstruction(
       new (&allocator_) HAdd(Primitive::kPrimInt, k_header, constant100_), 0);
@@ -507,17 +638,17 @@
   HInstruction* shl = InsertInstruction(
       new (&allocator_) HShl(Primitive::kPrimInt, k_header, constant2_), 0);
   HInstruction* rem = InsertInstruction(
-      new (&allocator_) HRem(Primitive::kPrimInt, k_header, constant100_, kNoDexPc), 0);
+      new (&allocator_) HRem(Primitive::kPrimInt, k_header, constant7_, kNoDexPc), 0);
   k_header->AddInput(rem);
   PerformInductionVarAnalysis();
 
-  // Note, only the phi in the cycle and direct additive derived are classified.
-  EXPECT_STREQ("geo((1) mod_i 100 + (0)):PrimInt", GetInductionInfo(k_header, 0).c_str());
-  EXPECT_STREQ("geo((1) mod_i 100 + (100)):PrimInt", GetInductionInfo(add, 0).c_str());
-  EXPECT_STREQ("geo((1) mod_i 100 + ((0) - (1))):PrimInt", GetInductionInfo(sub, 0).c_str());
-  EXPECT_STREQ("", GetInductionInfo(neg, 0).c_str());
-  EXPECT_STREQ("", GetInductionInfo(mul, 0).c_str());
-  EXPECT_STREQ("", GetInductionInfo(shl, 0).c_str());
+  // Note, only the phi in the cycle and derived are classified.
+  EXPECT_STREQ("wrap((100), ((100) % (7))):PrimInt", GetInductionInfo(k_header, 0).c_str());
+  EXPECT_STREQ("wrap(((100) + (100)), (((100) % (7)) + (100))):PrimInt", GetInductionInfo(add, 0).c_str());
+  EXPECT_STREQ("wrap(((100) - (1)), (((100) % (7)) - (1))):PrimInt", GetInductionInfo(sub, 0).c_str());
+  EXPECT_STREQ("wrap(( - ((100) - (1))), ( - (((100) % (7)) - (1)))):PrimInt", GetInductionInfo(neg, 0).c_str());
+  EXPECT_STREQ("wrap(((100) * (2)), (((100) % (7)) * (2))):PrimInt", GetInductionInfo(mul, 0).c_str());
+  EXPECT_STREQ("wrap(((100) * (4)), (((100) % (7)) * (4))):PrimInt", GetInductionInfo(shl, 0).c_str());
   EXPECT_STREQ("", GetInductionInfo(rem, 0).c_str());
 }
 
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index 75619a3..e665551 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -460,6 +460,8 @@
   if (info != nullptr) {
     if (info->induction_class == HInductionVarAnalysis::kLinear) {
       return IsConstant(info->op_a, kExact, stride_value);
+    } else if (info->induction_class == HInductionVarAnalysis::kPolynomial) {
+      return NeedsTripCount(info->op_a, stride_value);
     } else if (info->induction_class == HInductionVarAnalysis::kWrapAround) {
       return NeedsTripCount(info->op_b, stride_value);
     }
@@ -492,7 +494,7 @@
                                                       bool in_body,
                                                       bool is_min) const {
   DCHECK(info != nullptr);
-  DCHECK(info->induction_class == HInductionVarAnalysis::kLinear);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kLinear);
   // Detect common situation where an offset inside the trip-count cancels out during range
   // analysis (finding max a * (TC - 1) + OFFSET for a == 1 and TC = UPPER - OFFSET or finding
   // min a * (TC - 1) + OFFSET for a == -1 and TC = OFFSET - UPPER) to avoid losing information
@@ -539,25 +541,49 @@
                   GetVal(info->op_b, trip, in_body, is_min));
 }
 
+InductionVarRange::Value InductionVarRange::GetPolynomial(HInductionVarAnalysis::InductionInfo* info,
+                                                          HInductionVarAnalysis::InductionInfo* trip,
+                                                          bool in_body,
+                                                          bool is_min) const {
+  DCHECK(info != nullptr);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPolynomial);
+  int64_t a = 0;
+  int64_t b = 0;
+  if (IsConstant(info->op_a->op_a, kExact, &a) && CanLongValueFitIntoInt(a) && a >= 0 &&
+      IsConstant(info->op_a->op_b, kExact, &b) && CanLongValueFitIntoInt(b) && b >= 0) {
+    // Evaluate bounds on sum_i=0^m-1(a * i + b) + c with a,b >= 0 for known
+    // maximum index value m as a * (m * (m-1)) / 2 + b * m + c.
+    Value c = GetVal(info->op_b, trip, in_body, is_min);
+    if (is_min) {
+      return c;
+    } else {
+      Value m = GetVal(trip, trip, in_body, is_min);
+      Value t = DivValue(MulValue(m, SubValue(m, Value(1))), Value(2));
+      Value x = MulValue(Value(a), t);
+      Value y = MulValue(Value(b), m);
+      return AddValue(AddValue(x, y), c);
+    }
+  }
+  return Value();
+}
+
 InductionVarRange::Value InductionVarRange::GetGeometric(HInductionVarAnalysis::InductionInfo* info,
                                                          HInductionVarAnalysis::InductionInfo* trip,
                                                          bool in_body,
                                                          bool is_min) const {
   DCHECK(info != nullptr);
-  DCHECK(info->induction_class == HInductionVarAnalysis::kGeometric);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kGeometric);
   int64_t a = 0;
   int64_t f = 0;
   if (IsConstant(info->op_a, kExact, &a) &&
       CanLongValueFitIntoInt(a) &&
-      IsIntAndGet(info->fetch, &f) &&
-      CanLongValueFitIntoInt(f) && f >= 1) {
-    // Conservative bounds on a * f^-i + b with f >= 1 can be computed without trip count.
-    // Same for mod. All other forms would require a much more elaborate evaluation.
+      IsIntAndGet(info->fetch, &f) && f >= 1) {
+    // Conservative bounds on a * f^-i + b with f >= 1 can be computed without
+    // trip count. Other forms would require a much more elaborate evaluation.
     const bool is_min_a = a >= 0 ? is_min : !is_min;
     if (info->operation == HInductionVarAnalysis::kDiv) {
-      return AddValue(Value(is_min_a ? 0 : a), GetVal(info->op_b, trip, in_body, is_min));
-    } else if (info->operation == HInductionVarAnalysis::kNop) {
-      return AddValue(Value(is_min_a ? (a % f) : a), GetVal(info->op_b, trip, in_body, is_min));
+      Value b = GetVal(info->op_b, trip, in_body, is_min);
+      return is_min_a ? b : AddValue(Value(a), b);
     }
   }
   return Value();
@@ -572,7 +598,7 @@
   if (chase_hint_ == nullptr && in_body && trip != nullptr && instruction == trip->op_a->fetch) {
     if (is_min) {
       return Value(1);
-    } else if (!IsUnsafeTripCount(trip)) {
+    } else if (!instruction->IsConstant() && !IsUnsafeTripCount(trip)) {
       return Value(std::numeric_limits<int32_t>::max());
     }
   }
@@ -650,6 +676,8 @@
             return GetMul(info->op_a, info->op_b, trip, in_body, is_min);
           case HInductionVarAnalysis::kDiv:
             return GetDiv(info->op_a, info->op_b, trip, in_body, is_min);
+          case HInductionVarAnalysis::kRem:
+            return GetRem(info->op_a, info->op_b);
           case HInductionVarAnalysis::kXor:
             return GetXor(info->op_a, info->op_b);
           case HInductionVarAnalysis::kFetch:
@@ -675,7 +703,7 @@
       case HInductionVarAnalysis::kLinear:
         return CorrectForType(GetLinear(info, trip, in_body, is_min), info->type);
       case HInductionVarAnalysis::kPolynomial:
-        break;
+        return GetPolynomial(info, trip, in_body, is_min);
       case HInductionVarAnalysis::kGeometric:
         return GetGeometric(info, trip, in_body, is_min);
       case HInductionVarAnalysis::kWrapAround:
@@ -757,6 +785,21 @@
   return Value();
 }
 
+InductionVarRange::Value InductionVarRange::GetRem(
+    HInductionVarAnalysis::InductionInfo* info1,
+    HInductionVarAnalysis::InductionInfo* info2) const {
+  int64_t v1 = 0;
+  int64_t v2 = 0;
+  // Only accept exact values.
+  if (IsConstant(info1, kExact, &v1) && IsConstant(info2, kExact, &v2) && v2 != 0) {
+    int64_t value = v1 % v2;
+    if (CanLongValueFitIntoInt(value)) {
+      return Value(static_cast<int32_t>(value));
+    }
+  }
+  return Value();
+}
+
 InductionVarRange::Value InductionVarRange::GetXor(
     HInductionVarAnalysis::InductionInfo* info1,
     HInductionVarAnalysis::InductionInfo* info2) const {
@@ -898,8 +941,12 @@
           upper = nullptr;
         }
         break;
+      case HInductionVarAnalysis::kPolynomial:
+        return GenerateLastValuePolynomial(info, trip, graph, block, lower);
       case HInductionVarAnalysis::kGeometric:
         return GenerateLastValueGeometric(info, trip, graph, block, lower);
+      case HInductionVarAnalysis::kWrapAround:
+        return GenerateLastValueWrapAround(info, trip, graph, block, lower);
       case HInductionVarAnalysis::kPeriodic:
         return GenerateLastValuePeriodic(info, trip, graph, block, lower, needs_taken_test);
       default:
@@ -925,13 +972,43 @@
       GenerateCode(info, trip, graph, block, upper, in_body, /* is_min */ false);
 }
 
+bool InductionVarRange::GenerateLastValuePolynomial(HInductionVarAnalysis::InductionInfo* info,
+                                                    HInductionVarAnalysis::InductionInfo* trip,
+                                                    HGraph* graph,
+                                                    HBasicBlock* block,
+                                                    /*out*/HInstruction** result) const {
+  DCHECK(info != nullptr);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPolynomial);
+  // Detect known coefficients and trip count (always taken).
+  int64_t a = 0;
+  int64_t b = 0;
+  int64_t m = 0;
+  if (IsConstant(info->op_a->op_a, kExact, &a) && a >= 0 &&
+      IsConstant(info->op_a->op_b, kExact, &b) && b >= 0 &&
+      IsConstant(trip->op_a, kExact, &m) && m >= 1) {
+    // Evaluate bounds on sum_i=0^m-1(a * i + b) + c with a,b >= 0 for known
+    // maximum index value m as a * (m * (m-1)) / 2 + b * m + c.
+    // TODO: generalize
+    HInstruction* c_instr = nullptr;
+    if (GenerateCode(info->op_b, nullptr, graph, block, graph ? &c_instr : nullptr, false, false)) {
+      if (graph != nullptr) {
+        int64_t sum = a * ((m * (m - 1)) / 2) + b * m;
+        *result = Insert(block, new (graph->GetArena()) HAdd(info->type,
+                                                             graph->GetIntConstant(sum), c_instr));
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
 bool InductionVarRange::GenerateLastValueGeometric(HInductionVarAnalysis::InductionInfo* info,
                                                    HInductionVarAnalysis::InductionInfo* trip,
                                                    HGraph* graph,
                                                    HBasicBlock* block,
                                                    /*out*/HInstruction** result) const {
   DCHECK(info != nullptr);
-  DCHECK(info->induction_class == HInductionVarAnalysis::kGeometric);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kGeometric);
   // Detect known base and trip count (always taken).
   int64_t f = 0;
   int64_t t = 0;
@@ -940,15 +1017,6 @@
     HInstruction* opb = nullptr;
     if (GenerateCode(info->op_a, nullptr, graph, block, &opa, false, false) &&
         GenerateCode(info->op_b, nullptr, graph, block, &opb, false, false)) {
-      // Generate a % f + b.
-      if (info->operation == HInductionVarAnalysis::kNop) {
-        if (graph != nullptr) {
-          HInstruction* rem =
-              Insert(block, new (graph->GetArena()) HRem(info->type, opa, info->fetch, kNoDexPc));
-          *result = Insert(block, new (graph->GetArena()) HAdd(info->type, rem, opb));
-        }
-        return true;
-      }
       // Compute f ^ t.
       int64_t fpowt = IntPow(f, t);
       if (graph != nullptr) {
@@ -980,6 +1048,28 @@
   return false;
 }
 
+bool InductionVarRange::GenerateLastValueWrapAround(HInductionVarAnalysis::InductionInfo* info,
+                                                    HInductionVarAnalysis::InductionInfo* trip,
+                                                    HGraph* graph,
+                                                    HBasicBlock* block,
+                                                    /*out*/HInstruction** result) const {
+  DCHECK(info != nullptr);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kWrapAround);
+  // Count depth.
+  int32_t depth = 0;
+  for (; info->induction_class == HInductionVarAnalysis::kWrapAround;
+       info = info->op_b, ++depth) {}
+  // Handle wrap(x, wrap(.., y)) if trip count reaches an invariant at end.
+  // TODO: generalize
+  int64_t t = 0;
+  if (info->induction_class == HInductionVarAnalysis::kInvariant &&
+      IsConstant(trip->op_a, kExact, &t) && t >= depth &&
+      GenerateCode(info, nullptr, graph, block, result, false, false)) {
+    return true;
+  }
+  return false;
+}
+
 bool InductionVarRange::GenerateLastValuePeriodic(HInductionVarAnalysis::InductionInfo* info,
                                                   HInductionVarAnalysis::InductionInfo* trip,
                                                   HGraph* graph,
@@ -987,17 +1077,18 @@
                                                   /*out*/HInstruction** result,
                                                   /*out*/bool* needs_taken_test) const {
   DCHECK(info != nullptr);
-  DCHECK(info->induction_class == HInductionVarAnalysis::kPeriodic);
+  DCHECK_EQ(info->induction_class, HInductionVarAnalysis::kPeriodic);
   // Count period.
   int32_t period = 1;
   for (HInductionVarAnalysis::InductionInfo* p = info;
        p->induction_class == HInductionVarAnalysis::kPeriodic;
        p = p->op_b, ++period) {}
   // Handle periodic(x, y) case for restricted types.
+  // TODO: generalize
   if (period != 2 ||
       trip->op_a->type != Primitive::kPrimInt ||
       (info->type != Primitive::kPrimInt && info->type != Primitive::kPrimBoolean)) {
-    return false;  // TODO: easy to generalize
+    return false;
   }
   HInstruction* x_instr = nullptr;
   HInstruction* y_instr = nullptr;
@@ -1058,6 +1149,7 @@
         // invariants, some effort is made to keep this parameter consistent).
         switch (info->operation) {
           case HInductionVarAnalysis::kAdd:
+          case HInductionVarAnalysis::kRem:  // no proper is_min for second arg
           case HInductionVarAnalysis::kXor:  // no proper is_min for second arg
           case HInductionVarAnalysis::kLT:
           case HInductionVarAnalysis::kLE:
@@ -1070,6 +1162,8 @@
                 switch (info->operation) {
                   case HInductionVarAnalysis::kAdd:
                     operation = new (graph->GetArena()) HAdd(type, opa, opb); break;
+                  case HInductionVarAnalysis::kRem:
+                    operation = new (graph->GetArena()) HRem(type, opa, opb, kNoDexPc); break;
                   case HInductionVarAnalysis::kXor:
                     operation = new (graph->GetArena()) HXor(type, opa, opb); break;
                   case HInductionVarAnalysis::kLT:
diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h
index f7360e8..ba14847 100644
--- a/compiler/optimizing/induction_var_range.h
+++ b/compiler/optimizing/induction_var_range.h
@@ -190,6 +190,10 @@
                   HInductionVarAnalysis::InductionInfo* trip,
                   bool in_body,
                   bool is_min) const;
+  Value GetPolynomial(HInductionVarAnalysis::InductionInfo* info,
+                      HInductionVarAnalysis::InductionInfo* trip,
+                      bool in_body,
+                      bool is_min) const;
   Value GetGeometric(HInductionVarAnalysis::InductionInfo* info,
                      HInductionVarAnalysis::InductionInfo* trip,
                      bool in_body,
@@ -212,6 +216,8 @@
                HInductionVarAnalysis::InductionInfo* trip,
                bool in_body,
                bool is_min) const;
+  Value GetRem(HInductionVarAnalysis::InductionInfo* info1,
+               HInductionVarAnalysis::InductionInfo* info2) const;
   Value GetXor(HInductionVarAnalysis::InductionInfo* info1,
                HInductionVarAnalysis::InductionInfo* info2) const;
 
@@ -249,12 +255,24 @@
                                 /*out*/ bool* needs_finite_test,
                                 /*out*/ bool* needs_taken_test) const;
 
+  bool GenerateLastValuePolynomial(HInductionVarAnalysis::InductionInfo* info,
+                                   HInductionVarAnalysis::InductionInfo* trip,
+                                   HGraph* graph,
+                                   HBasicBlock* block,
+                                   /*out*/HInstruction** result) const;
+
   bool GenerateLastValueGeometric(HInductionVarAnalysis::InductionInfo* info,
                                   HInductionVarAnalysis::InductionInfo* trip,
                                   HGraph* graph,
                                   HBasicBlock* block,
                                   /*out*/HInstruction** result) const;
 
+  bool GenerateLastValueWrapAround(HInductionVarAnalysis::InductionInfo* info,
+                                   HInductionVarAnalysis::InductionInfo* trip,
+                                   HGraph* graph,
+                                   HBasicBlock* block,
+                                   /*out*/HInstruction** result) const;
+
   bool GenerateLastValuePeriodic(HInductionVarAnalysis::InductionInfo* info,
                                  HInductionVarAnalysis::InductionInfo* trip,
                                  HGraph* graph,
diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc
index 510841e..aa3e1aa 100644
--- a/compiler/optimizing/induction_var_range_test.cc
+++ b/compiler/optimizing/induction_var_range_test.cc
@@ -135,6 +135,8 @@
       case 'n': op = HInductionVarAnalysis::kNeg; break;
       case '*': op = HInductionVarAnalysis::kMul; break;
       case '/': op = HInductionVarAnalysis::kDiv; break;
+      case '%': op = HInductionVarAnalysis::kRem; break;
+      case '^': op = HInductionVarAnalysis::kXor; break;
       case '<': op = HInductionVarAnalysis::kLT;  break;
       default:  op = HInductionVarAnalysis::kNop; break;
     }
@@ -178,12 +180,21 @@
                                  Primitive::kPrimInt);
   }
 
+  /** Constructs a polynomial sum(a * i + b) + c induction. */
+  HInductionVarAnalysis::InductionInfo* CreatePolynomial(int32_t a, int32_t b, int32_t c) {
+    return iva_->CreateInduction(HInductionVarAnalysis::kPolynomial,
+                                 HInductionVarAnalysis::kNop,
+                                 CreateLinear(a, b),
+                                 CreateConst(c),
+                                 nullptr,
+                                 Primitive::kPrimInt);
+  }
+
   /** Constructs a geometric a * f^i + b induction. */
   HInductionVarAnalysis::InductionInfo* CreateGeometric(int32_t a, int32_t b, int32_t f, char op) {
     return iva_->CreateInduction(HInductionVarAnalysis::kGeometric,
-                                 op == '*' ? HInductionVarAnalysis::kMul :
-                                 op == '/' ? HInductionVarAnalysis::kDiv :
-                                             HInductionVarAnalysis::kNop,
+                                 op == '*' ? HInductionVarAnalysis::kMul
+                                           : HInductionVarAnalysis::kDiv,
                                  CreateConst(a),
                                  CreateConst(b),
                                  graph_->GetIntConstant(f),
@@ -200,7 +211,7 @@
                                  Primitive::kPrimInt);
   }
 
-  /** Constructs a wrap-around induction consisting of a constant, followed info */
+  /** Constructs a wrap-around induction consisting of a constant, followed by info. */
   HInductionVarAnalysis::InductionInfo* CreateWrapAround(
       int32_t initial,
       HInductionVarAnalysis::InductionInfo* info) {
@@ -256,6 +267,16 @@
     return range_.GetDiv(info1, info2, nullptr, /* in_body */ true, is_min);
   }
 
+  Value GetRem(HInductionVarAnalysis::InductionInfo* info1,
+               HInductionVarAnalysis::InductionInfo* info2) {
+    return range_.GetRem(info1, info2);
+  }
+
+  Value GetXor(HInductionVarAnalysis::InductionInfo* info1,
+               HInductionVarAnalysis::InductionInfo* info2) {
+    return range_.GetXor(info1, info2);
+  }
+
   bool IsExact(HInductionVarAnalysis::InductionInfo* info, int64_t* value) {
     return range_.IsConstant(info, InductionVarRange::kExact, value);
   }
@@ -438,6 +459,27 @@
   ExpectEqual(Value(20), GetMax(CreateWrapAround(20, -1, 10), nullptr));
 }
 
+TEST_F(InductionVarRangeTest, GetMinMaxPolynomial) {
+  ExpectEqual(Value(7), GetMin(CreatePolynomial(3, 5, 7), nullptr));
+  ExpectEqual(Value(), GetMax(CreatePolynomial(3, 5, 7), nullptr));
+  ExpectEqual(Value(7), GetMin(CreatePolynomial(3, 5, 7), CreateTripCount(5, true, true)));
+  ExpectEqual(Value(45), GetMax(CreatePolynomial(3, 5, 7), CreateTripCount(5, true, true)));
+  ExpectEqual(Value(7), GetMin(CreatePolynomial(3, 5, 7), CreateTripCount(10, true, true)));
+  ExpectEqual(Value(160), GetMax(CreatePolynomial(3, 5, 7), CreateTripCount(10, true, true)));
+  ExpectEqual(Value(-7), GetMin(CreatePolynomial(11, 13, -7),
+                               CreateTripCount(5, true, true)));
+  ExpectEqual(Value(111), GetMax(CreatePolynomial(11, 13, -7),
+                                 CreateTripCount(5, true, true)));
+  ExpectEqual(Value(-7), GetMin(CreatePolynomial(11, 13, -7),
+                               CreateTripCount(10, true, true)));
+  ExpectEqual(Value(506), GetMax(CreatePolynomial(11, 13, -7),
+                                 CreateTripCount(10, true, true)));
+  ExpectEqual(Value(), GetMin(CreatePolynomial(-3, 5, 7), CreateTripCount(10, true, true)));
+  ExpectEqual(Value(), GetMax(CreatePolynomial(-3, 5, 7), CreateTripCount(10, true, true)));
+  ExpectEqual(Value(), GetMin(CreatePolynomial(3, -5, 7), CreateTripCount(10, true, true)));
+  ExpectEqual(Value(), GetMax(CreatePolynomial(3, -5, 7), CreateTripCount(10, true, true)));
+}
+
 TEST_F(InductionVarRangeTest, GetMinMaxGeometricMul) {
   ExpectEqual(Value(), GetMin(CreateGeometric(1, 1, 1, '*'), nullptr));
   ExpectEqual(Value(), GetMax(CreateGeometric(1, 1, 1, '*'), nullptr));
@@ -454,17 +496,6 @@
   ExpectEqual(Value(-5), GetMax(CreateGeometric(-11, -5, 3, '/'), nullptr));
 }
 
-TEST_F(InductionVarRangeTest, GetMinMaxGeometricRem) {
-  ExpectEqual(Value(7), GetMin(CreateGeometric(11, 5, 3, '%'), nullptr));
-  ExpectEqual(Value(16), GetMax(CreateGeometric(11, 5, 3, '%'), nullptr));
-  ExpectEqual(Value(-3), GetMin(CreateGeometric(11, -5, 3, '%'), nullptr));
-  ExpectEqual(Value(6), GetMax(CreateGeometric(11, -5, 3, '%'), nullptr));
-  ExpectEqual(Value(-6), GetMin(CreateGeometric(-11, 5, 3, '%'), nullptr));
-  ExpectEqual(Value(3), GetMax(CreateGeometric(-11, 5, 3, '%'), nullptr));
-  ExpectEqual(Value(-16), GetMin(CreateGeometric(-11, -5, 3, '%'), nullptr));
-  ExpectEqual(Value(-7), GetMax(CreateGeometric(-11, -5, 3, '%'), nullptr));
-}
-
 TEST_F(InductionVarRangeTest, GetMinMaxPeriodic) {
   ExpectEqual(Value(-2), GetMin(CreateRange(-2, 99), nullptr));
   ExpectEqual(Value(99), GetMax(CreateRange(-2, 99), nullptr));
@@ -530,6 +561,46 @@
   ExpectEqual(Value(), GetDiv(CreateRange(-1, 1), CreateRange(-1, 1), false));
 }
 
+TEST_F(InductionVarRangeTest, GetMinMaxRem) {
+  ExpectEqual(Value(), GetMin(CreateInvariant('%', CreateConst(2), CreateRange(10, 20)), nullptr));
+  ExpectEqual(Value(), GetMax(CreateInvariant('%', CreateConst(2), CreateRange(10, 20)), nullptr));
+  ExpectEqual(Value(), GetMin(CreateInvariant('%', CreateRange(10, 20), CreateConst(2)), nullptr));
+  ExpectEqual(Value(), GetMax(CreateInvariant('%', CreateRange(10, 20), CreateConst(2)), nullptr));
+  ExpectEqual(Value(2), GetMin(CreateInvariant('%', CreateConst(2), CreateConst(5)), nullptr));
+  ExpectEqual(Value(2), GetMax(CreateInvariant('%', CreateConst(2), CreateConst(5)), nullptr));
+  ExpectEqual(Value(1), GetMin(CreateInvariant('%', CreateConst(11), CreateConst(5)), nullptr));
+  ExpectEqual(Value(1), GetMax(CreateInvariant('%', CreateConst(11), CreateConst(5)), nullptr));
+}
+
+TEST_F(InductionVarRangeTest, GetRem) {
+  ExpectEqual(Value(0), GetRem(CreateConst(1), CreateConst(1)));
+  ExpectEqual(Value(2), GetRem(CreateConst(2), CreateConst(5)));
+  ExpectEqual(Value(1), GetRem(CreateConst(11), CreateConst(5)));
+  ExpectEqual(Value(-2), GetRem(CreateConst(-2), CreateConst(5)));
+  ExpectEqual(Value(-1), GetRem(CreateConst(-11), CreateConst(5)));
+  ExpectEqual(Value(2), GetRem(CreateConst(2), CreateConst(-5)));
+  ExpectEqual(Value(1), GetRem(CreateConst(11), CreateConst(-5)));
+  ExpectEqual(Value(-2), GetRem(CreateConst(-2), CreateConst(-5)));
+  ExpectEqual(Value(-1), GetRem(CreateConst(-11), CreateConst(-5)));
+  ExpectEqual(Value(), GetRem(CreateConst(1), CreateConst(0)));
+}
+
+TEST_F(InductionVarRangeTest, GetMinMaxXor) {
+  ExpectEqual(Value(), GetMin(CreateInvariant('^', CreateConst(2), CreateRange(10, 20)), nullptr));
+  ExpectEqual(Value(), GetMax(CreateInvariant('^', CreateConst(2), CreateRange(10, 20)), nullptr));
+  ExpectEqual(Value(), GetMin(CreateInvariant('^', CreateRange(10, 20), CreateConst(2)), nullptr));
+  ExpectEqual(Value(), GetMax(CreateInvariant('^', CreateRange(10, 20), CreateConst(2)), nullptr));
+  ExpectEqual(Value(3), GetMin(CreateInvariant('^', CreateConst(1), CreateConst(2)), nullptr));
+  ExpectEqual(Value(3), GetMax(CreateInvariant('^', CreateConst(1), CreateConst(2)), nullptr));
+}
+
+TEST_F(InductionVarRangeTest, GetXor) {
+  ExpectEqual(Value(0), GetXor(CreateConst(1), CreateConst(1)));
+  ExpectEqual(Value(3), GetXor(CreateConst(1), CreateConst(2)));
+  ExpectEqual(Value(-2), GetXor(CreateConst(1), CreateConst(-1)));
+  ExpectEqual(Value(0), GetXor(CreateConst(-1), CreateConst(-1)));
+}
+
 TEST_F(InductionVarRangeTest, AddValue) {
   ExpectEqual(Value(110), AddValue(Value(10), Value(100)));
   ExpectEqual(Value(-5), AddValue(Value(x_, 1, -4), Value(x_, -1, -1)));
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index f4616e3..9d73e29 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -64,7 +64,8 @@
       top_loop_(nullptr),
       last_loop_(nullptr),
       iset_(nullptr),
-      induction_simplication_count_(0) {
+      induction_simplication_count_(0),
+      simplified_(false) {
 }
 
 void HLoopOptimization::Run() {
@@ -169,9 +170,15 @@
     if (current_induction_simplification_count != induction_simplication_count_) {
       induction_range_.ReVisit(node->loop_info);
     }
-    SimplifyBlocks(node);
-    SimplifyInduction(node);
-    SimplifyBlocks(node);
+    // Repeat simplifications until no more changes occur. Note that since
+    // each simplification consists of eliminating code (without introducing
+    // new code), this process is always finite.
+    do {
+      simplified_ = false;
+      SimplifyBlocks(node);
+      SimplifyInduction(node);
+    } while (simplified_);
+    // Remove inner loops when empty.
     if (node->inner == nullptr) {
       RemoveIfEmptyInnerLoop(node);
     }
@@ -198,63 +205,57 @@
       for (HInstruction* i : *iset_) {
         RemoveFromCycle(i);
       }
+      simplified_ = true;
       induction_simplication_count_++;
     }
   }
 }
 
 void HLoopOptimization::SimplifyBlocks(LoopNode* node) {
-  // Repeat the block simplifications until no more changes occur. Note that since
-  // each simplification consists of eliminating code (without introducing new code),
-  // this process is always finite.
-  bool changed;
-  do {
-    changed = false;
-    // Iterate over all basic blocks in the loop-body.
-    for (HBlocksInLoopIterator it(*node->loop_info); !it.Done(); it.Advance()) {
-      HBasicBlock* block = it.Current();
-      // Remove dead instructions from the loop-body.
-      for (HBackwardInstructionIterator i(block->GetInstructions()); !i.Done(); i.Advance()) {
-        HInstruction* instruction = i.Current();
-        if (instruction->IsDeadAndRemovable()) {
-          changed = true;
-          block->RemoveInstruction(instruction);
-        }
+  // Iterate over all basic blocks in the loop-body.
+  for (HBlocksInLoopIterator it(*node->loop_info); !it.Done(); it.Advance()) {
+    HBasicBlock* block = it.Current();
+    // Remove dead instructions from the loop-body.
+    for (HBackwardInstructionIterator i(block->GetInstructions()); !i.Done(); i.Advance()) {
+      HInstruction* instruction = i.Current();
+      if (instruction->IsDeadAndRemovable()) {
+        simplified_ = true;
+        block->RemoveInstruction(instruction);
       }
-      // Remove trivial control flow blocks from the loop-body.
-      HBasicBlock* succ = nullptr;
-      if (IsGotoBlock(block, &succ) && succ->GetPredecessors().size() == 1) {
-        // Trivial goto block can be removed.
-        HBasicBlock* pred = block->GetSinglePredecessor();
-        changed = true;
-        pred->ReplaceSuccessor(block, succ);
-        block->RemoveDominatedBlock(succ);
-        block->DisconnectAndDelete();
-        pred->AddDominatedBlock(succ);
-        succ->SetDominator(pred);
-      } else if (block->GetSuccessors().size() == 2) {
-        // Trivial if block can be bypassed to either branch.
-        HBasicBlock* succ0 = block->GetSuccessors()[0];
-        HBasicBlock* succ1 = block->GetSuccessors()[1];
-        HBasicBlock* meet0 = nullptr;
-        HBasicBlock* meet1 = nullptr;
-        if (succ0 != succ1 &&
-            IsGotoBlock(succ0, &meet0) &&
-            IsGotoBlock(succ1, &meet1) &&
-            meet0 == meet1 &&  // meets again
-            meet0 != block &&  // no self-loop
-            meet0->GetPhis().IsEmpty()) {  // not used for merging
-          changed = true;
-          succ0->DisconnectAndDelete();
-          if (block->Dominates(meet0)) {
-            block->RemoveDominatedBlock(meet0);
-            succ1->AddDominatedBlock(meet0);
-            meet0->SetDominator(succ1);
-          }
+    }
+    // Remove trivial control flow blocks from the loop-body.
+    HBasicBlock* succ = nullptr;
+    if (IsGotoBlock(block, &succ) && succ->GetPredecessors().size() == 1) {
+      // Trivial goto block can be removed.
+      HBasicBlock* pred = block->GetSinglePredecessor();
+      simplified_ = true;
+      pred->ReplaceSuccessor(block, succ);
+      block->RemoveDominatedBlock(succ);
+      block->DisconnectAndDelete();
+      pred->AddDominatedBlock(succ);
+      succ->SetDominator(pred);
+    } else if (block->GetSuccessors().size() == 2) {
+      // Trivial if block can be bypassed to either branch.
+      HBasicBlock* succ0 = block->GetSuccessors()[0];
+      HBasicBlock* succ1 = block->GetSuccessors()[1];
+      HBasicBlock* meet0 = nullptr;
+      HBasicBlock* meet1 = nullptr;
+      if (succ0 != succ1 &&
+          IsGotoBlock(succ0, &meet0) &&
+          IsGotoBlock(succ1, &meet1) &&
+          meet0 == meet1 &&  // meets again
+          meet0 != block &&  // no self-loop
+          meet0->GetPhis().IsEmpty()) {  // not used for merging
+        simplified_ = true;
+        succ0->DisconnectAndDelete();
+        if (block->Dominates(meet0)) {
+          block->RemoveDominatedBlock(meet0);
+          succ1->AddDominatedBlock(meet0);
+          meet0->SetDominator(succ1);
         }
       }
     }
-  } while (changed);
+  }
 }
 
 void HLoopOptimization::RemoveIfEmptyInnerLoop(LoopNode* node) {
diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h
index 3391bef..0f05b24 100644
--- a/compiler/optimizing/loop_optimization.h
+++ b/compiler/optimizing/loop_optimization.h
@@ -95,6 +95,9 @@
   // when the induction of inner loops has changed.
   int32_t induction_simplication_count_;
 
+  // Flag that tracks if any simplifications have occurred.
+  bool simplified_;
+
   friend class LoopOptimizationTest;
 
   DISALLOW_COPY_AND_ASSIGN(HLoopOptimization);
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 319991b..f3a5be2 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -629,13 +629,13 @@
   // Sanity check Class[] and Object[]'s interfaces. GetDirectInterface may cause thread
   // suspension.
   CHECK_EQ(java_lang_Cloneable.Get(),
-           mirror::Class::GetDirectInterface(self, class_array_class, 0));
+           mirror::Class::GetDirectInterface(self, class_array_class.Get(), 0));
   CHECK_EQ(java_io_Serializable.Get(),
-           mirror::Class::GetDirectInterface(self, class_array_class, 1));
+           mirror::Class::GetDirectInterface(self, class_array_class.Get(), 1));
   CHECK_EQ(java_lang_Cloneable.Get(),
-           mirror::Class::GetDirectInterface(self, object_array_class, 0));
+           mirror::Class::GetDirectInterface(self, object_array_class.Get(), 0));
   CHECK_EQ(java_io_Serializable.Get(),
-           mirror::Class::GetDirectInterface(self, object_array_class, 1));
+           mirror::Class::GetDirectInterface(self, object_array_class.Get(), 1));
 
   CHECK_EQ(object_array_string.Get(),
            FindSystemClass(self, GetClassRootDescriptor(kJavaLangStringArrayClass)));
@@ -1981,13 +1981,36 @@
   void Visit(ObjPtr<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) OVERRIDE {
     ClassTable* const class_table = class_loader->GetClassTable();
-    if (!done_ && class_table != nullptr && !class_table->Visit(*visitor_)) {
-      // If the visitor ClassTable returns false it means that we don't need to continue.
-      done_ = true;
+    if (!done_ && class_table != nullptr) {
+      DefiningClassLoaderFilterVisitor visitor(class_loader, visitor_);
+      if (!class_table->Visit(visitor)) {
+        // If the visitor ClassTable returns false it means that we don't need to continue.
+        done_ = true;
+      }
     }
   }
 
  private:
+  // Class visitor that limits the class visits from a ClassTable to the classes with
+  // the provided defining class loader. This filter is used to avoid multiple visits
+  // of the same class which can be recorded for multiple initiating class loaders.
+  class DefiningClassLoaderFilterVisitor : public ClassVisitor {
+   public:
+    DefiningClassLoaderFilterVisitor(ObjPtr<mirror::ClassLoader> defining_class_loader,
+                                     ClassVisitor* visitor)
+        : defining_class_loader_(defining_class_loader), visitor_(visitor) { }
+
+    bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (klass->GetClassLoader() != defining_class_loader_) {
+        return true;
+      }
+      return (*visitor_)(klass);
+    }
+
+    ObjPtr<mirror::ClassLoader> const defining_class_loader_;
+    ClassVisitor* const visitor_;
+  };
+
   ClassVisitor* const visitor_;
   // If done is true then we don't need to do any more visiting.
   bool done_;
@@ -2474,56 +2497,109 @@
     }
   } else {
     ScopedObjectAccessUnchecked soa(self);
-    ObjPtr<mirror::Class> cp_klass;
-    if (FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &cp_klass)) {
-      // The chain was understood. So the value in cp_klass is either the class we were looking
-      // for, or not found.
-      if (cp_klass != nullptr) {
-        return cp_klass.Ptr();
-      }
-      // TODO: We handle the boot classpath loader in FindClassInBaseDexClassLoader. Try to unify
-      //       this and the branch above. TODO: throw the right exception here.
+    ObjPtr<mirror::Class> result_ptr;
+    bool descriptor_equals;
+    bool known_hierarchy =
+        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);
+    if (result_ptr != nullptr) {
+      // The chain was understood and we found the class. We still need to add the class to
+      // the class table to protect from racy programs that can try and redefine the path list
+      // which would change the Class<?> returned for subsequent evaluation of const-class.
+      DCHECK(known_hierarchy);
+      DCHECK(result_ptr->DescriptorEquals(descriptor));
+      descriptor_equals = true;
+    } else {
+      // Either the chain wasn't understood or the class wasn't found.
+      //
+      // If the chain was understood but we did not find the class, let the Java-side
+      // rediscover all this and throw the exception with the right stack trace. Note that
+      // the Java-side could still succeed for racy programs if another thread is actively
+      // modifying the class loader's path list.
 
-      // We'll let the Java-side rediscover all this and throw the exception with the right stack
-      // trace.
-    }
-
-    if (Runtime::Current()->IsAotCompiler()) {
-      // Oops, compile-time, can't run actual class-loader code.
-      ObjPtr<mirror::Throwable> pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
-      self->SetException(pre_allocated);
-      return nullptr;
-    }
-
-    ScopedLocalRef<jobject> class_loader_object(soa.Env(),
-                                                soa.AddLocalReference<jobject>(class_loader.Get()));
-    std::string class_name_string(DescriptorToDot(descriptor));
-    ScopedLocalRef<jobject> result(soa.Env(), nullptr);
-    {
-      ScopedThreadStateChange tsc(self, kNative);
-      ScopedLocalRef<jobject> class_name_object(soa.Env(),
-                                                soa.Env()->NewStringUTF(class_name_string.c_str()));
-      if (class_name_object.get() == nullptr) {
-        DCHECK(self->IsExceptionPending());  // OOME.
+      if (Runtime::Current()->IsAotCompiler()) {
+        // Oops, compile-time, can't run actual class-loader code.
+        ObjPtr<mirror::Throwable> pre_allocated =
+            Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
+        self->SetException(pre_allocated);
         return nullptr;
       }
-      CHECK(class_loader_object.get() != nullptr);
-      result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
-                                               WellKnownClasses::java_lang_ClassLoader_loadClass,
-                                               class_name_object.get()));
+
+      ScopedLocalRef<jobject> class_loader_object(
+          soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
+      std::string class_name_string(DescriptorToDot(descriptor));
+      ScopedLocalRef<jobject> result(soa.Env(), nullptr);
+      {
+        ScopedThreadStateChange tsc(self, kNative);
+        ScopedLocalRef<jobject> class_name_object(
+            soa.Env(), soa.Env()->NewStringUTF(class_name_string.c_str()));
+        if (class_name_object.get() == nullptr) {
+          DCHECK(self->IsExceptionPending());  // OOME.
+          return nullptr;
+        }
+        CHECK(class_loader_object.get() != nullptr);
+        result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
+                                                 WellKnownClasses::java_lang_ClassLoader_loadClass,
+                                                 class_name_object.get()));
+      }
+      if (self->IsExceptionPending()) {
+        // If the ClassLoader threw, pass that exception up.
+        // However, to comply with the RI behavior, first check if another thread succeeded.
+        result_ptr = LookupClass(self, descriptor, hash, class_loader.Get());
+        if (result_ptr != nullptr && !result_ptr->IsErroneous()) {
+          self->ClearException();
+          return EnsureResolved(self, descriptor, result_ptr);
+        }
+        return nullptr;
+      } else if (result.get() == nullptr) {
+        // broken loader - throw NPE to be compatible with Dalvik
+        ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s",
+                                               class_name_string.c_str()).c_str());
+        return nullptr;
+      }
+      result_ptr = soa.Decode<mirror::Class>(result.get());
+      // Check the name of the returned class.
+      descriptor_equals = result_ptr->DescriptorEquals(descriptor);
     }
-    if (self->IsExceptionPending()) {
-      // If the ClassLoader threw, pass that exception up.
-      return nullptr;
-    } else if (result.get() == nullptr) {
-      // broken loader - throw NPE to be compatible with Dalvik
-      ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s",
-                                             class_name_string.c_str()).c_str());
-      return nullptr;
-    } else {
-      // success, return mirror::Class*
-      return soa.Decode<mirror::Class>(result.get()).Ptr();
+
+    // Try to insert the class to the class table, checking for mismatch.
+    ObjPtr<mirror::Class> old;
+    {
+      WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+      ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get());
+      old = class_table->Lookup(descriptor, hash);
+      if (old == nullptr) {
+        old = result_ptr;  // For the comparison below, after releasing the lock.
+        if (descriptor_equals) {
+          class_table->InsertWithHash(result_ptr.Ptr(), hash);
+          Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get());
+        }  // else throw below, after releasing the lock.
+      }
     }
+    if (UNLIKELY(old != result_ptr)) {
+      // Return `old` (even if `!descriptor_equals`) to mimic the RI behavior for parallel
+      // capable class loaders.  (All class loaders are considered parallel capable on Android.)
+      mirror::Class* loader_class = class_loader->GetClass();
+      const char* loader_class_name =
+          loader_class->GetDexFile().StringByTypeIdx(loader_class->GetDexTypeIndex());
+      LOG(WARNING) << "Initiating class loader of type " << DescriptorToDot(loader_class_name)
+          << " is not well-behaved; it returned a different Class for racing loadClass(\""
+          << DescriptorToDot(descriptor) << "\").";
+      return EnsureResolved(self, descriptor, old);
+    }
+    if (UNLIKELY(!descriptor_equals)) {
+      std::string result_storage;
+      const char* result_name = result_ptr->GetDescriptor(&result_storage);
+      std::string loader_storage;
+      const char* loader_class_name = class_loader->GetClass()->GetDescriptor(&loader_storage);
+      ThrowNoClassDefFoundError(
+          "Initiating class loader of type %s returned class %s instead of %s.",
+          DescriptorToDot(loader_class_name).c_str(),
+          DescriptorToDot(result_name).c_str(),
+          DescriptorToDot(descriptor).c_str());
+      return nullptr;
+    }
+    // success, return mirror::Class*
+    return result_ptr.Ptr();
   }
   UNREACHABLE();
 }
@@ -3609,12 +3685,6 @@
   Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(klass);
 }
 
-bool ClassLinker::RemoveClass(const char* descriptor, ObjPtr<mirror::ClassLoader> class_loader) {
-  WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
-  ClassTable* const class_table = ClassTableForClassLoader(class_loader);
-  return class_table != nullptr && class_table->Remove(descriptor);
-}
-
 mirror::Class* ClassLinker::LookupClass(Thread* self,
                                         const char* descriptor,
                                         size_t hash,
@@ -3665,7 +3735,8 @@
       REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) OVERRIDE {
     ClassTable* const class_table = class_loader->GetClassTable();
     ObjPtr<mirror::Class> klass = class_table->Lookup(descriptor_, hash_);
-    if (klass != nullptr) {
+    // Add `klass` only if `class_loader` is its defining (not just initiating) class loader.
+    if (klass != nullptr && klass->GetClassLoader() == class_loader) {
       result_->push_back(klass);
     }
   }
@@ -3684,6 +3755,7 @@
   const size_t hash = ComputeModifiedUtf8Hash(descriptor);
   ObjPtr<mirror::Class> klass = boot_class_table_.Lookup(descriptor, hash);
   if (klass != nullptr) {
+    DCHECK(klass->GetClassLoader() == nullptr);
     result.push_back(klass);
   }
   LookupClassesVisitor visitor(descriptor, hash, &result);
@@ -4486,7 +4558,7 @@
       StackHandleScope<1> hs_iface(self);
       MutableHandle<mirror::Class> handle_scope_iface(hs_iface.NewHandle<mirror::Class>(nullptr));
       for (size_t i = 0; i < num_direct_interfaces; i++) {
-        handle_scope_iface.Assign(mirror::Class::GetDirectInterface(self, klass, i));
+        handle_scope_iface.Assign(mirror::Class::GetDirectInterface(self, klass.Get(), i));
         CHECK(handle_scope_iface.Get() != nullptr);
         CHECK(handle_scope_iface->IsInterface());
         if (handle_scope_iface->HasBeenRecursivelyInitialized()) {
@@ -4622,7 +4694,8 @@
     MutableHandle<mirror::Class> handle_super_iface(hs.NewHandle<mirror::Class>(nullptr));
     // First we initialize all of iface's super-interfaces recursively.
     for (size_t i = 0; i < num_direct_ifaces; i++) {
-      ObjPtr<mirror::Class> super_iface = mirror::Class::GetDirectInterface(self, iface, i);
+      ObjPtr<mirror::Class> super_iface = mirror::Class::GetDirectInterface(self, iface.Get(), i);
+      DCHECK(super_iface != nullptr);
       if (!super_iface->HasBeenRecursivelyInitialized()) {
         // Recursive step
         handle_super_iface.Assign(super_iface);
@@ -6383,7 +6456,7 @@
   for (size_t i = 0; i < num_interfaces; i++) {
     ObjPtr<mirror::Class> interface = have_interfaces
         ? interfaces->GetWithoutChecks(i)
-        : mirror::Class::GetDirectInterface(self, klass, i);
+        : mirror::Class::GetDirectInterface(self, klass.Get(), i);
     DCHECK(interface != nullptr);
     if (UNLIKELY(!interface->IsInterface())) {
       std::string temp;
@@ -6421,7 +6494,7 @@
     std::vector<mirror::Class*> to_add;
     for (size_t i = 0; i < num_interfaces; i++) {
       ObjPtr<mirror::Class> interface = have_interfaces ? interfaces->Get(i) :
-          mirror::Class::GetDirectInterface(self, klass, i);
+          mirror::Class::GetDirectInterface(self, klass.Get(), i);
       to_add.push_back(interface.Ptr());
     }
 
@@ -7796,16 +7869,14 @@
   }
   const DexFile::FieldId& field_id = dex_file.GetFieldId(field_idx);
   Thread* const self = Thread::Current();
-  StackHandleScope<1> hs(self);
-  Handle<mirror::Class> klass(
-      hs.NewHandle(ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader)));
-  if (klass.Get() == nullptr) {
+  ObjPtr<mirror::Class> klass = ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader);
+  if (klass == nullptr) {
     DCHECK(Thread::Current()->IsExceptionPending());
     return nullptr;
   }
 
   if (is_static) {
-    resolved = mirror::Class::FindStaticField(self, klass.Get(), dex_cache.Get(), field_idx);
+    resolved = mirror::Class::FindStaticField(self, klass, dex_cache.Get(), field_idx);
   } else {
     resolved = klass->FindInstanceField(dex_cache.Get(), field_idx);
   }
@@ -7819,7 +7890,7 @@
       resolved = klass->FindInstanceField(name, type);
     }
     if (resolved == nullptr) {
-      ThrowNoSuchFieldError(is_static ? "static " : "instance ", klass.Get(), type, name);
+      ThrowNoSuchFieldError(is_static ? "static " : "instance ", klass, type, name);
       return nullptr;
     }
   }
@@ -7839,10 +7910,8 @@
   }
   const DexFile::FieldId& field_id = dex_file.GetFieldId(field_idx);
   Thread* self = Thread::Current();
-  StackHandleScope<1> hs(self);
-  Handle<mirror::Class> klass(
-      hs.NewHandle(ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader)));
-  if (klass.Get() == nullptr) {
+  ObjPtr<mirror::Class> klass(ResolveType(dex_file, field_id.class_idx_, dex_cache, class_loader));
+  if (klass == nullptr) {
     DCHECK(Thread::Current()->IsExceptionPending());
     return nullptr;
   }
@@ -7854,7 +7923,7 @@
   if (resolved != nullptr) {
     dex_cache->SetResolvedField(field_idx, resolved, image_pointer_size_);
   } else {
-    ThrowNoSuchFieldError("", klass.Get(), type, name);
+    ThrowNoSuchFieldError("", klass, type, name);
   }
   return resolved;
 }
@@ -7972,8 +8041,8 @@
       REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) OVERRIDE {
     ClassTable* const class_table = class_loader->GetClassTable();
     if (class_table != nullptr) {
-      num_zygote_classes += class_table->NumZygoteClasses();
-      num_non_zygote_classes += class_table->NumNonZygoteClasses();
+      num_zygote_classes += class_table->NumZygoteClasses(class_loader);
+      num_non_zygote_classes += class_table->NumNonZygoteClasses(class_loader);
     }
   }
 
@@ -7984,13 +8053,13 @@
 size_t ClassLinker::NumZygoteClasses() const {
   CountClassesVisitor visitor;
   VisitClassLoaders(&visitor);
-  return visitor.num_zygote_classes + boot_class_table_.NumZygoteClasses();
+  return visitor.num_zygote_classes + boot_class_table_.NumZygoteClasses(nullptr);
 }
 
 size_t ClassLinker::NumNonZygoteClasses() const {
   CountClassesVisitor visitor;
   VisitClassLoaders(&visitor);
-  return visitor.num_non_zygote_classes + boot_class_table_.NumNonZygoteClasses();
+  return visitor.num_non_zygote_classes + boot_class_table_.NumNonZygoteClasses(nullptr);
 }
 
 size_t ClassLinker::NumLoadedClasses() {
@@ -8078,7 +8147,7 @@
   ScopedObjectAccessUnchecked soa(self);
 
   // For now, create a libcore-level DexFile for each ART DexFile. This "explodes" multidex.
-  StackHandleScope<11> hs(self);
+  StackHandleScope<6> hs(self);
 
   ArtField* dex_elements_field =
       jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements);
@@ -8157,7 +8226,9 @@
   // Make a pretend boot-classpath.
   // TODO: Should we scan the image?
   ArtField* const parent_field =
-      mirror::Class::FindField(self, hs.NewHandle(h_path_class_loader->GetClass()), "parent",
+      mirror::Class::FindField(self,
+                               h_path_class_loader->GetClass(),
+                               "parent",
                                "Ljava/lang/ClassLoader;");
   DCHECK(parent_field != nullptr);
   ObjPtr<mirror::Object> boot_cl =
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index de1f0f0..9b25303 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -218,12 +218,6 @@
 
   mirror::Class* FindPrimitiveClass(char type) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // General class unloading is not supported, this is used to prune
-  // unwanted classes during image writing.
-  bool RemoveClass(const char* descriptor, ObjPtr<mirror::ClassLoader> class_loader)
-      REQUIRES(!Locks::classlinker_classes_lock_)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   void DumpForSigQuit(std::ostream& os) REQUIRES(!Locks::classlinker_classes_lock_);
 
   size_t NumLoadedClasses()
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index ddb9e59..862585a 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -215,10 +215,12 @@
     EXPECT_TRUE(array->ShouldHaveEmbeddedVTable());
     EXPECT_EQ(2, array->GetIfTableCount());
     ASSERT_TRUE(array->GetIfTable() != nullptr);
-    ObjPtr<mirror::Class> direct_interface0 = mirror::Class::GetDirectInterface(self, array, 0);
+    ObjPtr<mirror::Class> direct_interface0 =
+        mirror::Class::GetDirectInterface(self, array.Get(), 0);
     EXPECT_TRUE(direct_interface0 != nullptr);
     EXPECT_STREQ(direct_interface0->GetDescriptor(&temp), "Ljava/lang/Cloneable;");
-    ObjPtr<mirror::Class> direct_interface1 = mirror::Class::GetDirectInterface(self, array, 1);
+    ObjPtr<mirror::Class> direct_interface1 =
+        mirror::Class::GetDirectInterface(self, array.Get(), 1);
     EXPECT_STREQ(direct_interface1->GetDescriptor(&temp), "Ljava/io/Serializable;");
     ObjPtr<mirror::Class> array_ptr = array->GetComponentType();
     EXPECT_OBJ_PTR_EQ(class_linker_->FindArrayClass(self, &array_ptr), array.Get());
@@ -1019,48 +1021,48 @@
 
   EXPECT_EQ(9U, statics->NumStaticFields());
 
-  ArtField* s0 = mirror::Class::FindStaticField(soa.Self(), statics, "s0", "Z");
+  ArtField* s0 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s0", "Z");
   EXPECT_EQ(s0->GetTypeAsPrimitiveType(), Primitive::kPrimBoolean);
   EXPECT_EQ(true, s0->GetBoolean(statics.Get()));
   s0->SetBoolean<false>(statics.Get(), false);
 
-  ArtField* s1 = mirror::Class::FindStaticField(soa.Self(), statics, "s1", "B");
+  ArtField* s1 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s1", "B");
   EXPECT_EQ(s1->GetTypeAsPrimitiveType(), Primitive::kPrimByte);
   EXPECT_EQ(5, s1->GetByte(statics.Get()));
   s1->SetByte<false>(statics.Get(), 6);
 
-  ArtField* s2 = mirror::Class::FindStaticField(soa.Self(), statics, "s2", "C");
+  ArtField* s2 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s2", "C");
   EXPECT_EQ(s2->GetTypeAsPrimitiveType(), Primitive::kPrimChar);
   EXPECT_EQ('a', s2->GetChar(statics.Get()));
   s2->SetChar<false>(statics.Get(), 'b');
 
-  ArtField* s3 = mirror::Class::FindStaticField(soa.Self(), statics, "s3", "S");
+  ArtField* s3 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s3", "S");
   EXPECT_EQ(s3->GetTypeAsPrimitiveType(), Primitive::kPrimShort);
   EXPECT_EQ(-536, s3->GetShort(statics.Get()));
   s3->SetShort<false>(statics.Get(), -535);
 
-  ArtField* s4 = mirror::Class::FindStaticField(soa.Self(), statics, "s4", "I");
+  ArtField* s4 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s4", "I");
   EXPECT_EQ(s4->GetTypeAsPrimitiveType(), Primitive::kPrimInt);
   EXPECT_EQ(2000000000, s4->GetInt(statics.Get()));
   s4->SetInt<false>(statics.Get(), 2000000001);
 
-  ArtField* s5 = mirror::Class::FindStaticField(soa.Self(), statics, "s5", "J");
+  ArtField* s5 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s5", "J");
   EXPECT_EQ(s5->GetTypeAsPrimitiveType(), Primitive::kPrimLong);
   EXPECT_EQ(0x1234567890abcdefLL, s5->GetLong(statics.Get()));
   s5->SetLong<false>(statics.Get(), INT64_C(0x34567890abcdef12));
 
-  ArtField* s6 = mirror::Class::FindStaticField(soa.Self(), statics, "s6", "F");
+  ArtField* s6 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s6", "F");
   EXPECT_EQ(s6->GetTypeAsPrimitiveType(), Primitive::kPrimFloat);
   EXPECT_DOUBLE_EQ(0.5, s6->GetFloat(statics.Get()));
   s6->SetFloat<false>(statics.Get(), 0.75);
 
-  ArtField* s7 = mirror::Class::FindStaticField(soa.Self(), statics, "s7", "D");
+  ArtField* s7 = mirror::Class::FindStaticField(soa.Self(), statics.Get(), "s7", "D");
   EXPECT_EQ(s7->GetTypeAsPrimitiveType(), Primitive::kPrimDouble);
   EXPECT_DOUBLE_EQ(16777217.0, s7->GetDouble(statics.Get()));
   s7->SetDouble<false>(statics.Get(), 16777219);
 
-  ArtField* s8 = mirror::Class::FindStaticField(soa.Self(), statics, "s8",
-                                                        "Ljava/lang/String;");
+  ArtField* s8 = mirror::Class::FindStaticField(
+      soa.Self(), statics.Get(), "s8", "Ljava/lang/String;");
   EXPECT_EQ(s8->GetTypeAsPrimitiveType(), Primitive::kPrimNot);
   EXPECT_TRUE(s8->GetObject(statics.Get())->AsString()->Equals("android"));
   mirror::String* str_value = mirror::String::AllocFromModifiedUtf8(soa.Self(), "robot");
@@ -1131,10 +1133,14 @@
   EXPECT_EQ(Aj1, A->FindVirtualMethodForVirtualOrInterface(Jj1, kRuntimePointerSize));
   EXPECT_EQ(Aj2, A->FindVirtualMethodForVirtualOrInterface(Jj2, kRuntimePointerSize));
 
-  ArtField* Afoo = mirror::Class::FindStaticField(soa.Self(), A, "foo", "Ljava/lang/String;");
-  ArtField* Bfoo = mirror::Class::FindStaticField(soa.Self(), B, "foo", "Ljava/lang/String;");
-  ArtField* Jfoo = mirror::Class::FindStaticField(soa.Self(), J, "foo", "Ljava/lang/String;");
-  ArtField* Kfoo = mirror::Class::FindStaticField(soa.Self(), K, "foo", "Ljava/lang/String;");
+  ArtField* Afoo =
+      mirror::Class::FindStaticField(soa.Self(), A.Get(), "foo", "Ljava/lang/String;");
+  ArtField* Bfoo =
+      mirror::Class::FindStaticField(soa.Self(), B.Get(), "foo", "Ljava/lang/String;");
+  ArtField* Jfoo =
+      mirror::Class::FindStaticField(soa.Self(), J.Get(), "foo", "Ljava/lang/String;");
+  ArtField* Kfoo =
+      mirror::Class::FindStaticField(soa.Self(), K.Get(), "foo", "Ljava/lang/String;");
   ASSERT_TRUE(Afoo != nullptr);
   EXPECT_EQ(Afoo, Bfoo);
   EXPECT_EQ(Afoo, Jfoo);
diff --git a/runtime/class_table.cc b/runtime/class_table.cc
index 6eb7496..ec33e5e 100644
--- a/runtime/class_table.cc
+++ b/runtime/class_table.cc
@@ -84,18 +84,29 @@
 
 #pragma clang diagnostic pop  // http://b/31104323
 
-size_t ClassTable::NumZygoteClasses() const {
+size_t ClassTable::CountDefiningLoaderClasses(ObjPtr<mirror::ClassLoader> defining_loader,
+                                              const ClassSet& set) const {
+  size_t count = 0;
+  for (const TableSlot& root : set) {
+    if (root.Read()->GetClassLoader() == defining_loader) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+size_t ClassTable::NumZygoteClasses(ObjPtr<mirror::ClassLoader> defining_loader) const {
   ReaderMutexLock mu(Thread::Current(), lock_);
   size_t sum = 0;
   for (size_t i = 0; i < classes_.size() - 1; ++i) {
-    sum += classes_[i].Size();
+    sum += CountDefiningLoaderClasses(defining_loader, classes_[i]);
   }
   return sum;
 }
 
-size_t ClassTable::NumNonZygoteClasses() const {
+size_t ClassTable::NumNonZygoteClasses(ObjPtr<mirror::ClassLoader> defining_loader) const {
   ReaderMutexLock mu(Thread::Current(), lock_);
-  return classes_.back().Size();
+  return CountDefiningLoaderClasses(defining_loader, classes_.back());
 }
 
 mirror::Class* ClassTable::Lookup(const char* descriptor, size_t hash) {
@@ -104,7 +115,7 @@
   for (ClassSet& class_set : classes_) {
     auto it = class_set.FindWithHash(pair, hash);
     if (it != class_set.end()) {
-     return it->Read();
+      return it->Read();
     }
   }
   return nullptr;
@@ -150,7 +161,6 @@
     DCHECK(!a.Read()->DescriptorEquals(b.Read()->GetDescriptor(&temp)));
     return false;
   }
-  DCHECK_EQ(a.Read()->GetClassLoader(), b.Read()->GetClassLoader());
   std::string temp;
   return a.Read()->DescriptorEquals(b.Read()->GetDescriptor(&temp));
 }
diff --git a/runtime/class_table.h b/runtime/class_table.h
index fe0bbb3..104871f 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -142,10 +142,14 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns the number of classes in previous snapshots.
-  size_t NumZygoteClasses() const REQUIRES(!lock_);
+  size_t NumZygoteClasses(ObjPtr<mirror::ClassLoader> defining_loader) const
+      REQUIRES(!lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns all off the classes in the lastest snapshot.
-  size_t NumNonZygoteClasses() const REQUIRES(!lock_);
+  size_t NumNonZygoteClasses(ObjPtr<mirror::ClassLoader> defining_loader) const
+      REQUIRES(!lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Update a class in the table with the new class. Returns the existing class which was replaced.
   mirror::Class* UpdateClass(const char* descriptor, mirror::Class* new_klass, size_t hash)
@@ -231,6 +235,11 @@
  private:
   void InsertWithoutLocks(ObjPtr<mirror::Class> klass) NO_THREAD_SAFETY_ANALYSIS;
 
+  size_t CountDefiningLoaderClasses(ObjPtr<mirror::ClassLoader> defining_loader,
+                                    const ClassSet& set) const
+      REQUIRES(lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Return true if we inserted the oat file, false if it already exists.
   bool InsertOatFileLocked(const OatFile* oat_file)
       REQUIRES(lock_)
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index dc2ae2e..e339666 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -1565,16 +1565,16 @@
 JDWP::JdwpError Dbg::OutputDeclaredInterfaces(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) {
   JDWP::JdwpError error;
   Thread* self = Thread::Current();
-  StackHandleScope<1> hs(self);
-  Handle<mirror::Class> c(hs.NewHandle(DecodeClass(class_id, &error)));
-  if (c.Get() == nullptr) {
+  ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
+  if (c == nullptr) {
     return error;
   }
   size_t interface_count = c->NumDirectInterfaces();
   expandBufAdd4BE(pReply, interface_count);
   for (size_t i = 0; i < interface_count; ++i) {
-    expandBufAddRefTypeId(pReply,
-                          gRegistry->AddRefType(mirror::Class::GetDirectInterface(self, c, i)));
+    ObjPtr<mirror::Class> interface = mirror::Class::GetDirectInterface(self, c, i);
+    DCHECK(interface != nullptr);
+    expandBufAddRefTypeId(pReply, gRegistry->AddRefType(interface));
   }
   return JDWP::ERR_NONE;
 }
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 01a2ad8..3c641b0 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -224,8 +224,8 @@
   }
   std::string temp;
   if (is_static) {
-    field = mirror::Class::FindStaticField(soa.Self(), c, name,
-                                           field_type->GetDescriptor(&temp));
+    field = mirror::Class::FindStaticField(
+        soa.Self(), c.Get(), name, field_type->GetDescriptor(&temp));
   } else {
     field = c->FindInstanceField(name, field_type->GetDescriptor(&temp));
   }
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 0cfe29b..a862c97 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -276,7 +276,7 @@
   if (num_direct_interfaces > 0) {
     os << "  interfaces (" << num_direct_interfaces << "):\n";
     for (size_t i = 0; i < num_direct_interfaces; ++i) {
-      ObjPtr<Class> interface = GetDirectInterface(self, h_this, i);
+      ObjPtr<Class> interface = GetDirectInterface(self, h_this.Get(), i);
       if (interface == nullptr) {
         os << StringPrintf("    %2zd: nullptr!\n", i);
       } else {
@@ -799,24 +799,21 @@
 }
 
 ArtField* Class::FindStaticField(Thread* self,
-                                 Handle<Class> klass,
+                                 ObjPtr<Class> klass,
                                  const StringPiece& name,
                                  const StringPiece& type) {
   // Is the field in this class (or its interfaces), or any of its
   // superclasses (or their interfaces)?
-  for (ObjPtr<Class> k = klass.Get(); k != nullptr; k = k->GetSuperClass()) {
+  for (ObjPtr<Class> k = klass; k != nullptr; k = k->GetSuperClass()) {
     // Is the field in this class?
     ArtField* f = k->FindDeclaredStaticField(name, type);
     if (f != nullptr) {
       return f;
     }
-    // Wrap k incase it moves during GetDirectInterface.
-    StackHandleScope<1> hs(self);
-    HandleWrapperObjPtr<Class> h_k(hs.NewHandleWrapper(&k));
     // Is this field in any of this class' interfaces?
-    for (uint32_t i = 0; i < h_k->NumDirectInterfaces(); ++i) {
-      StackHandleScope<1> hs2(self);
-      Handle<Class> interface(hs2.NewHandle(GetDirectInterface(self, h_k, i)));
+    for (uint32_t i = 0, num_interfaces = k->NumDirectInterfaces(); i != num_interfaces; ++i) {
+      ObjPtr<Class> interface = GetDirectInterface(self, k, i);
+      DCHECK(interface != nullptr);
       f = FindStaticField(self, interface, name, type);
       if (f != nullptr) {
         return f;
@@ -839,11 +836,10 @@
     // Though GetDirectInterface() should not cause thread suspension when called
     // from here, it takes a Handle as an argument, so we need to wrap `k`.
     ScopedAssertNoThreadSuspension ants(__FUNCTION__);
-    StackHandleScope<1> hs(self);
-    Handle<Class> h_k(hs.NewHandle(k));
     // Is this field in any of this class' interfaces?
-    for (uint32_t i = 0; i < h_k->NumDirectInterfaces(); ++i) {
-      ObjPtr<Class> interface = GetDirectInterface(self, h_k, i);
+    for (uint32_t i = 0, num_interfaces = k->NumDirectInterfaces(); i != num_interfaces; ++i) {
+      ObjPtr<Class> interface = GetDirectInterface(self, k, i);
+      DCHECK(interface != nullptr);
       f = FindStaticField(self, interface, dex_cache, dex_field_idx);
       if (f != nullptr) {
         return f;
@@ -854,11 +850,11 @@
 }
 
 ArtField* Class::FindField(Thread* self,
-                           Handle<Class> klass,
+                           ObjPtr<Class> klass,
                            const StringPiece& name,
                            const StringPiece& type) {
   // Find a field using the JLS field resolution order
-  for (ObjPtr<Class> k = klass.Get(); k != nullptr; k = k->GetSuperClass()) {
+  for (ObjPtr<Class> k = klass; k != nullptr; k = k->GetSuperClass()) {
     // Is the field in this class?
     ArtField* f = k->FindDeclaredInstanceField(name, type);
     if (f != nullptr) {
@@ -869,12 +865,10 @@
       return f;
     }
     // Is this field in any of this class' interfaces?
-    StackHandleScope<1> hs(self);
-    HandleWrapperObjPtr<Class> h_k(hs.NewHandleWrapper(&k));
-    for (uint32_t i = 0; i < h_k->NumDirectInterfaces(); ++i) {
-      StackHandleScope<1> hs2(self);
-      Handle<Class> interface(hs2.NewHandle(GetDirectInterface(self, h_k, i)));
-      f = interface->FindStaticField(self, interface, name, type);
+    for (uint32_t i = 0, num_interfaces = k->NumDirectInterfaces(); i != num_interfaces; ++i) {
+      ObjPtr<Class> interface = GetDirectInterface(self, k, i);
+      DCHECK(interface != nullptr);
+      f = FindStaticField(self, interface, name, type);
       if (f != nullptr) {
         return f;
       }
@@ -929,36 +923,46 @@
   return GetInterfaceTypeList()->GetTypeItem(idx).type_idx_;
 }
 
-ObjPtr<Class> Class::GetDirectInterface(Thread* self,
-                                        Handle<Class> klass,
-                                        uint32_t idx) {
-  DCHECK(klass.Get() != nullptr);
+ObjPtr<Class> Class::GetDirectInterface(Thread* self, ObjPtr<Class> klass, uint32_t idx) {
+  DCHECK(klass != nullptr);
   DCHECK(!klass->IsPrimitive());
   if (klass->IsArrayClass()) {
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    // Use ClassLinker::LookupClass(); avoid poisoning ObjPtr<>s by ClassLinker::FindSystemClass().
+    ObjPtr<Class> interface;
     if (idx == 0) {
-      return class_linker->FindSystemClass(self, "Ljava/lang/Cloneable;");
+      interface = class_linker->LookupClass(self, "Ljava/lang/Cloneable;", nullptr);
     } else {
       DCHECK_EQ(1U, idx);
-      return class_linker->FindSystemClass(self, "Ljava/io/Serializable;");
+      interface = class_linker->LookupClass(self, "Ljava/io/Serializable;", nullptr);
     }
+    DCHECK(interface != nullptr);
+    return interface;
   } else if (klass->IsProxyClass()) {
-    ObjPtr<ObjectArray<Class>> interfaces = klass.Get()->GetInterfaces();
+    ObjPtr<ObjectArray<Class>> interfaces = klass->GetInterfaces();
     DCHECK(interfaces != nullptr);
     return interfaces->Get(idx);
   } else {
     dex::TypeIndex type_idx = klass->GetDirectInterfaceTypeIdx(idx);
     ObjPtr<Class> interface = klass->GetDexCache()->GetResolvedType(type_idx);
-    if (interface == nullptr) {
-      interface = Runtime::Current()->GetClassLinker()->ResolveType(klass->GetDexFile(),
-                                                                    type_idx,
-                                                                    klass.Get());
-      CHECK(interface != nullptr || self->IsExceptionPending());
-    }
     return interface;
   }
 }
 
+ObjPtr<Class> Class::ResolveDirectInterface(Thread* self, Handle<Class> klass, uint32_t idx) {
+  ObjPtr<Class> interface = GetDirectInterface(self, klass.Get(), idx);
+  if (interface == nullptr) {
+    DCHECK(!klass->IsArrayClass());
+    DCHECK(!klass->IsProxyClass());
+    dex::TypeIndex type_idx = klass->GetDirectInterfaceTypeIdx(idx);
+    interface = Runtime::Current()->GetClassLinker()->ResolveType(klass->GetDexFile(),
+                                                                  type_idx,
+                                                                  klass.Get());
+    CHECK(interface != nullptr || self->IsExceptionPending());
+  }
+  return interface;
+}
+
 ObjPtr<Class> Class::GetCommonSuperClass(Handle<Class> klass) {
   DCHECK(klass.Get() != nullptr);
   DCHECK(!klass->IsInterface());
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 248c941..d7449c8 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -1087,7 +1087,9 @@
   ArtField* GetStaticField(uint32_t i) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Find a static or instance field using the JLS resolution order
-  static ArtField* FindField(Thread* self, Handle<Class> klass, const StringPiece& name,
+  static ArtField* FindField(Thread* self,
+                             ObjPtr<Class> klass,
+                             const StringPiece& name,
                              const StringPiece& type)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1108,7 +1110,7 @@
 
   // Finds the given static field in this class or a superclass.
   static ArtField* FindStaticField(Thread* self,
-                                   Handle<Class> klass,
+                                   ObjPtr<Class> klass,
                                    const StringPiece& name,
                                    const StringPiece& type)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -1204,9 +1206,15 @@
 
   dex::TypeIndex GetDirectInterfaceTypeIdx(uint32_t idx) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static ObjPtr<Class> GetDirectInterface(Thread* self,
-                                          Handle<Class> klass,
-                                          uint32_t idx)
+  // Get the direct interface of the `klass` at index `idx` if resolved, otherwise return null.
+  // If the caller expects the interface to be resolved, for example for a resolved `klass`,
+  // that assumption should be checked by `DCHECK(result != nullptr)`.
+  static ObjPtr<Class> GetDirectInterface(Thread* self, ObjPtr<Class> klass, uint32_t idx)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Resolve and get the direct interface of the `klass` at index `idx`.
+  // Returns null with a pending exception if the resolution fails.
+  static ObjPtr<Class> ResolveDirectInterface(Thread* self, Handle<Class> klass, uint32_t idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   const char* GetSourceFile() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index 4b47f7f..a6f56ae 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -140,9 +140,9 @@
   Handle<mirror::Class> klass(hs.NewHandle(oa->GetClass()));
   ASSERT_EQ(2U, klass->NumDirectInterfaces());
   EXPECT_OBJ_PTR_EQ(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Cloneable;"),
-                    mirror::Class::GetDirectInterface(soa.Self(), klass, 0));
+                    mirror::Class::GetDirectInterface(soa.Self(), klass.Get(), 0));
   EXPECT_OBJ_PTR_EQ(class_linker_->FindSystemClass(soa.Self(), "Ljava/io/Serializable;"),
-                    mirror::Class::GetDirectInterface(soa.Self(), klass, 1));
+                    mirror::Class::GetDirectInterface(soa.Self(), klass.Get(), 1));
 }
 
 TEST_F(ObjectTest, AllocArray) {
@@ -708,19 +708,19 @@
   // Wrong type.
   EXPECT_TRUE(c->FindDeclaredStaticField("CASE_INSENSITIVE_ORDER", "I") == nullptr);
   EXPECT_TRUE(mirror::Class::FindStaticField(
-      soa.Self(), c, "CASE_INSENSITIVE_ORDER", "I") == nullptr);
+      soa.Self(), c.Get(), "CASE_INSENSITIVE_ORDER", "I") == nullptr);
 
   // Wrong name.
   EXPECT_TRUE(c->FindDeclaredStaticField(
       "cASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;") == nullptr);
   EXPECT_TRUE(
-      mirror::Class::FindStaticField(soa.Self(), c, "cASE_INSENSITIVE_ORDER",
-                                     "Ljava/util/Comparator;") == nullptr);
+      mirror::Class::FindStaticField(
+          soa.Self(), c.Get(), "cASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;") == nullptr);
 
   // Right name and type.
   ArtField* f1 = c->FindDeclaredStaticField("CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
-  ArtField* f2 = mirror::Class::FindStaticField(soa.Self(), c, "CASE_INSENSITIVE_ORDER",
-                                                "Ljava/util/Comparator;");
+  ArtField* f2 = mirror::Class::FindStaticField(
+      soa.Self(), c.Get(), "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
   EXPECT_TRUE(f1 != nullptr);
   EXPECT_TRUE(f2 != nullptr);
   EXPECT_EQ(f1, f2);
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 642826c..3341f53 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -278,7 +278,7 @@
 
     uint32_t num_direct_interfaces = h_clazz->NumDirectInterfaces();
     for (uint32_t i = 0; i < num_direct_interfaces; i++) {
-      ObjPtr<mirror::Class> iface = mirror::Class::GetDirectInterface(self, h_clazz, i);
+      ObjPtr<mirror::Class> iface = mirror::Class::ResolveDirectInterface(self, h_clazz, i);
       if (UNLIKELY(iface == nullptr)) {
         self->AssertPendingException();
         return nullptr;
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 5730cf2..94c12af 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -1297,7 +1297,7 @@
 
   for (std::pair<OatFileAssistant::DexOptNeeded, const char*> field : mapping) {
     ArtField* art_field = mirror::Class::FindStaticField(
-        soa.Self(), dexfile, field.second, "I");
+        soa.Self(), dexfile.Get(), field.second, "I");
     ASSERT_FALSE(art_field == nullptr);
     EXPECT_EQ(art_field->GetTypeAsPrimitiveType(), Primitive::kPrimInt);
     EXPECT_EQ(field.first, art_field->GetInt(dexfile.Get()));
diff --git a/runtime/obj_ptr.h b/runtime/obj_ptr.h
index d24c6fb..2da2ae5 100644
--- a/runtime/obj_ptr.h
+++ b/runtime/obj_ptr.h
@@ -51,27 +51,24 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       : reference_(0u) {}
 
-  template <typename Type>
+  template <typename Type,
+            typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
   ALWAYS_INLINE ObjPtr(Type* ptr)  // NOLINT
       REQUIRES_SHARED(Locks::mutator_lock_)
       : reference_(Encode(static_cast<MirrorType*>(ptr))) {
-    static_assert(std::is_base_of<MirrorType, Type>::value,
-                  "Input type must be a subtype of the ObjPtr type");
   }
 
-  template <typename Type>
+  template <typename Type,
+            typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
   ALWAYS_INLINE ObjPtr(const ObjPtr<Type, kPoison>& other)  // NOLINT
       REQUIRES_SHARED(Locks::mutator_lock_)
       : reference_(Encode(static_cast<MirrorType*>(other.Ptr()))) {
-    static_assert(std::is_base_of<MirrorType, Type>::value,
-                  "Input type must be a subtype of the ObjPtr type");
   }
 
-  template <typename Type>
+  template <typename Type,
+            typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
   ALWAYS_INLINE ObjPtr& operator=(const ObjPtr<Type, kPoison>& other)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    static_assert(std::is_base_of<MirrorType, Type>::value,
-                  "Input type must be a subtype of the ObjPtr type");
     reference_ = Encode(static_cast<MirrorType*>(other.Ptr()));
     return *this;
   }
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index 5e588a8..5f18b7c 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -484,7 +484,7 @@
     art::Handle<art::mirror::Class> h_klass(hs.NewHandle<art::mirror::Class>(klass));
     for (size_t i = 0; i < h_klass->NumDirectInterfaces(); ++i) {
       art::ObjPtr<art::mirror::Class> inf_klass =
-          art::mirror::Class::GetDirectInterface(self, h_klass, i);
+          art::mirror::Class::ResolveDirectInterface(self, h_klass, i);
       if (inf_klass == nullptr) {
         // TODO: With a resolved class this should not happen...
         self->ClearException();
diff --git a/runtime/proxy_test.cc b/runtime/proxy_test.cc
index fd7e56d..1292a81 100644
--- a/runtime/proxy_test.cc
+++ b/runtime/proxy_test.cc
@@ -128,8 +128,8 @@
   ASSERT_TRUE(proxy_class->IsInitialized());
 
   EXPECT_EQ(2U, proxy_class->NumDirectInterfaces());  // Interfaces$I and Interfaces$J.
-  EXPECT_OBJ_PTR_EQ(I.Get(), mirror::Class::GetDirectInterface(soa.Self(), proxy_class, 0));
-  EXPECT_OBJ_PTR_EQ(J.Get(), mirror::Class::GetDirectInterface(soa.Self(), proxy_class, 1));
+  EXPECT_OBJ_PTR_EQ(I.Get(), mirror::Class::GetDirectInterface(soa.Self(), proxy_class.Get(), 0));
+  EXPECT_OBJ_PTR_EQ(J.Get(), mirror::Class::GetDirectInterface(soa.Self(), proxy_class.Get(), 1));
   std::string temp;
   const char* proxy_class_descriptor = proxy_class->GetDescriptor(&temp);
   EXPECT_STREQ("L$Proxy1234;", proxy_class_descriptor);
diff --git a/test/530-checker-loops4/src/Main.java b/test/530-checker-loops4/src/Main.java
index 2e19c88..7d3d7d9 100644
--- a/test/530-checker-loops4/src/Main.java
+++ b/test/530-checker-loops4/src/Main.java
@@ -88,11 +88,9 @@
   //
   /// CHECK-START: int Main.geo4(int) loop_optimization (after)
   /// CHECK-DAG: <<Par:i\d+>> ParameterValue        loop:none
-  /// CHECK-DAG: <<Zer:i\d+>> IntConstant 0         loop:none
   /// CHECK-DAG: <<Int:i\d+>> IntConstant 7         loop:none
   /// CHECK-DAG: <<Rem:i\d+>> Rem [<<Par>>,<<Int>>] loop:none
-  /// CHECK-DAG: <<Add:i\d+>> Add [<<Rem>>,<<Zer>>] loop:none
-  /// CHECK-DAG:              Return [<<Add>>]      loop:none
+  /// CHECK-DAG:              Return [<<Rem>>]      loop:none
   //
   /// CHECK-START: int Main.geo4(int) loop_optimization (after)
   /// CHECK-NOT: Phi
@@ -104,6 +102,17 @@
   }
 
   // TODO: someday?
+  //
+  /// CHECK-START: int Main.geo1BCE() BCE (before)
+  /// CHECK-DAG: BoundsCheck loop:none
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.geo1BCE() BCE (after)
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.geo1BCE() BCE (after)
+  /// CHECK-NOT: BoundsCheck loop:none
+  /// CHECK-NOT: Deoptimize
   public static int geo1BCE() {
     int[] x = {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
                 11, 12, 13, 14, 15, 16, 17, 19, 19, 20,
@@ -118,6 +127,17 @@
   }
 
   // TODO: someday?
+  //
+  /// CHECK-START: int Main.geo2BCE() BCE (before)
+  /// CHECK-DAG: BoundsCheck loop:none
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.geo2BCE() BCE (after)
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.geo2BCE() BCE (after)
+  /// CHECK-NOT: BoundsCheck loop:none
+  /// CHECK-NOT: Deoptimize
   public static int geo2BCE() {
     int[] x = {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
                 11, 12, 13, 14, 15, 16, 17, 19, 19, 20,
@@ -225,16 +245,20 @@
     return a;
   }
 
-  // TODO: Rem is already optimized away by the time the loop optimizer runs;
-  //       we could still optimize this case with last value on wrap-around!
+  // Even though Rem is already optimized away by the time induction analysis
+  // and the loop optimizer run, the loop is optimized with a trivial
+  // wrap-around induction just as the wrap-around for REM would.
   //
   /// CHECK-START: int Main.geoRemBlackHole(int) loop_optimization (before)
   /// CHECK-DAG: Phi loop:<<Loop:B\d+>>
   /// CHECK-DAG: Phi loop:<<Loop>>
   //
   /// CHECK-START: int Main.geoRemBlackHole(int) loop_optimization (after)
-  /// CHECK-DAG: Phi loop:<<Loop:B\d+>>
-  /// CHECK-DAG: Phi loop:<<Loop>>
+  /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0
+  /// CHECK-DAG:               Return [<<Zero>>]
+  //
+  /// CHECK-START: int Main.geoRemBlackHole(int) loop_optimization (after)
+  /// CHECK-NOT: Phi
   public static int geoRemBlackHole(int a) {
     for (int i = 0; i < 100; i++) {
       a %= 1;
diff --git a/test/530-checker-loops5/expected.txt b/test/530-checker-loops5/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/530-checker-loops5/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/530-checker-loops5/info.txt b/test/530-checker-loops5/info.txt
new file mode 100644
index 0000000..15dbf37
--- /dev/null
+++ b/test/530-checker-loops5/info.txt
@@ -0,0 +1 @@
+Test on loop optimizations, in particular with polynomial induction.
diff --git a/test/530-checker-loops5/src/Main.java b/test/530-checker-loops5/src/Main.java
new file mode 100644
index 0000000..54b54d0
--- /dev/null
+++ b/test/530-checker-loops5/src/Main.java
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+//
+// Test on loop optimizations, in particular around polynomial induction.
+//
+public class Main {
+
+  /// CHECK-START: int Main.poly1() loop_optimization (before)
+  /// CHECK-DAG: Phi loop:<<Loop:B\d+>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  //
+  /// CHECK-START: int Main.poly1() loop_optimization (after)
+  /// CHECK-DAG: <<Zer:i\d+>> IntConstant 0         loop:none
+  /// CHECK-DAG: <<Int:i\d+>> IntConstant 55        loop:none
+  /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Zer>>] loop:none
+  /// CHECK-DAG:              Return [<<Add>>]      loop:none
+  //
+  /// CHECK-START: int Main.poly1() instruction_simplifier$after_bce (after)
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 55 loop:none
+  /// CHECK-DAG:               Return [<<Int>>]  loop:none
+  //
+  /// CHECK-START: int Main.poly1() loop_optimization (after)
+  /// CHECK-NOT: Phi
+  public static int poly1() {
+    int a = 0;
+    for (int i = 0; i <= 10; i++) {
+      a += i;
+    }
+    return a;
+  }
+
+  // Multiplication in linear induction has been optimized earlier,
+  // but that does not stop the induction variable recognition
+  // and loop optimizer.
+  //
+  /// CHECK-START: int Main.poly2(int) loop_optimization (before)
+  /// CHECK-DAG: Phi loop:<<Loop:B\d+>>
+  /// CHECK-DAG: Shl loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  //
+  /// CHECK-START: int Main.poly2(int) loop_optimization (after)
+  /// CHECK-DAG: <<Par:i\d+>> ParameterValue        loop:none
+  /// CHECK-DAG: <<Int:i\d+>> IntConstant 185       loop:none
+  /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Par>>] loop:none
+  /// CHECK-DAG:              Return [<<Add>>]      loop:none
+  //
+  /// CHECK-START: int Main.poly2(int) loop_optimization (after)
+  /// CHECK-NOT: Phi
+  public static int poly2(int a) {
+    for (int i = 0; i < 10; i++) {
+      int k = 3 * i + 5;
+      a += k;
+    }
+    return a;
+  }
+
+  /// CHECK-START: int Main.poly3() loop_optimization (before)
+  /// CHECK-DAG: Phi loop:<<Loop:B\d+>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  /// CHECK-DAG: Add loop:<<Loop>>
+  //
+  /// CHECK-START: int Main.poly3() loop_optimization (after)
+  /// CHECK-DAG: <<Ini:i\d+>> IntConstant 12345       loop:none
+  /// CHECK-DAG: <<Int:i\d+>> IntConstant -2146736968 loop:none
+  /// CHECK-DAG: <<Add:i\d+>> Add [<<Int>>,<<Ini>>]   loop:none
+  /// CHECK-DAG:              Return [<<Add>>]        loop:none
+  //
+  /// CHECK-START: int Main.poly3() instruction_simplifier$after_bce (after)
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -2146724623 loop:none
+  /// CHECK-DAG:               Return [<<Int>>]        loop:none
+  //
+  /// CHECK-START: int Main.poly3() loop_optimization (after)
+  /// CHECK-NOT: Phi
+  public static int poly3() {
+    int a = 12345;
+    for (int i = 0; i <= 10; i++) {
+      a += (2147483646 * i + 67890);
+    }
+    return a;
+  }
+
+  /// CHECK-START: int Main.polyBCE1() BCE (before)
+  /// CHECK-DAG: BoundsCheck loop:none
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.polyBCE1() BCE (after)
+  /// CHECK-NOT: BoundsCheck
+  /// CHECK-NOT: Deoptimize
+  public static int polyBCE1() {
+    int[] x = {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
+                11, 12, 13, 14, 15, 16, 17, 19, 19, 20,
+                21, 22 };
+    int a = 0;
+    int r = 0;
+    for (int i = 0; i < 8; i++) {
+      r += x[a];
+      a += i;
+    }
+    return r;
+  }
+
+  /// CHECK-START: int Main.polyBCE2() BCE (before)
+  /// CHECK-DAG: BoundsCheck loop:none
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.polyBCE2() BCE (after)
+  /// CHECK-NOT: BoundsCheck
+  /// CHECK-NOT: Deoptimize
+  public static int polyBCE2() {
+    int[] x = {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
+                11, 12, 13, 14, 15, 16, 17, 19, 19, 20,
+                21, 22, 23, 24, 25, 26, 27 };
+    int a = 1;
+    int r = 0;
+    for (int i = 0; i < 6; i++) {
+      int k = 2 * i + 1;
+      r -= x[a];
+      a += k;
+    }
+    return r;
+  }
+
+  /// CHECK-START: int Main.polyBCE3() BCE (before)
+  /// CHECK-DAG: BoundsCheck loop:none
+  /// CHECK-DAG: BoundsCheck loop:{{B\d+}}
+  //
+  /// CHECK-START: int Main.polyBCE3() BCE (after)
+  /// CHECK-NOT: BoundsCheck
+  /// CHECK-NOT: Deoptimize
+  public static int polyBCE3() {
+    int[] x = {  1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
+                11, 12, 13, 14, 15, 16, 17, 19, 19, 20,
+                21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+                31, 32, 33, 34, 35, 36, 37, 38 };
+    int r = 0;
+    int a1 = 1;
+    int a2 = 2;
+    for (int i = 0; i < 5; i++) {
+      int t = a1 + a2;  // two polynomials combined into new polynomial
+      r -= x[t];
+      a1 += (3 * i + 1);
+      a2 += (2 * i);
+    }
+    return r;
+  }
+
+  //
+  // Verifier.
+  //
+
+  public static void main(String[] args) {
+    expectEquals(55, poly1());
+    expectEquals(185, poly2(0));
+    expectEquals(192, poly2(7));
+    expectEquals(-2146724623, poly3());
+    expectEquals(64, polyBCE1());
+    expectEquals(-68, polyBCE2());
+    expectEquals(-80, polyBCE3());
+
+    System.out.println("passed");
+  }
+
+  private static void expectEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+}
diff --git a/test/618-checker-induction/src/Main.java b/test/618-checker-induction/src/Main.java
index f85479a..87a69b2 100644
--- a/test/618-checker-induction/src/Main.java
+++ b/test/618-checker-induction/src/Main.java
@@ -25,7 +25,7 @@
   /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
   //
   /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}} outer_loop:none
+  /// CHECK-NOT: Phi
   static void deadSingleLoop() {
     for (int i = 0; i < 4; i++) {
     }
@@ -35,7 +35,7 @@
   /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
   //
   /// CHECK-START: void Main.deadSingleLoop() loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}} outer_loop:none
+  /// CHECK-NOT: Phi
   static void deadSingleLoopN(int n) {
     for (int i = 0; i < n; i++) {
     }
@@ -56,7 +56,7 @@
   /// CHECK-DAG: Phi loop:{{B\d+}}      outer_loop:<<Loop>>
   //
   /// CHECK-START: void Main.deadNestedLoops() loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}}
+  /// CHECK-NOT: Phi
   static void deadNestedLoops() {
     for (int i = 0; i < 4; i++) {
       for (int j = 0; j < 4; j++) {
@@ -74,7 +74,7 @@
   /// CHECK-DAG: Phi loop:{{B\d+}}       outer_loop:none
   //
   /// CHECK-START: void Main.deadNestedAndFollowingLoops() loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}}
+  /// CHECK-NOT: Phi
   static void deadNestedAndFollowingLoops() {
     for (int i = 0; i < 4; i++) {
       for (int j = 0; j < 4; j++) {
@@ -96,7 +96,7 @@
   /// CHECK-DAG: Phi loop:{{B\d+}} outer_loop:none
   //
   /// CHECK-START: void Main.deadConditional(int) loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}}
+  /// CHECK-NOT: Phi
   public static void deadConditional(int n) {
     int k = 0;
     int m = 0;
@@ -116,7 +116,7 @@
   /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
   //
   /// CHECK-START: void Main.deadConditionalCycle(int) loop_optimization (after)
-  /// CHECK-NOT: Phi loop:{{B\d+}}
+  /// CHECK-NOT: Phi
   public static void deadConditionalCycle(int n) {
     int k = 0;
     int m = 0;
@@ -215,12 +215,11 @@
   /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedFormInductionUp() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedFormInductionUp() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 12395
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 12395 loop:none
+  /// CHECK-DAG:               Return [<<Int>>]  loop:none
   static int closedFormInductionUp() {
     int closed = 12345;
     for (int i = 0; i < 10; i++) {
@@ -235,8 +234,13 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: int Main.closedFormInductionInAndDown(int) loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
+  //
+  /// CHECK-START: int Main.closedFormInductionInAndDown(int) instruction_simplifier$after_bce (after)
+  /// CHECK-DAG: <<Par:i\d+>>  ParameterValue        loop:none
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -50       loop:none
+  /// CHECK-DAG: <<Add:i\d+>>  Add [<<Int>>,<<Par>>] loop:none
+  /// CHECK-DAG:               Return [<<Add>>]      loop:none
   static int closedFormInductionInAndDown(int closed) {
     for (int i = 0; i < 10; i++) {
       closed -= 5;
@@ -252,12 +256,10 @@
   /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedFormNested() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:loop{{B\d+}}
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedFormNested() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 100
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 100  loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   static int closedFormNested() {
     int closed = 0;
@@ -277,13 +279,11 @@
   /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:loop{{B\d+}}
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedFormNestedAlt() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 15082
-  /// CHECK-DAG:               Return [<<Int>>] loop:none
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 15082 loop:none
+  /// CHECK-DAG:               Return [<<Int>>]  loop:none
   static int closedFormNestedAlt() {
     int closed = 12345;
     for (int i = 0; i < 17; i++) {
@@ -360,11 +360,10 @@
   /// CHECK-DAG:              Return [<<Phi>>] loop:none
   //
   /// CHECK-START: int Main.mainIndexReturned() loop_optimization (after)
-  /// CHECK-NOT:              Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:              Return loop:none
+  /// CHECK-NOT:              Phi
   //
   /// CHECK-START: int Main.mainIndexReturned() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   static int mainIndexReturned() {
     int i;
@@ -378,11 +377,10 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: int Main.periodicReturned9() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.periodicReturned9() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 1
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 1    loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   static int periodicReturned9() {
     int k = 0;
@@ -398,11 +396,10 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: int Main.periodicReturned10() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.periodicReturned10() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   static int periodicReturned10() {
     int k = 0;
@@ -412,7 +409,18 @@
     return k;
   }
 
-  // If ever replaced by closed form, last value should be correct!
+  /// CHECK-START: int Main.getSum21() loop_optimization (before)
+  /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
+  /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG:               Return [<<Phi3>>] loop:none
+  //
+  /// CHECK-START: int Main.getSum21() loop_optimization (after)
+  /// CHECK-NOT:               Phi
+  //
+  /// CHECK-START: int Main.getSum21() instruction_simplifier$after_bce (after)
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 21   loop:none
+  /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static int getSum21() {
     int k = 0;
     int sum = 0;
@@ -436,8 +444,7 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: int Main.periodicReturnedN(int) loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   static int periodicReturnedN(int n) {
     int k = 0;
     for (int i = 0; i < n; i++) {
@@ -480,11 +487,10 @@
   /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>"
   //
   /// CHECK-START: int Main.closedFeed() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedFeed() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 20
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 20   loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static int closedFeed() {
     int closed = 0;
@@ -505,11 +511,10 @@
   /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedLargeUp() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:B\d+ outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedLargeUp() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -10
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant -10  loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static int closedLargeUp() {
     int closed = 0;
@@ -525,11 +530,10 @@
   /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedLargeDown() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:B\d+ outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.closedLargeDown() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 10   loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static int closedLargeDown() {
     int closed = 0;
@@ -548,11 +552,10 @@
   /// CHECK-DAG:               Return [<<Phi5>>] loop:none
   //
   /// CHECK-START: int Main.waterFall() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:B\d+ outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: int Main.waterFall() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 50
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 50   loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static int waterFall() {
     int i = 0;
@@ -570,11 +573,10 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom1() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static boolean periodicBoolIdiom1() {
     boolean x = true;
@@ -590,11 +592,10 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom2() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static boolean periodicBoolIdiom2() {
     boolean x = true;
@@ -610,11 +611,10 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom3() instruction_simplifier$after_bce (after)
-  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0
+  /// CHECK-DAG: <<Int:i\d+>>  IntConstant 0    loop:none
   /// CHECK-DAG:               Return [<<Int>>] loop:none
   private static boolean periodicBoolIdiom3() {
     boolean x = true;
@@ -630,8 +630,7 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   private static boolean periodicBoolIdiom1N(boolean x, int n) {
     for (int i = 0; i < n; i++) {
       x = !x;
@@ -645,8 +644,7 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   private static boolean periodicBoolIdiom2N(boolean x, int n) {
     for (int i = 0; i < n; i++) {
       x = (x != true);
@@ -660,8 +658,7 @@
   /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (after)
-  /// CHECK-NOT:               Phi    loop:{{B\d+}} outer_loop:none
-  /// CHECK-DAG:               Return loop:none
+  /// CHECK-NOT:               Phi
   private static boolean periodicBoolIdiom3N(boolean x, int n) {
     for (int i = 0; i < n; i++) {
       x = (x == false);
diff --git a/test/626-const-class-linking/clear_dex_cache_types.cc b/test/626-const-class-linking/clear_dex_cache_types.cc
new file mode 100644
index 0000000..b035896
--- /dev/null
+++ b/test/626-const-class-linking/clear_dex_cache_types.cc
@@ -0,0 +1,65 @@
+/*
+ * 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 "jni.h"
+#include "object_lock.h"
+#include "scoped_thread_state_change-inl.h"
+
+namespace art {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeClearResolvedTypes(JNIEnv*, jclass, jclass cls) {
+  ScopedObjectAccess soa(Thread::Current());
+  mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
+  for (size_t i = 0, num_types = dex_cache->NumResolvedTypes(); i != num_types; ++i) {
+    dex_cache->SetResolvedType(dex::TypeIndex(i), ObjPtr<mirror::Class>(nullptr));
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeSkipVerification(JNIEnv*, jclass, jclass cls) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Class> klass = hs.NewHandle(soa.Decode<mirror::Class>(cls));
+  mirror::Class::Status status = klass->GetStatus();
+  if (status == mirror::Class::kStatusResolved) {
+    ObjectLock<mirror::Class> lock(soa.Self(), klass);
+    klass->SetStatus(klass, mirror::Class::kStatusVerified, soa.Self());
+  } else {
+    LOG(ERROR) << klass->PrettyClass() << " has unexpected status: " << status;
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_nativeDumpClasses(JNIEnv*, jclass, jobjectArray array) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ObjectArray<mirror::Object>> classes =
+      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(array));
+  CHECK(classes.Get() != nullptr);
+  for (size_t i = 0, length = classes->GetLength(); i != length; ++i) {
+    CHECK(classes->Get(i) != nullptr) << i;
+    CHECK(classes->Get(i)->IsClass())
+        << i << " " << classes->Get(i)->GetClass()->PrettyDescriptor();
+    mirror::Class* as_class = classes->Get(i)->AsClass();
+    mirror::ClassLoader* loader = as_class->GetClassLoader();
+    LOG(ERROR) << "Class #" << i << ": " << as_class->PrettyDescriptor()
+        << " @" << static_cast<const void*>(as_class)
+        << " status:" << as_class->GetStatus()
+        << " definingLoader:" << static_cast<const void*>(loader)
+        << " definingLoaderClass:"
+        << (loader != nullptr ? loader->GetClass()->PrettyDescriptor() : "N/A");
+  }
+}
+
+}  // namespace art
diff --git a/test/626-const-class-linking/expected.txt b/test/626-const-class-linking/expected.txt
new file mode 100644
index 0000000..de1b815
--- /dev/null
+++ b/test/626-const-class-linking/expected.txt
@@ -0,0 +1,61 @@
+JNI_OnLoad called
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+testClearDexCache done
+first: Helper1 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+first: Helper2 class loader: DelegatingLoader
+second: Test class loader: DefiningLoader
+testMultiDex done
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+total: 4
+  throwables: 0
+  classes: 4 (1 unique)
+testRacyLoader done
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyLoader
+second: Test class loader: DefiningLoader
+first: Helper3 class loader: RacyLoader
+second: Test3 class loader: DefiningLoader
+first: Helper3 class loader: RacyLoader
+second: Test3 class loader: DefiningLoader
+total: 4
+  throwables: 0
+  classes: 4 (2 unique)
+testRacyLoader2 done
+java.lang.NoClassDefFoundError: Initiating class loader of type MisbehavingLoader returned class Helper2 instead of Test.
+testMisbehavingLoader done
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+total: 4
+  throwables: 0
+  classes: 4 (1 unique)
+testRacyMisbehavingLoader done
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+first: Helper1 class loader: RacyMisbehavingLoader
+second: Test class loader: DefiningLoader
+total: 4
+  throwables: 0
+  classes: 4 (1 unique)
+testRacyMisbehavingLoader2 done
diff --git a/test/626-const-class-linking/info.txt b/test/626-const-class-linking/info.txt
new file mode 100644
index 0000000..9c19a46
--- /dev/null
+++ b/test/626-const-class-linking/info.txt
@@ -0,0 +1,3 @@
+Test that once a const-class instruction is linked, it will keep referring
+to the same class even in the presence of custom class loaders even after
+clearing the dex cache type array.
diff --git a/test/626-const-class-linking/multidex.jpp b/test/626-const-class-linking/multidex.jpp
new file mode 100644
index 0000000..c7a6648
--- /dev/null
+++ b/test/626-const-class-linking/multidex.jpp
@@ -0,0 +1,27 @@
+ClassPair:
+  @@com.android.jack.annotations.ForceInMainDex
+  class ClassPair
+DefiningLoader:
+  @@com.android.jack.annotations.ForceInMainDex
+  class DefiningLoader
+DelegatingLoader:
+  @@com.android.jack.annotations.ForceInMainDex
+  class DelegatingLoader
+Helper1:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Helper1
+Main:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Main
+MisbehavingLoader:
+  @@com.android.jack.annotations.ForceInMainDex
+  class MisbehavingLoader
+RacyLoader:
+  @@com.android.jack.annotations.ForceInMainDex
+  class RacyLoader
+RacyMisbehavingHelper:
+  @@com.android.jack.annotations.ForceInMainDex
+  class RacyMisbehavingHelper
+RacyMisbehavingLoader:
+  @@com.android.jack.annotations.ForceInMainDex
+  class RacyMisbehavingLoader
diff --git a/test/626-const-class-linking/src-multidex/Helper2.java b/test/626-const-class-linking/src-multidex/Helper2.java
new file mode 100644
index 0000000..5bb31ee
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Helper2.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+public class Helper2 {
+    public static ClassPair get() {
+        Class<?> helper2_class = Helper2.class;
+        Class<?> test_class = Test.class;
+        return new ClassPair(helper2_class, test_class);
+    }
+}
diff --git a/test/626-const-class-linking/src-multidex/Helper3.java b/test/626-const-class-linking/src-multidex/Helper3.java
new file mode 100644
index 0000000..af996de
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Helper3.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+public class Helper3 {
+    public static ClassPair get() {
+        Class<?> helper3_class = Helper3.class;
+        Class<?> test3_class = Test3.class;
+        return new ClassPair(helper3_class, test3_class);
+    }
+}
diff --git a/test/626-const-class-linking/src-multidex/Test.java b/test/626-const-class-linking/src-multidex/Test.java
new file mode 100644
index 0000000..1b0cc2a
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Test.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+public class Test {
+}
diff --git a/test/626-const-class-linking/src-multidex/Test3.java b/test/626-const-class-linking/src-multidex/Test3.java
new file mode 100644
index 0000000..c4b134d
--- /dev/null
+++ b/test/626-const-class-linking/src-multidex/Test3.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+public class Test3 {
+}
diff --git a/test/626-const-class-linking/src/ClassPair.java b/test/626-const-class-linking/src/ClassPair.java
new file mode 100644
index 0000000..b07036c
--- /dev/null
+++ b/test/626-const-class-linking/src/ClassPair.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+public class ClassPair {
+    public Class<?> first;
+    public Class<?> second;
+
+    public ClassPair(Class<?> first, Class<?> second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    public void print() {
+        String first_loader_name = first.getClassLoader().getClass().getName();
+        System.out.println("first: " + first.getName() + " class loader: " + first_loader_name);
+        String second_loader_name = second.getClassLoader().getClass().getName();
+        System.out.println("second: " + second.getName() + " class loader: " + second_loader_name);
+    }
+}
diff --git a/test/626-const-class-linking/src/DefiningLoader.java b/test/626-const-class-linking/src/DefiningLoader.java
new file mode 100644
index 0000000..b17ab77
--- /dev/null
+++ b/test/626-const-class-linking/src/DefiningLoader.java
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A class loader with atypical behavior: we try to load a private
+ * class implementation before asking the system or boot loader.  This
+ * is used to create multiple classes with identical names in a single VM.
+ *
+ * If DexFile is available, we use that; if not, we assume we're not in
+ * Dalvik and instantiate the class with defineClass().
+ *
+ * The location of the DEX files and class data is dependent upon the
+ * test framework.
+ */
+public class DefiningLoader extends ClassLoader {
+    static {
+        // For JVM, register as parallel capable.
+        // Android treats all class loaders as parallel capable and makes this a no-op.
+        registerAsParallelCapable();
+    }
+
+    /* this is where the .class files live */
+    static final String CLASS_PATH1 = "classes/";
+    static final String CLASS_PATH2 = "classes2/";
+
+    /* this is the DEX/Jar file */
+    static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/626-const-class-linking.jar";
+
+    /* on Dalvik, this is a DexFile; otherwise, it's null */
+    private Class<?> mDexClass;
+
+    private Object mDexFile;
+
+    /**
+     * Construct DefiningLoader, grabbing a reference to the DexFile class
+     * if we're running under Dalvik.
+     */
+    public DefiningLoader(ClassLoader parent) {
+        super(parent);
+
+        try {
+            mDexClass = parent.loadClass("dalvik.system.DexFile");
+        } catch (ClassNotFoundException cnfe) {
+            // ignore -- not running Dalvik
+        }
+    }
+
+    /**
+     * Finds the class with the specified binary name.
+     *
+     * We search for a file in CLASS_PATH or pull an entry from DEX_FILE.
+     * If we don't find a match, we throw an exception.
+     */
+    protected Class<?> findClass(String name) throws ClassNotFoundException
+    {
+        if (mDexClass != null) {
+            return findClassDalvik(name);
+        } else {
+            return findClassNonDalvik(name);
+        }
+    }
+
+    /**
+     * Finds the class with the specified binary name, from a DEX file.
+     */
+    private Class<?> findClassDalvik(String name)
+        throws ClassNotFoundException {
+
+        if (mDexFile == null) {
+            synchronized (DefiningLoader.class) {
+                Constructor<?> ctor;
+                /*
+                 * Construct a DexFile object through reflection.
+                 */
+                try {
+                    ctor = mDexClass.getConstructor(String.class);
+                } catch (NoSuchMethodException nsme) {
+                    throw new ClassNotFoundException("getConstructor failed",
+                        nsme);
+                }
+
+                try {
+                    mDexFile = ctor.newInstance(DEX_FILE);
+                } catch (InstantiationException ie) {
+                    throw new ClassNotFoundException("newInstance failed", ie);
+                } catch (IllegalAccessException iae) {
+                    throw new ClassNotFoundException("newInstance failed", iae);
+                } catch (InvocationTargetException ite) {
+                    throw new ClassNotFoundException("newInstance failed", ite);
+                }
+            }
+        }
+
+        /*
+         * Call DexFile.loadClass(String, ClassLoader).
+         */
+        Method meth;
+
+        try {
+            meth = mDexClass.getMethod("loadClass", String.class, ClassLoader.class);
+        } catch (NoSuchMethodException nsme) {
+            throw new ClassNotFoundException("getMethod failed", nsme);
+        }
+
+        try {
+            meth.invoke(mDexFile, name, this);
+        } catch (IllegalAccessException iae) {
+            throw new ClassNotFoundException("loadClass failed", iae);
+        } catch (InvocationTargetException ite) {
+            throw new ClassNotFoundException("loadClass failed",
+                ite.getCause());
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds the class with the specified binary name, from .class files.
+     */
+    private Class<?> findClassNonDalvik(String name)
+        throws ClassNotFoundException {
+
+        String[] pathNames = { CLASS_PATH1 + name + ".class", CLASS_PATH2 + name + ".class" };
+
+        String pathName = null;
+        RandomAccessFile raf = null;
+
+        for (String pn : pathNames) {
+            pathName = pn;
+            try {
+                //System.out.println("--- Defining: looking for " + pathName);
+                raf = new RandomAccessFile(new File(pathName), "r");
+                break;
+            } catch (FileNotFoundException fnfe) {
+            }
+        }
+        if (raf == null) {
+            throw new ClassNotFoundException("Not found: " + pathNames[0] + ":" + pathNames[1]);
+        }
+
+        /* read the entire file in */
+        byte[] fileData;
+        try {
+            fileData = new byte[(int) raf.length()];
+            raf.readFully(fileData);
+        } catch (IOException ioe) {
+            throw new ClassNotFoundException("Read error: " + pathName);
+        } finally {
+            try {
+                raf.close();
+            } catch (IOException ioe) {
+                // drop
+            }
+        }
+
+        /* create the class */
+        //System.out.println("--- Defining: defining " + name);
+        try {
+            return defineClass(name, fileData, 0, fileData.length);
+        } catch (Throwable th) {
+            throw new ClassNotFoundException("defineClass failed", th);
+        }
+    }
+
+    /**
+     * Load a class.
+     *
+     * Normally a class loader wouldn't override this, but we want our
+     * version of the class to take precedence over an already-loaded
+     * version.
+     *
+     * We still want the system classes (e.g. java.lang.Object) from the
+     * bootstrap class loader.
+     */
+    synchronized protected Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        Class<?> res;
+
+        /*
+         * 1. Invoke findLoadedClass(String) to check if the class has
+         * already been loaded.
+         *
+         * This doesn't change.
+         */
+        res = findLoadedClass(name);
+        if (res != null) {
+            // System.out.println("FancyLoader.loadClass: " + name + " already loaded");
+            if (resolve)
+                resolveClass(res);
+            return res;
+        }
+
+        /*
+         * 3. Invoke the findClass(String) method to find the class.
+         */
+        try {
+            res = findClass(name);
+            if (resolve)
+                resolveClass(res);
+        }
+        catch (ClassNotFoundException e) {
+            // we couldn't find it, so eat the exception and keep going
+        }
+
+        /*
+         * 2. Invoke the loadClass method on the parent class loader.  If
+         * the parent loader is null the class loader built-in to the
+         * virtual machine is used, instead.
+         *
+         * (Since we're not in java.lang, we can't actually invoke the
+         * parent's loadClass() method, but we passed our parent to the
+         * super-class which can take care of it for us.)
+         */
+        res = super.loadClass(name, resolve);   // returns class or throws
+        return res;
+    }
+}
diff --git a/test/626-const-class-linking/src/DelegatingLoader.java b/test/626-const-class-linking/src/DelegatingLoader.java
new file mode 100644
index 0000000..49955d4
--- /dev/null
+++ b/test/626-const-class-linking/src/DelegatingLoader.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+public class DelegatingLoader extends DefiningLoader {
+    private DefiningLoader defining_loader;
+
+    public DelegatingLoader(ClassLoader parent, DefiningLoader defining_loader) {
+        super(parent);
+        this.defining_loader = defining_loader;
+    }
+
+    public void resetDefiningLoader(DefiningLoader defining_loader) {
+        this.defining_loader = defining_loader;
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException
+    {
+        if (name.equals("Test")) {
+            throw new Error("Unexpected DelegatingLoader.findClass(\"Test\")");
+        }
+        return super.findClass(name);
+    }
+
+    protected Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        if (name.equals("Test")) {
+            return defining_loader.loadClass(name, resolve);
+        }
+        return super.loadClass(name, resolve);
+    }
+}
diff --git a/test/626-const-class-linking/src/Helper1.java b/test/626-const-class-linking/src/Helper1.java
new file mode 100644
index 0000000..ff9cd1a
--- /dev/null
+++ b/test/626-const-class-linking/src/Helper1.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+public class Helper1 {
+    public static ClassPair get() {
+        Class<?> helper1_class = Helper1.class;
+        Class<?> test_class = Test.class;
+        return new ClassPair(helper1_class, test_class);
+    }
+}
diff --git a/test/626-const-class-linking/src/Main.java b/test/626-const-class-linking/src/Main.java
new file mode 100644
index 0000000..0029428
--- /dev/null
+++ b/test/626-const-class-linking/src/Main.java
@@ -0,0 +1,354 @@
+/*
+ * 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.
+ */
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+public class Main {
+    public static void main(String[] args) throws Exception {
+        try {
+            System.loadLibrary(args[0]);
+        } catch (UnsatisfiedLinkError ule) {
+            usingRI = true;
+            // Add expected JNI_OnLoad log line to match expected.txt.
+            System.out.println("JNI_OnLoad called");
+        }
+
+        testClearDexCache();
+        testMultiDex();
+        testRacyLoader();
+        testRacyLoader2();
+        testMisbehavingLoader();
+        testRacyMisbehavingLoader();
+        testRacyMisbehavingLoader2();
+    }
+
+    private static void testClearDexCache() throws Exception {
+        DelegatingLoader delegating_loader = createDelegatingLoader();
+        Class<?> helper = delegating_loader.loadClass("Helper1");
+
+        WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper);
+        changeInner(delegating_loader);
+        clearResolvedTypes(helper);
+        Runtime.getRuntime().gc();
+        WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper);
+        Runtime.getRuntime().gc();
+
+        Class<?> test1 = weak_test1.get();
+        if (test1 == null) {
+            System.out.println("test1 disappeared");
+        }
+        Class<?> test2 = weak_test2.get();
+        if (test2 == null) {
+            System.out.println("test2 disappeared");
+        }
+        if (test1 != test2) {
+            System.out.println("test1 != test2");
+        }
+
+        System.out.println("testClearDexCache done");
+    }
+
+    private static void testMultiDex() throws Exception {
+        DelegatingLoader delegating_loader = createDelegatingLoader();
+
+        Class<?> helper1 = delegating_loader.loadClass("Helper1");
+        WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper1);
+
+        changeInner(delegating_loader);
+
+        Class<?> helper2 = delegating_loader.loadClass("Helper2");
+        WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper2);
+
+        Runtime.getRuntime().gc();
+
+        Class<?> test1 = weak_test1.get();
+        if (test1 == null) {
+            System.out.println("test1 disappeared");
+        }
+        Class<?> test2 = weak_test2.get();
+        if (test2 == null) {
+            System.out.println("test2 disappeared");
+        }
+        if (test1 != test2) {
+            System.out.println("test1 != test2");
+        }
+
+        System.out.println("testMultiDex done");
+    }
+
+    private static void testMisbehavingLoader() throws Exception {
+        ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+        DefiningLoader defining_loader = new DefiningLoader(system_loader);
+        MisbehavingLoader misbehaving_loader =
+            new MisbehavingLoader(system_loader, defining_loader);
+        Class<?> helper = misbehaving_loader.loadClass("Helper1");
+
+        try {
+            WeakReference<Class<?>> weak_test = wrapHelperGet(helper);
+        } catch (InvocationTargetException ite) {
+            String message = ite.getCause().getMessage();
+            if (usingRI && "Test".equals(message)) {
+              // Replace RI message with dalvik message to match expected.txt.
+              message = "Initiating class loader of type " +
+                  misbehaving_loader.getClass().getName() +
+                  " returned class Helper2 instead of Test.";
+            }
+            System.out.println(ite.getCause().getClass().getName() + ": " + message);
+        }
+        System.out.println("testMisbehavingLoader done");
+    }
+
+    private static void testRacyLoader() throws Exception {
+        final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+        final Thread[] threads = new Thread[4];
+        final Object[] results = new Object[threads.length];
+
+        final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
+        final Class<?> helper1 = racy_loader.loadClass("Helper1");
+        skipVerification(helper1);  // Avoid class loading during verification.
+
+        for (int i = 0; i != threads.length; ++i) {
+            final int my_index = i;
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        Method get = helper1.getDeclaredMethod("get");
+                        results[my_index] = get.invoke(null);
+                    } catch (InvocationTargetException ite) {
+                        results[my_index] = ite.getCause();
+                    } catch (Throwable t) {
+                        results[my_index] = t;
+                    }
+                }
+            };
+            t.start();
+            threads[i] = t;
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+        dumpResultStats(results, 1);
+        System.out.println("testRacyLoader done");
+    }
+
+    private static void testRacyLoader2() throws Exception {
+        final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+        final Thread[] threads = new Thread[4];
+        final Object[] results = new Object[threads.length];
+
+        final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
+        final Class<?> helper1 = racy_loader.loadClass("Helper1");
+        skipVerification(helper1);  // Avoid class loading during verification.
+        final Class<?> helper3 = racy_loader.loadClass("Helper3");
+        skipVerification(helper3);  // Avoid class loading during verification.
+
+        for (int i = 0; i != threads.length; ++i) {
+            final int my_index = i;
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        Class<?> helper = (my_index < threads.length / 2) ? helper1 : helper3;
+                        Method get = helper.getDeclaredMethod("get");
+                        results[my_index] = get.invoke(null);
+                    } catch (InvocationTargetException ite) {
+                        results[my_index] = ite.getCause();
+                    } catch (Throwable t) {
+                        results[my_index] = t;
+                    }
+                }
+            };
+            t.start();
+            threads[i] = t;
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+        dumpResultStats(results, 2);
+        System.out.println("testRacyLoader2 done");
+    }
+
+    private static void testRacyMisbehavingLoader() throws Exception {
+        final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+        final Thread[] threads = new Thread[4];
+        final Object[] results = new Object[threads.length];
+
+        final RacyMisbehavingLoader racy_loader =
+            new RacyMisbehavingLoader(system_loader, threads.length, false);
+        final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
+        skipVerification(helper1);  // Avoid class loading during verification.
+
+        for (int i = 0; i != threads.length; ++i) {
+            final int my_index = i;
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        Method get = helper1.getDeclaredMethod("get");
+                        results[my_index] = get.invoke(null);
+                    } catch (InvocationTargetException ite) {
+                        results[my_index] = ite.getCause();
+                    } catch (Throwable t) {
+                        results[my_index] = t;
+                    }
+                }
+            };
+            t.start();
+            threads[i] = t;
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+        dumpResultStats(results, 1);
+        System.out.println("testRacyMisbehavingLoader done");
+    }
+
+    private static void testRacyMisbehavingLoader2() throws Exception {
+        final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+
+        final Thread[] threads = new Thread[4];
+        final Object[] results = new Object[threads.length];
+
+        final RacyMisbehavingLoader racy_loader =
+            new RacyMisbehavingLoader(system_loader, threads.length, true);
+        final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
+        skipVerification(helper1);  // Avoid class loading during verification.
+
+        for (int i = 0; i != threads.length; ++i) {
+            final int my_index = i;
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        Method get = helper1.getDeclaredMethod("get");
+                        results[my_index] = get.invoke(null);
+                    } catch (InvocationTargetException ite) {
+                        results[my_index] = ite.getCause();
+                    } catch (Throwable t) {
+                        results[my_index] = t;
+                    }
+                }
+            };
+            t.start();
+            threads[i] = t;
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+        dumpResultStats(results, 1);
+        System.out.println("testRacyMisbehavingLoader2 done");
+    }
+
+    private static void dumpResultStats(Object[] results, int expected_unique) throws Exception {
+        int throwables = 0;
+        int classes = 0;
+        int unique_classes = 0;
+        for (int i = 0; i != results.length; ++i) {
+            Object r = results[i];
+            if (r instanceof Throwable) {
+                ++throwables;
+                System.out.println(((Throwable) r).getMessage());
+            } else if (isClassPair(r)) {
+                printPair(r);
+                Object ref = getSecond(r);
+                ++classes;
+                ++unique_classes;
+                for (int j = 0; j != i; ++j) {
+                    Object rj = results[j];
+                    if (isClassPair(results[j]) && getSecond(results[j]) == ref) {
+                        --unique_classes;
+                        break;
+                    }
+                }
+            }
+        }
+        System.out.println("total: " + results.length);
+        System.out.println("  throwables: " + throwables);
+        System.out.println("  classes: " + classes
+            + " (" + unique_classes + " unique)");
+        if (expected_unique != unique_classes) {
+            System.out.println("MISMATCH with expected_unique: " + expected_unique);
+            ArrayList<Class<?>> list = new ArrayList<Class<?>>();
+            for (int i = 0; i != results.length; ++i) {
+                Object r = results[i];
+                if (isClassPair(r)) {
+                    list.add(getSecond(r));
+                }
+            }
+            nativeDumpClasses(list.toArray());
+        }
+    }
+
+    private static DelegatingLoader createDelegatingLoader() {
+        ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+        DefiningLoader defining_loader = new DefiningLoader(system_loader);
+        return new DelegatingLoader(system_loader, defining_loader);
+    }
+
+    private static void changeInner(DelegatingLoader delegating_loader) {
+        ClassLoader system_loader = ClassLoader.getSystemClassLoader();
+        DefiningLoader defining_loader = new DefiningLoader(system_loader);
+        delegating_loader.resetDefiningLoader(defining_loader);
+    }
+
+    private static WeakReference<Class<?>> wrapHelperGet(Class<?> helper) throws Exception {
+        Method get = helper.getDeclaredMethod("get");
+        Object pair = get.invoke(null);
+        printPair(pair);
+        return new WeakReference<Class<?>>(getSecond(pair));
+    }
+
+    private static void printPair(Object pair) throws Exception {
+        Method print = pair.getClass().getDeclaredMethod("print");
+        print.invoke(pair);
+    }
+
+    private static Class<?> getSecond(Object pair) throws Exception {
+        Field second = pair.getClass().getDeclaredField("second");
+        return (Class<?>) second.get(pair);
+    }
+
+    private static boolean isClassPair(Object r) {
+        return r != null && r.getClass().getName().equals("ClassPair");
+    }
+
+    public static void clearResolvedTypes(Class<?> c) {
+        if (!usingRI) {
+            nativeClearResolvedTypes(c);
+        }
+    }
+
+    // Skip verification of a class on ART. Verification can cause classes to be loaded
+    // while holding a lock on the class being verified and holding that lock can interfere
+    // with the intent of the "racy" tests. In these tests we're waiting in the loadClass()
+    // for all the tested threads to synchronize and they cannot reach that point if they
+    // are waiting for the class lock on ClassLinker::InitializeClass(Helper1/Helper3).
+    public static void skipVerification(Class<?> c) {
+        if (!usingRI) {
+            nativeSkipVerification(c);
+        }
+    }
+
+    public static native void nativeClearResolvedTypes(Class<?> c);
+    public static native void nativeSkipVerification(Class<?> c);
+    public static native void nativeDumpClasses(Object[] array);
+
+    static boolean usingRI = false;
+}
diff --git a/test/626-const-class-linking/src/MisbehavingLoader.java b/test/626-const-class-linking/src/MisbehavingLoader.java
new file mode 100644
index 0000000..ca9783e
--- /dev/null
+++ b/test/626-const-class-linking/src/MisbehavingLoader.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+// Class loader that returns Helper2.class when asked to load "Test".
+public class MisbehavingLoader extends DefiningLoader {
+    private DefiningLoader defining_loader;
+
+    public MisbehavingLoader(ClassLoader parent, DefiningLoader defining_loader) {
+        super(parent);
+        this.defining_loader = defining_loader;
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException
+    {
+        if (name.equals("Helper1") || name.equals("Helper2")) {
+            return super.findClass(name);
+        } else if (name.equals("Test")) {
+            throw new Error("Unexpected MisbehavingLoader.findClass(\"Test\")");
+        }
+        return super.findClass(name);
+    }
+
+    protected Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        if (name.equals("Helper1") || name.equals("Helper2")) {
+            return super.loadClass(name, resolve);
+        } else if (name.equals("Test")) {
+            // Ask for a different class.
+            return defining_loader.loadClass("Helper2", resolve);
+        }
+        return super.loadClass(name, resolve);
+    }
+}
diff --git a/test/626-const-class-linking/src/RacyLoader.java b/test/626-const-class-linking/src/RacyLoader.java
new file mode 100644
index 0000000..9c164a3
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyLoader.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+public class RacyLoader extends DefiningLoader {
+    static {
+        // For JVM, register as parallel capable.
+        // Android treats all class loaders as parallel capable and makes this a no-op.
+        registerAsParallelCapable();
+    }
+
+    private Object lock = new Object();
+    private int index = 0;
+    private int count;
+
+    private DefiningLoader[] defining_loaders;
+
+    public RacyLoader(ClassLoader parent, int count) {
+        super(parent);
+        this.count = count;
+        defining_loaders = new DefiningLoader[2];
+        for (int i = 0; i != defining_loaders.length; ++i) {
+            defining_loaders[i] = new DefiningLoader(parent);
+        }
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException
+    {
+        if (name.equals("Test") || name.equals("Test3")) {
+            throw new Error("Unexpected RacyLoader.findClass(\"" + name + "\")");
+        }
+        return super.findClass(name);
+    }
+
+    protected Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        if (name.equals("Test") || name.equals("Test3")) {
+            int my_index = syncWithOtherInstances(count);
+            Class<?> result = defining_loaders[my_index & 1].loadClass(name, resolve);
+            syncWithOtherInstances(2 * count);
+            return result;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    private int syncWithOtherInstances(int limit) {
+        int my_index;
+        synchronized (lock) {
+            my_index = index;
+            ++index;
+            if (index != limit) {
+                do {
+                    try {
+                        lock.wait();
+                    } catch (InterruptedException ie) {
+                        throw new Error(ie);
+                    }
+                } while (index < limit);
+            } else {
+                lock.notifyAll();
+            }
+        }
+        return my_index;
+    }
+}
diff --git a/test/626-const-class-linking/src/RacyMisbehavingHelper.java b/test/626-const-class-linking/src/RacyMisbehavingHelper.java
new file mode 100644
index 0000000..4525278
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyMisbehavingHelper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.Method;
+
+public class RacyMisbehavingHelper {
+    public static ClassPair get() {
+        Class<?> helper1_class = Helper1.class;
+        Class<?> test_class = Test.class;
+        try {
+            // After loading the correct class, allow loading the incorrect class.
+            ClassLoader loader = helper1_class.getClassLoader();
+            Method reportAfterLoading = loader.getClass().getDeclaredMethod("reportAfterLoading");
+            reportAfterLoading.invoke(loader);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        return new ClassPair(helper1_class, test_class);
+    }
+}
diff --git a/test/626-const-class-linking/src/RacyMisbehavingLoader.java b/test/626-const-class-linking/src/RacyMisbehavingLoader.java
new file mode 100644
index 0000000..f5bcb4c
--- /dev/null
+++ b/test/626-const-class-linking/src/RacyMisbehavingLoader.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+public class RacyMisbehavingLoader extends DefiningLoader {
+    static {
+        // For JVM, register as parallel capable.
+        // Android treats all class loaders as parallel capable and makes this a no-op.
+        registerAsParallelCapable();
+    }
+
+    private Object lock = new Object();
+    private int index = 0;
+    private int count;
+    private boolean throw_error;
+
+    private DefiningLoader[] defining_loaders;
+
+    public RacyMisbehavingLoader(ClassLoader parent, int count, boolean throw_error) {
+        super(parent);
+        this.count = count;
+        this.throw_error = throw_error;
+        defining_loaders = new DefiningLoader[2];
+        for (int i = 0; i != defining_loaders.length; ++i) {
+            defining_loaders[i] = new DefiningLoader(parent);
+        }
+    }
+
+    public void reportAfterLoading() {
+        synchronized (lock) {
+            ++index;
+            if (index == 2 * count) {
+                lock.notifyAll();
+            }
+        }
+    }
+
+    protected Class<?> findClass(String name) throws ClassNotFoundException
+    {
+        if (name.equals("Test")) {
+            throw new Error("Unexpected RacyLoader.findClass(\"" + name + "\")");
+        }
+        return super.findClass(name);
+    }
+
+    protected Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        if (name.equals("Test")) {
+            int my_index = syncWithOtherInstances(count);
+            Class<?> result;
+            if ((my_index & 1) == 0) {
+              // Do not delay loading the correct class.
+              result = defining_loaders[my_index & 1].loadClass(name, resolve);
+            } else {
+              // Delay loading the wrong class.
+              syncWithOtherInstances(2 * count);
+              if (throw_error) {
+                throw new Error("RacyMisbehavingLoader throw_error=true");
+              }
+              result = defining_loaders[my_index & 1].loadClass("Test3", resolve);
+            }
+            return result;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    private int syncWithOtherInstances(int limit) {
+        int my_index;
+        synchronized (lock) {
+            my_index = index;
+            ++index;
+            if (index != limit) {
+                do {
+                    try {
+                        lock.wait();
+                    } catch (InterruptedException ie) {
+                        throw new Error(ie);
+                    }
+                } while (index < limit);
+            } else {
+                lock.notifyAll();
+            }
+        }
+        return my_index;
+    }
+}
diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java
index ee9c436..17b56b4 100644
--- a/test/956-methodhandles/src/Main.java
+++ b/test/956-methodhandles/src/Main.java
@@ -1080,7 +1080,7 @@
       return result;
     }
     public static Long sumToReference(int... ints) {
-      System.err.println("Hi");
+      System.out.println("Hi");
       return new Long(sumToPrimitive(ints));
     }
     public static MethodHandles.Lookup lookup() {
@@ -1432,13 +1432,13 @@
     assertEquals(Long.valueOf(10l), (Long) mh.invoke(1, 2, 3, 4));
     try {
       // WrongMethodTypeException should be raised before invoke here.
-      System.err.print("Expect Hi here: ");
+      System.out.print("Expect Hi here: ");
       assertEquals(Long.valueOf(10l), (Byte) mh.invoke(1, 2, 3, 4));
       fail();
     } catch (ClassCastException e) {}
     try {
       // WrongMethodTypeException should be raised before invoke here.
-      System.err.println("Don't expect Hi now");
+      System.out.println("Don't expect Hi now");
       byte b = (byte) mh.invoke(1, 2, 3, 4);
       fail();
     } catch (WrongMethodTypeException e) {}
diff --git a/test/Android.bp b/test/Android.bp
index 44c64c1..2625f56 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -314,6 +314,7 @@
         "595-profile-saving/profile-saving.cc",
         "596-app-images/app_images.cc",
         "597-deopt-new-string/deopt.cc",
+        "626-const-class-linking/clear_dex_cache_types.cc",
     ],
     shared_libs: [
         "libbacktrace",
diff --git a/test/etc/default-build b/test/etc/default-build
index f2b5078..51ae175 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -273,8 +273,10 @@
 fi
 
 # Create a single jar with two dex files for multidex.
-if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then
-  zip $TEST_NAME.jar classes.dex classes2.dex
-elif [ ${NEED_DEX} = "true" ]; then
-  zip $TEST_NAME.jar classes.dex
+if [ ${NEED_DEX} = "true" ]; then
+  if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then
+    zip $TEST_NAME.jar classes.dex classes2.dex
+  else
+    zip $TEST_NAME.jar classes.dex
+  fi
 fi
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index bb3a3ad..f0abb44 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -316,7 +316,8 @@
 if [ "$USE_JVM" = "y" ]; then
   export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
   # Xmx is necessary since we don't pass down the ART flags to JVM.
-  cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes ${FLAGS} $MAIN $@ ${ARGS}"
+  # We pass the classes2 path whether it's used (src-multidex) or not.
+  cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
   if [ "$DEV_MODE" = "y" ]; then
     echo $cmdline
   fi