Merge "Change gcstress runtest option"
diff --git a/compiler/Android.mk b/compiler/Android.mk
index 46b7e5d..6c6d99f 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -284,7 +284,7 @@
   endif
   LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk
   LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
-  # Vixl assembly support for ARM64 targets.
+  # VIXL assembly support for ARM64 targets.
   ifeq ($$(art_ndebug_or_debug),debug)
     ifeq ($$(art_static_or_shared), static)
       LOCAL_WHOLESTATIC_LIBRARIES += libvixld-arm64
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 2bf675d..4c4128c 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -432,11 +432,6 @@
            (instruction_->IsInvokeVirtual()) && 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
@@ -517,11 +512,6 @@
            (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);
@@ -4507,6 +4497,8 @@
   Primitive::Type type = instruction->GetType();
   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:
@@ -4541,11 +4533,6 @@
     }
 
     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.");
@@ -4688,6 +4675,8 @@
   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:
@@ -4952,6 +4941,8 @@
 }
 
 void LocationsBuilderARM::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);
 
@@ -4966,6 +4957,9 @@
   Location first = locations->InAt(0);
   Location second = locations->InAt(1);
 
+  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
+  DCHECK(!kEmitCompilerReadBarrier);
+
   if (second.IsRegister()) {
     __ add(out.AsRegister<Register>(),
            first.AsRegister<Register>(),
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 3b8a521..d95e7df 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -598,11 +598,6 @@
            (instruction_->IsInvokeVirtual()) && 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
@@ -685,9 +680,7 @@
            (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.
+    // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
     DCHECK(!(instruction_->IsArrayGet() &&
              instruction_->AsArrayGet()->GetArray()->IsIntermediateAddress()));
 
@@ -1990,6 +1983,8 @@
 }
 
 void LocationsBuilderARM64::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);
   locations->SetInAt(0, Location::RequiresRegister());
@@ -1997,7 +1992,10 @@
   locations->SetOut(Location::RequiresRegister());
 }
 
-void InstructionCodeGeneratorARM64::VisitIntermediateAddress(HIntermediateAddress* instruction) {
+void InstructionCodeGeneratorARM64::VisitIntermediateAddress(
+    HIntermediateAddress* instruction) {
+  // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
+  DCHECK(!kEmitCompilerReadBarrier);
   __ Add(OutputRegister(instruction),
          InputRegisterAt(instruction, 0),
          Operand(InputOperandAt(instruction, 1)));
@@ -2093,15 +2091,11 @@
   // Block pools between `Load` and `MaybeRecordImplicitNullCheck`.
   BlockPoolsScope block_pools(masm);
 
-  // The read barrier instrumentation of object ArrayGet instructions
-  // does not support the HIntermediateAddress instruction.
-  DCHECK(!((type == Primitive::kPrimNot) &&
-           instruction->GetArray()->IsIntermediateAddress() &&
-           kEmitCompilerReadBarrier));
-
   if (type == Primitive::kPrimNot && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
     // Object ArrayGet with Baker's read barrier case.
     Register temp = temps.AcquireW();
+    // The read barrier instrumentation does not support the HIntermediateAddress instruction yet.
+    DCHECK(!instruction->GetArray()->IsIntermediateAddress());
     // Note that a potential implicit null check is handled in the
     // CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier call.
     codegen_->GenerateArrayLoadWithBakerReadBarrier(
@@ -2115,6 +2109,9 @@
     } else {
       Register temp = temps.AcquireSameSizeAs(obj);
       if (instruction->GetArray()->IsIntermediateAddress()) {
+        // The read barrier instrumentation does not support the
+        // HIntermediateAddress instruction yet.
+        DCHECK(!kEmitCompilerReadBarrier);
         // We do not need to compute the intermediate address from the array: the
         // input instruction has done it already. See the comment in
         // `TryExtractArrayAccessAddress()`.
@@ -2204,6 +2201,9 @@
       UseScratchRegisterScope temps(masm);
       Register temp = temps.AcquireSameSizeAs(array);
       if (instruction->GetArray()->IsIntermediateAddress()) {
+        // The read barrier instrumentation does not support the
+        // HIntermediateAddress instruction yet.
+        DCHECK(!kEmitCompilerReadBarrier);
         // We do not need to compute the intermediate address from the array: the
         // input instruction has done it already. See the comment in
         // `TryExtractArrayAccessAddress()`.
@@ -2223,6 +2223,7 @@
     codegen_->Store(value_type, value, destination);
     codegen_->MaybeRecordImplicitNullCheck(instruction);
   } else {
+    DCHECK(needs_write_barrier);
     DCHECK(!instruction->GetArray()->IsIntermediateAddress());
     vixl::aarch64::Label done;
     SlowPathCodeARM64* slow_path = nullptr;
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index 1b5fa85..921ce10 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -27,11 +27,11 @@
 #include "utils/arm64/assembler_arm64.h"
 #include "utils/type_reference.h"
 
-// TODO: make vixl clean wrt -Wshadow.
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wshadow"
-#include "a64/disasm-a64.h"
-#include "a64/macro-assembler-a64.h"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h
index af0ee4e..cc949c5 100644
--- a/compiler/optimizing/common_arm64.h
+++ b/compiler/optimizing/common_arm64.h
@@ -22,8 +22,13 @@
 #include "nodes.h"
 #include "utils/arm64/assembler_arm64.h"
 
-#include "a64/disasm-a64.h"
-#include "a64/macro-assembler-a64.h"
+// TODO(VIXL): Make VIXL compile with -Wshadow.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
+#include "aarch64/simulator-aarch64.h"
+#pragma GCC diagnostic pop
 
 namespace art {
 namespace arm64 {
diff --git a/compiler/optimizing/instruction_simplifier_shared.cc b/compiler/optimizing/instruction_simplifier_shared.cc
index 6632cd9..8f7778f 100644
--- a/compiler/optimizing/instruction_simplifier_shared.cc
+++ b/compiler/optimizing/instruction_simplifier_shared.cc
@@ -231,6 +231,15 @@
                                   HInstruction* array,
                                   HInstruction* index,
                                   size_t data_offset) {
+  if (kEmitCompilerReadBarrier) {
+    // The read barrier instrumentation does not support the
+    // HIntermediateAddress instruction yet.
+    //
+    // TODO: Handle this case properly in the ARM64 and ARM code generator and
+    // re-enable this optimization; otherwise, remove this TODO.
+    // b/26601270
+    return false;
+  }
   if (index->IsConstant() ||
       (index->IsBoundsCheck() && index->AsBoundsCheck()->GetIndex()->IsConstant())) {
     // When the index is a constant all the addressing can be fitted in the
@@ -242,13 +251,6 @@
     // The access may require a runtime call or the original array pointer.
     return false;
   }
-  if (kEmitCompilerReadBarrier &&
-      access->IsArrayGet() &&
-      access->AsArrayGet()->GetType() == Primitive::kPrimNot) {
-    // For object arrays, the read barrier instrumentation requires
-    // the original array pointer.
-    return false;
-  }
 
   // Proceed to extract the base address computation.
   HGraph* graph = access->GetBlock()->GetGraph();
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index e233672..9cfe3ce 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -29,11 +29,11 @@
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
-// TODO: make vixl clean wrt -Wshadow.
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wshadow"
-#include "a64/disasm-a64.h"
-#include "a64/macro-assembler-a64.h"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/compiler/utils/arm64/assembler_arm64.cc b/compiler/utils/arm64/assembler_arm64.cc
index 22221e7..19450b3 100644
--- a/compiler/utils/arm64/assembler_arm64.cc
+++ b/compiler/utils/arm64/assembler_arm64.cc
@@ -36,7 +36,7 @@
 }
 
 size_t Arm64Assembler::CodeSize() const {
-  return vixl_masm_.GetBufferCapacity() - vixl_masm_.GetRemainingBufferSpace();
+  return vixl_masm_.GetSizeOfCodeGenerated();
 }
 
 const uint8_t* Arm64Assembler::CodeBufferBaseAddress() const {
diff --git a/compiler/utils/arm64/assembler_arm64.h b/compiler/utils/arm64/assembler_arm64.h
index 4e88e64..2847cb8 100644
--- a/compiler/utils/arm64/assembler_arm64.h
+++ b/compiler/utils/arm64/assembler_arm64.h
@@ -27,13 +27,11 @@
 #include "utils/assembler.h"
 #include "offsets.h"
 
-// TODO: make vixl clean wrt -Wshadow, -Wunknown-pragmas, -Wmissing-noreturn
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunknown-pragmas"
 #pragma GCC diagnostic ignored "-Wshadow"
-#pragma GCC diagnostic ignored "-Wmissing-noreturn"
-#include "a64/disasm-a64.h"
-#include "a64/macro-assembler-a64.h"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.h b/compiler/utils/arm64/jni_macro_assembler_arm64.h
index 79ee441..b9f6854 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.h
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.h
@@ -29,12 +29,10 @@
 #include "utils/jni_macro_assembler.h"
 #include "offsets.h"
 
-// TODO: make vixl clean wrt -Wshadow, -Wunknown-pragmas, -Wmissing-noreturn
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunknown-pragmas"
 #pragma GCC diagnostic ignored "-Wshadow"
-#pragma GCC diagnostic ignored "-Wmissing-noreturn"
-#include "a64/macro-assembler-a64.h"
+#include "aarch64/macro-assembler-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/disassembler/disassembler_arm64.h b/disassembler/disassembler_arm64.h
index c64d8ea..7c64792 100644
--- a/disassembler/disassembler_arm64.h
+++ b/disassembler/disassembler_arm64.h
@@ -19,10 +19,11 @@
 
 #include "disassembler.h"
 
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wshadow"
-#include "a64/decoder-a64.h"
-#include "a64/disasm-a64.h"
+#include "aarch64/decoder-aarch64.h"
+#include "aarch64/disasm-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc
index 0e2a672..492a12d 100644
--- a/runtime/arch/arm/entrypoints_init_arm.cc
+++ b/runtime/arch/arm/entrypoints_init_arm.cc
@@ -133,7 +133,7 @@
   qpoints->pReadBarrierMarkReg09 = art_quick_read_barrier_mark_reg09;
   qpoints->pReadBarrierMarkReg10 = art_quick_read_barrier_mark_reg10;
   qpoints->pReadBarrierMarkReg11 = art_quick_read_barrier_mark_reg11;
-  qpoints->pReadBarrierMarkReg12 = art_quick_read_barrier_mark_reg12;
+  qpoints->pReadBarrierMarkReg12 = nullptr;  // Cannot use register 12 (IP) to pass arguments.
   qpoints->pReadBarrierMarkReg13 = nullptr;  // Cannot use register 13 (SP) to pass arguments.
   qpoints->pReadBarrierMarkReg14 = nullptr;  // Cannot use register 14 (LR) to pass arguments.
   qpoints->pReadBarrierMarkReg15 = nullptr;  // Cannot use register 15 (PC) to pass arguments.
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 3d0da80..c4ec726 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1246,9 +1246,15 @@
     ldr    r2, [r2, r0, lsl #COMPRESSED_REFERENCE_SIZE_SHIFT]
                                                               // Read barrier for class load.
     ldr    r3, [r9, #THREAD_IS_GC_MARKING_OFFSET]
-    cbnz   r3, .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path
+    cbnz   r3, .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking
 .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit:
     ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path
+.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking:
+    cbz    r2, .Lart_quick_alloc_object_region_tlab_slow_path  // Null check for loading lock word.
+    // Check lock word for mark bit, if marked do the allocation.
+    ldr r3, [r2, MIRROR_OBJECT_LOCK_WORD_OFFSET]
+    ands r3, #LOCK_WORD_MARK_BIT_MASK_SHIFTED
+    bne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit
 .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path:
                                                               // The read barrier slow path. Mark
                                                               // the class.
@@ -1817,6 +1823,39 @@
     pop   {pc}
 END art_quick_l2f
 
+.macro CONDITIONAL_CBZ reg, reg_if, dest
+.ifc \reg, \reg_if
+    cbz \reg, \dest
+.endif
+.endm
+
+.macro CONDITIONAL_CMPBZ reg, reg_if, dest
+.ifc \reg, \reg_if
+    cmp \reg, #0
+    beq \dest
+.endif
+.endm
+
+// Use CBZ if the register is in {r0, r7} otherwise compare and branch.
+.macro SMART_CBZ reg, dest
+    CONDITIONAL_CBZ \reg, r0, \dest
+    CONDITIONAL_CBZ \reg, r1, \dest
+    CONDITIONAL_CBZ \reg, r2, \dest
+    CONDITIONAL_CBZ \reg, r3, \dest
+    CONDITIONAL_CBZ \reg, r4, \dest
+    CONDITIONAL_CBZ \reg, r5, \dest
+    CONDITIONAL_CBZ \reg, r6, \dest
+    CONDITIONAL_CBZ \reg, r7, \dest
+    CONDITIONAL_CMPBZ \reg, r8, \dest
+    CONDITIONAL_CMPBZ \reg, r9, \dest
+    CONDITIONAL_CMPBZ \reg, r10, \dest
+    CONDITIONAL_CMPBZ \reg, r11, \dest
+    CONDITIONAL_CMPBZ \reg, r12, \dest
+    CONDITIONAL_CMPBZ \reg, r13, \dest
+    CONDITIONAL_CMPBZ \reg, r14, \dest
+    CONDITIONAL_CMPBZ \reg, r15, \dest
+.endm
+
     /*
      * Create a function `name` calling the ReadBarrier::Mark routine,
      * getting its argument and returning its result through register
@@ -1835,28 +1874,25 @@
 .macro READ_BARRIER_MARK_REG name, reg
 ENTRY \name
     // Null check so that we can load the lock word.
-    cmp \reg, #0
-    beq .Lret_rb_\name
-    // Check lock word for mark bit, if marked return.
-    push {r0}
-    ldr r0, [\reg, MIRROR_OBJECT_LOCK_WORD_OFFSET]
-    and r0, #LOCK_WORD_MARK_BIT_MASK_SHIFTED
-    cbz r0, .Lslow_rb_\name
-    // Restore LR and return.
-    pop   {r0}
-    bx    lr
+    SMART_CBZ \reg, .Lret_rb_\name
+    // Check lock word for mark bit, if marked return. Use IP for scratch since it is blocked.
+    ldr ip, [\reg, MIRROR_OBJECT_LOCK_WORD_OFFSET]
+    ands ip, #LOCK_WORD_MARK_BIT_MASK_SHIFTED
+    beq .Lslow_rb_\name
+    // Already marked, return right away.
+    bx lr
 
 .Lslow_rb_\name:
-    pop   {r0}
-    push  {r0-r4, r9, r12, lr}          @ save return address and core caller-save registers
+    push  {r0-r5, r9, lr}               @ save return address and core caller-save registers
+                                        @ also save callee save r5 for 16 byte alignment
     .cfi_adjust_cfa_offset 32
     .cfi_rel_offset r0, 0
     .cfi_rel_offset r1, 4
     .cfi_rel_offset r2, 8
     .cfi_rel_offset r3, 12
     .cfi_rel_offset r4, 16
-    .cfi_rel_offset r9, 20
-    .cfi_rel_offset r12, 24
+    .cfi_rel_offset r5, 20
+    .cfi_rel_offset r9, 24
     .cfi_rel_offset lr, 28
     vpush {s0-s15}                      @ save floating-point caller-save registers
     .cfi_adjust_cfa_offset 64
@@ -1865,48 +1901,11 @@
       mov   r0, \reg                    @ pass arg1 - obj from `reg`
     .endif
     bl    artReadBarrierMark            @ r0 <- artReadBarrierMark(obj)
-
+    mov ip, r0                          @ Save result in IP
     vpop {s0-s15}                       @ restore floating-point registers
     .cfi_adjust_cfa_offset -64
-    @ If `reg` is a caller-save register, save the result to its
-    @ corresponding stack slot; it will be restored by the "pop"
-    @ instruction below. Otherwise, move result into `reg`.
-    @
-    @ (Note that saving `reg` to its stack slot will overwrite the value
-    @ previously stored by the "push" instruction above. That is
-    @ alright, as in that case we know that `reg` is not a live
-    @ register, as it is used to pass the argument and return the result
-    @ of this function.)
-    .ifc \reg, r0
-      PUSH_REG r0, 0                    @ copy result to r0's stack location
-    .else
-      .ifc \reg, r1
-        PUSH_REG r0, 4                  @ copy result to r1's stack location
-      .else
-        .ifc \reg, r2
-          PUSH_REG r0, 8                @ copy result to r2's stack location
-        .else
-          .ifc \reg, r3
-            PUSH_REG r0, 12             @ copy result to r3's stack location
-          .else
-            .ifc \reg, r4
-              PUSH_REG r0, 16           @ copy result to r4's stack location
-            .else
-              .ifc \reg, r9
-                PUSH_REG r0, 20         @ copy result to r9's stack location
-              .else
-                .ifc \reg, r12
-                  PUSH_REG r0, 24       @ copy result to r12's stack location
-                .else
-                  mov   \reg, r0        @ return result into `reg`
-                .endif
-              .endif
-            .endif
-          .endif
-        .endif
-      .endif
-    .endif
-    pop   {r0-r4, r9, r12, pc}          @ restore caller-save registers and return
+    pop   {r0-r5, r9, lr}               @ restore caller-save registers
+    mov \reg, ip                        @ copy result to reg
 .Lret_rb_\name:
     bx lr
 END \name
@@ -1924,4 +1923,3 @@
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg09, r9
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg10, r10
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg11, r11
-READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg12, r12
diff --git a/runtime/arch/arm64/entrypoints_init_arm64.cc b/runtime/arch/arm64/entrypoints_init_arm64.cc
index cc5bf29..55b09c3 100644
--- a/runtime/arch/arm64/entrypoints_init_arm64.cc
+++ b/runtime/arch/arm64/entrypoints_init_arm64.cc
@@ -149,7 +149,7 @@
   qpoints->pReadBarrierMarkReg13 = art_quick_read_barrier_mark_reg13;
   qpoints->pReadBarrierMarkReg14 = art_quick_read_barrier_mark_reg14;
   qpoints->pReadBarrierMarkReg15 = art_quick_read_barrier_mark_reg15;
-  qpoints->pReadBarrierMarkReg16 = art_quick_read_barrier_mark_reg16;
+  qpoints->pReadBarrierMarkReg16 = nullptr;  // IP0 is used as a temp by the asm stub.
   qpoints->pReadBarrierMarkReg17 = art_quick_read_barrier_mark_reg17;
   qpoints->pReadBarrierMarkReg18 = art_quick_read_barrier_mark_reg18;
   qpoints->pReadBarrierMarkReg19 = art_quick_read_barrier_mark_reg19;
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 35f5c56..4289cab 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2751,7 +2751,7 @@
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg13, w13, x13
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg14, w14, x14
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg15, w15, x15
-READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg16, w16, x16
+// READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg16, w16, x16 ip0 is blocked
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg17, w17, x17
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg18, w18, x18
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg19, w19, x19
diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h
index 743fbb9..d717ec0 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -137,7 +137,20 @@
   }
 
   bool GetCachedImageFile(/*out*/std::string* image, std::string* error_msg) const {
-    std::string cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA), true);
+    std::string cache;
+    bool have_android_data;
+    bool dalvik_cache_exists;
+    bool is_global_cache;
+    GetDalvikCache(GetInstructionSetString(kRuntimeISA),
+                   true,
+                   &cache,
+                   &have_android_data,
+                   &dalvik_cache_exists,
+                   &is_global_cache);
+    if (!dalvik_cache_exists) {
+      *error_msg = "Failed to create dalvik cache";
+      return false;
+    }
     return GetDalvikCacheFilename(GetImageLocation().c_str(), cache.c_str(), image, error_msg);
   }
 
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index b574c3b..aa3bf61 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -297,12 +297,13 @@
     for (size_t index = 0; index < image_file_names.size(); ++index) {
       std::string& image_name = image_file_names[index];
       std::string error_msg;
-      space::ImageSpace* boot_image_space = space::ImageSpace::CreateBootImage(
+      std::unique_ptr<space::ImageSpace> boot_image_space_uptr = space::ImageSpace::CreateBootImage(
           image_name.c_str(),
           image_instruction_set,
           index > 0,
           &error_msg);
-      if (boot_image_space != nullptr) {
+      if (boot_image_space_uptr != nullptr) {
+        space::ImageSpace* boot_image_space = boot_image_space_uptr.release();
         AddSpace(boot_image_space);
         added_image_spaces.push_back(boot_image_space);
         // Oat files referenced by image files immediately follow them in memory, ensure alloc space
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 6fcad29..4505c24 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -79,7 +79,12 @@
   return r;
 }
 
-static bool GenerateImage(const std::string& image_filename, InstructionSet image_isa,
+static int32_t ChooseRelocationOffsetDelta() {
+  return ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA, ART_BASE_ADDRESS_MAX_DELTA);
+}
+
+static bool GenerateImage(const std::string& image_filename,
+                          InstructionSet image_isa,
                           std::string* error_msg) {
   const std::string boot_class_path_string(Runtime::Current()->GetBootClassPathString());
   std::vector<std::string> boot_class_path;
@@ -118,8 +123,7 @@
   CHECK_EQ(image_isa, kRuntimeISA)
       << "We should always be generating an image for the current isa.";
 
-  int32_t base_offset = ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA,
-                                                    ART_BASE_ADDRESS_MAX_DELTA);
+  int32_t base_offset = ChooseRelocationOffsetDelta();
   LOG(INFO) << "Using an offset of 0x" << std::hex << base_offset << " from default "
             << "art base address of 0x" << std::hex << ART_BASE_ADDRESS;
   arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS + base_offset));
@@ -138,14 +142,17 @@
   return Exec(arg_vector, error_msg);
 }
 
-bool ImageSpace::FindImageFilename(const char* image_location,
-                                   const InstructionSet image_isa,
-                                   std::string* system_filename,
-                                   bool* has_system,
-                                   std::string* cache_filename,
-                                   bool* dalvik_cache_exists,
-                                   bool* has_cache,
-                                   bool* is_global_cache) {
+static bool FindImageFilenameImpl(const char* image_location,
+                                  const InstructionSet image_isa,
+                                  bool* has_system,
+                                  std::string* system_filename,
+                                  bool* dalvik_cache_exists,
+                                  std::string* dalvik_cache,
+                                  bool* is_global_cache,
+                                  bool* has_cache,
+                                  std::string* cache_filename) {
+  DCHECK(dalvik_cache != nullptr);
+
   *has_system = false;
   *has_cache = false;
   // image_location = /system/framework/boot.art
@@ -158,9 +165,12 @@
 
   bool have_android_data = false;
   *dalvik_cache_exists = false;
-  std::string dalvik_cache;
-  GetDalvikCache(GetInstructionSetString(image_isa), true, &dalvik_cache,
-                 &have_android_data, dalvik_cache_exists, is_global_cache);
+  GetDalvikCache(GetInstructionSetString(image_isa),
+                 true,
+                 dalvik_cache,
+                 &have_android_data,
+                 dalvik_cache_exists,
+                 is_global_cache);
 
   if (have_android_data && *dalvik_cache_exists) {
     // Always set output location even if it does not exist,
@@ -169,7 +179,10 @@
     // image_location = /system/framework/boot.art
     // *image_filename = /data/dalvik-cache/<image_isa>/boot.art
     std::string error_msg;
-    if (!GetDalvikCacheFilename(image_location, dalvik_cache.c_str(), cache_filename, &error_msg)) {
+    if (!GetDalvikCacheFilename(image_location,
+                                dalvik_cache->c_str(),
+                                cache_filename,
+                                &error_msg)) {
       LOG(WARNING) << error_msg;
       return *has_system;
     }
@@ -178,6 +191,26 @@
   return *has_system || *has_cache;
 }
 
+bool ImageSpace::FindImageFilename(const char* image_location,
+                                   const InstructionSet image_isa,
+                                   std::string* system_filename,
+                                   bool* has_system,
+                                   std::string* cache_filename,
+                                   bool* dalvik_cache_exists,
+                                   bool* has_cache,
+                                   bool* is_global_cache) {
+  std::string dalvik_cache_unused;
+  return FindImageFilenameImpl(image_location,
+                               image_isa,
+                               has_system,
+                               system_filename,
+                               dalvik_cache_exists,
+                               &dalvik_cache_unused,
+                               is_global_cache,
+                               has_cache,
+                               cache_filename);
+}
+
 static bool ReadSpecificImageHeader(const char* filename, ImageHeader* image_header) {
     std::unique_ptr<File> image_file(OS::OpenFileForReading(filename));
     if (image_file.get() == nullptr) {
@@ -191,8 +224,10 @@
 }
 
 // Relocate the image at image_location to dest_filename and relocate it by a random amount.
-static bool RelocateImage(const char* image_location, const char* dest_filename,
-                               InstructionSet isa, std::string* error_msg) {
+static bool RelocateImage(const char* image_location,
+                          const char* dest_filename,
+                          InstructionSet isa,
+                          std::string* error_msg) {
   // We should clean up so we are more likely to have room for the image.
   if (Runtime::Current()->IsZygote()) {
     LOG(INFO) << "Pruning dalvik-cache since we are relocating an image and will need to recompile";
@@ -211,8 +246,7 @@
   instruction_set_arg += GetInstructionSetString(isa);
 
   std::string base_offset_arg("--base-offset-delta=");
-  StringAppendF(&base_offset_arg, "%d", ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA,
-                                                                    ART_BASE_ADDRESS_MAX_DELTA));
+  StringAppendF(&base_offset_arg, "%d", ChooseRelocationOffsetDelta());
 
   std::vector<std::string> argv;
   argv.push_back(patchoat);
@@ -237,16 +271,6 @@
   return hdr.release();
 }
 
-ImageHeader* ImageSpace::ReadImageHeaderOrDie(const char* image_location,
-                                              const InstructionSet image_isa) {
-  std::string error_msg;
-  ImageHeader* image_header = ReadImageHeader(image_location, image_isa, &error_msg);
-  if (image_header == nullptr) {
-    LOG(FATAL) << error_msg;
-  }
-  return image_header;
-}
-
 ImageHeader* ImageSpace::ReadImageHeader(const char* image_location,
                                          const InstructionSet image_isa,
                                          std::string* error_msg) {
@@ -312,11 +336,31 @@
   return nullptr;
 }
 
-static bool ChecksumsMatch(const char* image_a, const char* image_b) {
+static bool ChecksumsMatch(const char* image_a, const char* image_b, std::string* error_msg) {
+  DCHECK(error_msg != nullptr);
+
   ImageHeader hdr_a;
   ImageHeader hdr_b;
-  return ReadSpecificImageHeader(image_a, &hdr_a) && ReadSpecificImageHeader(image_b, &hdr_b)
-      && hdr_a.GetOatChecksum() == hdr_b.GetOatChecksum();
+
+  if (!ReadSpecificImageHeader(image_a, &hdr_a)) {
+    *error_msg = StringPrintf("Cannot read header of %s", image_a);
+    return false;
+  }
+  if (!ReadSpecificImageHeader(image_b, &hdr_b)) {
+    *error_msg = StringPrintf("Cannot read header of %s", image_b);
+    return false;
+  }
+
+  if (hdr_a.GetOatChecksum() != hdr_b.GetOatChecksum()) {
+    *error_msg = StringPrintf("Checksum mismatch: %u(%s) vs %u(%s)",
+                              hdr_a.GetOatChecksum(),
+                              image_a,
+                              hdr_b.GetOatChecksum(),
+                              image_b);
+    return false;
+  }
+
+  return true;
 }
 
 static bool ImageCreationAllowed(bool is_global_cache, std::string* error_msg) {
@@ -334,256 +378,6 @@
   return false;
 }
 
-static constexpr uint64_t kLowSpaceValue = 50 * MB;
-static constexpr uint64_t kTmpFsSentinelValue = 384 * MB;
-
-// Read the free space of the cache partition and make a decision whether to keep the generated
-// image. This is to try to mitigate situations where the system might run out of space later.
-static bool CheckSpace(const std::string& cache_filename, std::string* error_msg) {
-  // Using statvfs vs statvfs64 because of b/18207376, and it is enough for all practical purposes.
-  struct statvfs buf;
-
-  int res = TEMP_FAILURE_RETRY(statvfs(cache_filename.c_str(), &buf));
-  if (res != 0) {
-    // Could not stat. Conservatively tell the system to delete the image.
-    *error_msg = "Could not stat the filesystem, assuming low-memory situation.";
-    return false;
-  }
-
-  uint64_t fs_overall_size = buf.f_bsize * static_cast<uint64_t>(buf.f_blocks);
-  // Zygote is privileged, but other things are not. Use bavail.
-  uint64_t fs_free_size = buf.f_bsize * static_cast<uint64_t>(buf.f_bavail);
-
-  // Take the overall size as an indicator for a tmpfs, which is being used for the decryption
-  // environment. We do not want to fail quickening the boot image there, as it is beneficial
-  // for time-to-UI.
-  if (fs_overall_size > kTmpFsSentinelValue) {
-    if (fs_free_size < kLowSpaceValue) {
-      *error_msg = StringPrintf("Low-memory situation: only %4.2f megabytes available after image"
-                                " generation, need at least %" PRIu64 ".",
-                                static_cast<double>(fs_free_size) / MB,
-                                kLowSpaceValue / MB);
-      return false;
-    }
-  }
-  return true;
-}
-
-ImageSpace* ImageSpace::CreateBootImage(const char* image_location,
-                                        const InstructionSet image_isa,
-                                        bool secondary_image,
-                                        std::string* error_msg) {
-  ScopedTrace trace(__FUNCTION__);
-  std::string system_filename;
-  bool has_system = false;
-  std::string cache_filename;
-  bool has_cache = false;
-  bool dalvik_cache_exists = false;
-  bool is_global_cache = true;
-  bool found_image = FindImageFilename(image_location, image_isa, &system_filename,
-                                       &has_system, &cache_filename, &dalvik_cache_exists,
-                                       &has_cache, &is_global_cache);
-
-  const bool is_zygote = Runtime::Current()->IsZygote();
-  if (is_zygote && !secondary_image) {
-    MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
-  }
-
-  ImageSpace* space;
-  bool relocate = Runtime::Current()->ShouldRelocate();
-  bool can_compile = Runtime::Current()->IsImageDex2OatEnabled();
-  if (found_image) {
-    const std::string* image_filename;
-    bool is_system = false;
-    bool relocated_version_used = false;
-    if (relocate) {
-      if (!dalvik_cache_exists) {
-        *error_msg = StringPrintf("Requiring relocation for image '%s' at '%s' but we do not have "
-                                  "any dalvik_cache to find/place it in.",
-                                  image_location, system_filename.c_str());
-        return nullptr;
-      }
-      if (has_system) {
-        if (has_cache && ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) {
-          // We already have a relocated version
-          image_filename = &cache_filename;
-          relocated_version_used = true;
-        } else {
-          // We cannot have a relocated version, Relocate the system one and use it.
-
-          std::string reason;
-          bool success;
-
-          // Check whether we are allowed to relocate.
-          if (!can_compile) {
-            reason = "Image dex2oat disabled by -Xnoimage-dex2oat.";
-            success = false;
-          } else if (!ImageCreationAllowed(is_global_cache, &reason)) {
-            // Whether we can write to the cache.
-            success = false;
-          } else if (secondary_image) {
-            if (is_zygote) {
-              // Secondary image is out of date. Clear cache and exit to let it retry from scratch.
-              LOG(ERROR) << "Cannot patch secondary image '" << image_location
-                         << "', clearing dalvik_cache and restarting zygote.";
-              PruneDalvikCache(image_isa);
-              _exit(1);
-            } else {
-              reason = "Should not have to patch secondary image.";
-              success = false;
-            }
-          } else {
-            // Try to relocate.
-            success = RelocateImage(image_location, cache_filename.c_str(), image_isa, &reason);
-          }
-
-          if (success) {
-            relocated_version_used = true;
-            image_filename = &cache_filename;
-          } else {
-            *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s': %s",
-                                      image_location, system_filename.c_str(),
-                                      cache_filename.c_str(), reason.c_str());
-            // We failed to create files, remove any possibly garbage output.
-            // Since ImageCreationAllowed was true above, we are the zygote
-            // and therefore the only process expected to generate these for
-            // the device.
-            PruneDalvikCache(image_isa);
-            return nullptr;
-          }
-        }
-      } else {
-        CHECK(has_cache);
-        // We can just use cache's since it should be fine. This might or might not be relocated.
-        image_filename = &cache_filename;
-      }
-    } else {
-      if (has_system && has_cache) {
-        // Check they have the same cksum. If they do use the cache. Otherwise system.
-        if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) {
-          image_filename = &cache_filename;
-          relocated_version_used = true;
-        } else {
-          image_filename = &system_filename;
-          is_system = true;
-        }
-      } else if (has_system) {
-        image_filename = &system_filename;
-        is_system = true;
-      } else {
-        CHECK(has_cache);
-        image_filename = &cache_filename;
-      }
-    }
-    {
-      // Note that we must not use the file descriptor associated with
-      // ScopedFlock::GetFile to Init the image file. We want the file
-      // descriptor (and the associated exclusive lock) to be released when
-      // we leave Create.
-      ScopedFlock image_lock;
-      // Should this be a RDWR lock? This is only a defensive measure, as at
-      // this point the image should exist.
-      // However, only the zygote can write into the global dalvik-cache, so
-      // restrict to zygote processes, or any process that isn't using
-      // /data/dalvik-cache (which we assume to be allowed to write there).
-      const bool rw_lock = is_zygote || !is_global_cache;
-      image_lock.Init(image_filename->c_str(),
-                      rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */,
-                      true /* block */,
-                      error_msg);
-      VLOG(startup) << "Using image file " << image_filename->c_str() << " for image location "
-                    << image_location;
-      // If we are in /system we can assume the image is good. We can also
-      // assume this if we are using a relocated image (i.e. image checksum
-      // matches) since this is only different by the offset. We need this to
-      // make sure that host tests continue to work.
-      // Since we are the boot image, pass null since we load the oat file from the boot image oat
-      // file name.
-      space = ImageSpace::Init(image_filename->c_str(),
-                               image_location,
-                               !(is_system || relocated_version_used),
-                               /* oat_file */nullptr,
-                               error_msg);
-    }
-    if (space != nullptr) {
-      // Check whether there is enough space left over in the data partition. Even if we can load
-      // the image, we need to be conservative, as some parts of the platform are not very tolerant
-      // of space constraints.
-      // ImageSpace doesn't know about the data partition per se, it relies on the FindImageFilename
-      // helper (which relies on GetDalvikCache). So for now, if we load an image out of /system,
-      // ignore the check (as it would test for free space in /system instead).
-      if (!is_system && !CheckSpace(*image_filename, error_msg)) {
-        // No. Delete the generated image and try to run out of the dex files.
-        PruneDalvikCache(image_isa);
-        return nullptr;
-      }
-      return space;
-    }
-
-    if (relocated_version_used) {
-      // Something is wrong with the relocated copy (even though checksums match). Cleanup.
-      // This can happen if the .oat is corrupt, since the above only checks the .art checksums.
-      // TODO: Check the oat file validity earlier.
-      *error_msg = StringPrintf("Attempted to use relocated version of %s at %s generated from %s "
-                                "but image failed to load: %s",
-                                image_location, cache_filename.c_str(), system_filename.c_str(),
-                                error_msg->c_str());
-      PruneDalvikCache(image_isa);
-      return nullptr;
-    } else if (is_system) {
-      // If the /system file exists, it should be up-to-date, don't try to generate it.
-      *error_msg = StringPrintf("Failed to load /system image '%s': %s",
-                                image_filename->c_str(), error_msg->c_str());
-      return nullptr;
-    } else {
-      // Otherwise, log a warning and fall through to GenerateImage.
-      LOG(WARNING) << *error_msg;
-    }
-  }
-
-  if (!can_compile) {
-    *error_msg = "Not attempting to compile image because -Xnoimage-dex2oat";
-    return nullptr;
-  } else if (!dalvik_cache_exists) {
-    *error_msg = StringPrintf("No place to put generated image.");
-    return nullptr;
-  } else if (!ImageCreationAllowed(is_global_cache, error_msg)) {
-    return nullptr;
-  } else if (secondary_image) {
-    *error_msg = "Cannot compile a secondary image.";
-    return nullptr;
-  } else if (!GenerateImage(cache_filename, image_isa, error_msg)) {
-    *error_msg = StringPrintf("Failed to generate image '%s': %s",
-                              cache_filename.c_str(), error_msg->c_str());
-    // We failed to create files, remove any possibly garbage output.
-    // Since ImageCreationAllowed was true above, we are the zygote
-    // and therefore the only process expected to generate these for
-    // the device.
-    PruneDalvikCache(image_isa);
-    return nullptr;
-  } else {
-    // Check whether there is enough space left over after we have generated the image.
-    if (!CheckSpace(cache_filename, error_msg)) {
-      // No. Delete the generated image and try to run out of the dex files.
-      PruneDalvikCache(image_isa);
-      return nullptr;
-    }
-
-    // Note that we must not use the file descriptor associated with
-    // ScopedFlock::GetFile to Init the image file. We want the file
-    // descriptor (and the associated exclusive lock) to be released when
-    // we leave Create.
-    ScopedFlock image_lock;
-    image_lock.Init(cache_filename.c_str(), error_msg);
-    space = ImageSpace::Init(cache_filename.c_str(), image_location, true, nullptr, error_msg);
-    if (space == nullptr) {
-      *error_msg = StringPrintf("Failed to load generated image '%s': %s",
-                                cache_filename.c_str(), error_msg->c_str());
-    }
-    return space;
-  }
-}
-
 void ImageSpace::VerifyImageAllocations() {
   uint8_t* current = Begin() + RoundUp(sizeof(ImageHeader), kObjectAlignment);
   while (current < End()) {
@@ -652,865 +446,1168 @@
             << reinterpret_cast<const void*>(reloc.Dest() + reloc.Length()) << ")";
 }
 
-class FixupVisitor : public ValueObject {
+// Helper class encapsulating loading, so we can access private ImageSpace members (this is a
+// friend class), but not declare functions in the header.
+class ImageSpaceLoader {
  public:
-  FixupVisitor(const RelocationRange& boot_image,
-               const RelocationRange& boot_oat,
-               const RelocationRange& app_image,
-               const RelocationRange& app_oat)
-      : boot_image_(boot_image),
-        boot_oat_(boot_oat),
-        app_image_(app_image),
-        app_oat_(app_oat) {}
-
-  // Return the relocated address of a heap object.
-  template <typename T>
-  ALWAYS_INLINE T* ForwardObject(T* src) const {
-    const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src);
-    if (boot_image_.InSource(uint_src)) {
-      return reinterpret_cast<T*>(boot_image_.ToDest(uint_src));
-    }
-    if (app_image_.InSource(uint_src)) {
-      return reinterpret_cast<T*>(app_image_.ToDest(uint_src));
-    }
-    // Since we are fixing up the app image, there should only be pointers to the app image and
-    // boot image.
-    DCHECK(src == nullptr) << reinterpret_cast<const void*>(src);
-    return src;
-  }
-
-  // Return the relocated address of a code pointer (contained by an oat file).
-  ALWAYS_INLINE const void* ForwardCode(const void* src) const {
-    const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src);
-    if (boot_oat_.InSource(uint_src)) {
-      return reinterpret_cast<const void*>(boot_oat_.ToDest(uint_src));
-    }
-    if (app_oat_.InSource(uint_src)) {
-      return reinterpret_cast<const void*>(app_oat_.ToDest(uint_src));
-    }
-    DCHECK(src == nullptr) << src;
-    return src;
-  }
-
-  // Must be called on pointers that already have been relocated to the destination relocation.
-  ALWAYS_INLINE bool IsInAppImage(mirror::Object* object) const {
-    return app_image_.InDest(reinterpret_cast<uintptr_t>(object));
-  }
-
- protected:
-  // Source section.
-  const RelocationRange boot_image_;
-  const RelocationRange boot_oat_;
-  const RelocationRange app_image_;
-  const RelocationRange app_oat_;
-};
-
-// Adapt for mirror::Class::FixupNativePointers.
-class FixupObjectAdapter : public FixupVisitor {
- public:
-  template<typename... Args>
-  explicit FixupObjectAdapter(Args... args) : FixupVisitor(args...) {}
-
-  template <typename T>
-  T* operator()(T* obj) const {
-    return ForwardObject(obj);
-  }
-};
-
-class FixupRootVisitor : public FixupVisitor {
- public:
-  template<typename... Args>
-  explicit FixupRootVisitor(Args... args) : FixupVisitor(args...) {}
-
-  ALWAYS_INLINE void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+  static std::unique_ptr<ImageSpace> Load(const char* image_location,
+                                          const std::string& image_filename,
+                                          bool is_zygote,
+                                          bool is_global_cache,
+                                          bool is_system,
+                                          bool relocated_version_used,
+                                          std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_) {
-    if (!root->IsNull()) {
-      VisitRoot(root);
-    }
+    // Note that we must not use the file descriptor associated with
+    // ScopedFlock::GetFile to Init the image file. We want the file
+    // descriptor (and the associated exclusive lock) to be released when
+    // we leave Create.
+    ScopedFlock image_lock;
+    // Should this be a RDWR lock? This is only a defensive measure, as at
+    // this point the image should exist.
+    // However, only the zygote can write into the global dalvik-cache, so
+    // restrict to zygote processes, or any process that isn't using
+    // /data/dalvik-cache (which we assume to be allowed to write there).
+    const bool rw_lock = is_zygote || !is_global_cache;
+    image_lock.Init(image_filename.c_str(),
+                    rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */,
+                    true /* block */,
+                    error_msg);
+    VLOG(startup) << "Using image file " << image_filename.c_str() << " for image location "
+                  << image_location;
+    // If we are in /system we can assume the image is good. We can also
+    // assume this if we are using a relocated image (i.e. image checksum
+    // matches) since this is only different by the offset. We need this to
+    // make sure that host tests continue to work.
+    // Since we are the boot image, pass null since we load the oat file from the boot image oat
+    // file name.
+    return Init(image_filename.c_str(),
+                image_location,
+                !(is_system || relocated_version_used),
+                /* oat_file */nullptr,
+                error_msg);
   }
 
-  ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+  static std::unique_ptr<ImageSpace> Init(const char* image_filename,
+                                          const char* image_location,
+                                          bool validate_oat_file,
+                                          const OatFile* oat_file,
+                                          std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_) {
-    mirror::Object* ref = root->AsMirrorPtr();
-    mirror::Object* new_ref = ForwardObject(ref);
-    if (ref != new_ref) {
-      root->Assign(new_ref);
-    }
-  }
-};
+    CHECK(image_filename != nullptr);
+    CHECK(image_location != nullptr);
 
-class FixupObjectVisitor : public FixupVisitor {
- public:
-  template<typename... Args>
-  explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited,
-                              const PointerSize pointer_size,
-                              Args... args)
-      : FixupVisitor(args...),
-        pointer_size_(pointer_size),
-        visited_(visited) {}
+    TimingLogger logger(__PRETTY_FUNCTION__, true, VLOG_IS_ON(image));
+    VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename;
 
-  // Fix up separately since we also need to fix up method entrypoints.
-  ALWAYS_INLINE void VisitRootIfNonNull(
-      mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {}
-
-  ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED)
-      const {}
-
-  ALWAYS_INLINE void operator()(mirror::Object* obj,
-                                MemberOffset offset,
-                                bool is_static ATTRIBUTE_UNUSED) const
-      NO_THREAD_SAFETY_ANALYSIS {
-    // There could be overlap between ranges, we must avoid visiting the same reference twice.
-    // Avoid the class field since we already fixed it up in FixupClassVisitor.
-    if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) {
-      // Space is not yet added to the heap, don't do a read barrier.
-      mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
-          offset);
-      // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the
-      // image.
-      obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref));
-    }
-  }
-
-  // Visit a pointer array and forward corresponding native data. Ignores pointer arrays in the
-  // boot image. Uses the bitmap to ensure the same array is not visited multiple times.
-  template <typename Visitor>
-  void UpdatePointerArrayContents(mirror::PointerArray* array, const Visitor& visitor) const
-      NO_THREAD_SAFETY_ANALYSIS {
-    DCHECK(array != nullptr);
-    DCHECK(visitor.IsInAppImage(array));
-    // The bit for the array contents is different than the bit for the array. Since we may have
-    // already visited the array as a long / int array from walking the bitmap without knowing it
-    // was a pointer array.
-    static_assert(kObjectAlignment == 8u, "array bit may be in another object");
-    mirror::Object* const contents_bit = reinterpret_cast<mirror::Object*>(
-        reinterpret_cast<uintptr_t>(array) + kObjectAlignment);
-    // If the bit is not set then the contents have not yet been updated.
-    if (!visited_->Test(contents_bit)) {
-      array->Fixup<kVerifyNone, kWithoutReadBarrier>(array, pointer_size_, visitor);
-      visited_->Set(contents_bit);
-    }
-  }
-
-  // java.lang.ref.Reference visitor.
-  void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const
-      SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) {
-    mirror::Object* obj = ref->GetReferent<kWithoutReadBarrier>();
-    ref->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(
-        mirror::Reference::ReferentOffset(),
-        ForwardObject(obj));
-  }
-
-  void operator()(mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS {
-    if (visited_->Test(obj)) {
-      // Already visited.
-      return;
-    }
-    visited_->Set(obj);
-
-    // Handle class specially first since we need it to be updated to properly visit the rest of
-    // the instance fields.
+    std::unique_ptr<File> file;
     {
-      mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
-      DCHECK(klass != nullptr) << "Null class in image";
-      // No AsClass since our fields aren't quite fixed up yet.
-      mirror::Class* new_klass = down_cast<mirror::Class*>(ForwardObject(klass));
-      if (klass != new_klass) {
-        obj->SetClass<kVerifyNone>(new_klass);
+      TimingLogger::ScopedTiming timing("OpenImageFile", &logger);
+      file.reset(OS::OpenFileForReading(image_filename));
+      if (file == nullptr) {
+        *error_msg = StringPrintf("Failed to open '%s'", image_filename);
+        return nullptr;
       }
-      if (new_klass != klass && IsInAppImage(new_klass)) {
-        // Make sure the klass contents are fixed up since we depend on it to walk the fields.
-        operator()(new_klass);
+    }
+    ImageHeader temp_image_header;
+    ImageHeader* image_header = &temp_image_header;
+    {
+      TimingLogger::ScopedTiming timing("ReadImageHeader", &logger);
+      bool success = file->ReadFully(image_header, sizeof(*image_header));
+      if (!success || !image_header->IsValid()) {
+        *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
+        return nullptr;
+      }
+    }
+    // Check that the file is larger or equal to the header size + data size.
+    const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
+    if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
+      *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
+                                image_file_size,
+                                sizeof(ImageHeader) + image_header->GetDataSize());
+      return nullptr;
+    }
+
+    if (oat_file != nullptr) {
+      // If we have an oat file, check the oat file checksum. The oat file is only non-null for the
+      // app image case. Otherwise, we open the oat file after the image and check the checksum there.
+      const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
+      const uint32_t image_oat_checksum = image_header->GetOatChecksum();
+      if (oat_checksum != image_oat_checksum) {
+        *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
+                                  oat_checksum,
+                                  image_oat_checksum,
+                                  image_filename);
+        return nullptr;
       }
     }
 
-    obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>(
-        *this,
-        *this);
-    // Note that this code relies on no circular dependencies.
-    // We want to use our own class loader and not the one in the image.
-    if (obj->IsClass<kVerifyNone, kWithoutReadBarrier>()) {
-      mirror::Class* as_klass = obj->AsClass<kVerifyNone, kWithoutReadBarrier>();
-      FixupObjectAdapter visitor(boot_image_, boot_oat_, app_image_, app_oat_);
-      as_klass->FixupNativePointers<kVerifyNone, kWithoutReadBarrier>(as_klass,
-                                                                      pointer_size_,
-                                                                      visitor);
-      // Deal with the pointer arrays. Use the helper function since multiple classes can reference
-      // the same arrays.
-      mirror::PointerArray* const vtable = as_klass->GetVTable<kVerifyNone, kWithoutReadBarrier>();
-      if (vtable != nullptr && IsInAppImage(vtable)) {
-        operator()(vtable);
-        UpdatePointerArrayContents(vtable, visitor);
+    if (VLOG_IS_ON(startup)) {
+      LOG(INFO) << "Dumping image sections";
+      for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
+        const auto section_idx = static_cast<ImageHeader::ImageSections>(i);
+        auto& section = image_header->GetImageSection(section_idx);
+        LOG(INFO) << section_idx << " start="
+            << reinterpret_cast<void*>(image_header->GetImageBegin() + section.Offset()) << " "
+            << section;
       }
-      mirror::IfTable* iftable = as_klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>();
-      // Ensure iftable arrays are fixed up since we need GetMethodArray to return the valid
-      // contents.
-      if (iftable != nullptr && IsInAppImage(iftable)) {
-        operator()(iftable);
-        for (int32_t i = 0, count = iftable->Count(); i < count; ++i) {
-          if (iftable->GetMethodArrayCount<kVerifyNone, kWithoutReadBarrier>(i) > 0) {
-            mirror::PointerArray* methods =
-                iftable->GetMethodArray<kVerifyNone, kWithoutReadBarrier>(i);
-            if (visitor.IsInAppImage(methods)) {
-              operator()(methods);
-              DCHECK(methods != nullptr);
-              UpdatePointerArrayContents(methods, visitor);
+    }
+
+    const auto& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap);
+    // The location we want to map from is the first aligned page after the end of the stored
+    // (possibly compressed) data.
+    const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(),
+                                               kPageSize);
+    const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size();
+    if (end_of_bitmap != image_file_size) {
+      *error_msg = StringPrintf(
+          "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size,
+          end_of_bitmap);
+      return nullptr;
+    }
+
+    std::unique_ptr<MemMap> map;
+    // GetImageBegin is the preferred address to map the image. If we manage to map the
+    // image at the image begin, the amount of fixup work required is minimized.
+    map.reset(LoadImageFile(image_filename,
+                            image_location,
+                            *image_header,
+                            image_header->GetImageBegin(),
+                            file->Fd(),
+                            logger,
+                            error_msg));
+    // If the header specifies PIC mode, we can also map at a random low_4gb address since we can
+    // relocate in-place.
+    if (map == nullptr && image_header->IsPic()) {
+      map.reset(LoadImageFile(image_filename,
+                              image_location,
+                              *image_header,
+                              /* address */ nullptr,
+                              file->Fd(),
+                              logger,
+                              error_msg));
+    }
+    // Were we able to load something and continue?
+    if (map == nullptr) {
+      DCHECK(!error_msg->empty());
+      return nullptr;
+    }
+    DCHECK_EQ(0, memcmp(image_header, map->Begin(), sizeof(ImageHeader)));
+
+    std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr,
+                                                                      bitmap_section.Size(),
+                                                                      PROT_READ, MAP_PRIVATE,
+                                                                      file->Fd(),
+                                                                      image_bitmap_offset,
+                                                                      /*low_4gb*/false,
+                                                                      /*reuse*/false,
+                                                                      image_filename,
+                                                                      error_msg));
+    if (image_bitmap_map == nullptr) {
+      *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
+      return nullptr;
+    }
+    // Loaded the map, use the image header from the file now in case we patch it with
+    // RelocateInPlace.
+    image_header = reinterpret_cast<ImageHeader*>(map->Begin());
+    const uint32_t bitmap_index = ImageSpace::bitmap_index_.FetchAndAddSequentiallyConsistent(1);
+    std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
+                                         image_filename,
+                                         bitmap_index));
+    // Bitmap only needs to cover until the end of the mirror objects section.
+    const ImageSection& image_objects = image_header->GetImageSection(ImageHeader::kSectionObjects);
+    // We only want the mirror object, not the ArtFields and ArtMethods.
+    uint8_t* const image_end = map->Begin() + image_objects.End();
+    std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap;
+    {
+      TimingLogger::ScopedTiming timing("CreateImageBitmap", &logger);
+      bitmap.reset(
+          accounting::ContinuousSpaceBitmap::CreateFromMemMap(
+              bitmap_name,
+              image_bitmap_map.release(),
+              reinterpret_cast<uint8_t*>(map->Begin()),
+              image_objects.End()));
+      if (bitmap == nullptr) {
+        *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str());
+        return nullptr;
+      }
+    }
+    {
+      TimingLogger::ScopedTiming timing("RelocateImage", &logger);
+      if (!RelocateInPlace(*image_header,
+                           map->Begin(),
+                           bitmap.get(),
+                           oat_file,
+                           error_msg)) {
+        return nullptr;
+      }
+    }
+    // We only want the mirror object, not the ArtFields and ArtMethods.
+    std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
+                                                     image_location,
+                                                     map.release(),
+                                                     bitmap.release(),
+                                                     image_end));
+
+    // VerifyImageAllocations() will be called later in Runtime::Init()
+    // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_
+    // and ArtField::java_lang_reflect_ArtField_, which are used from
+    // Object::SizeOf() which VerifyImageAllocations() calls, are not
+    // set yet at this point.
+    if (oat_file == nullptr) {
+      TimingLogger::ScopedTiming timing("OpenOatFile", &logger);
+      space->oat_file_ = OpenOatFile(*space, image_filename, error_msg);
+      if (space->oat_file_ == nullptr) {
+        DCHECK(!error_msg->empty());
+        return nullptr;
+      }
+      space->oat_file_non_owned_ = space->oat_file_.get();
+    } else {
+      space->oat_file_non_owned_ = oat_file;
+    }
+
+    if (validate_oat_file) {
+      TimingLogger::ScopedTiming timing("ValidateOatFile", &logger);
+      CHECK(space->oat_file_ != nullptr);
+      if (!ValidateOatFile(*space, *space->oat_file_, error_msg)) {
+        DCHECK(!error_msg->empty());
+        return nullptr;
+      }
+    }
+
+    Runtime* runtime = Runtime::Current();
+
+    // If oat_file is null, then it is the boot image space. Use oat_file_non_owned_ from the space
+    // to set the runtime methods.
+    CHECK_EQ(oat_file != nullptr, image_header->IsAppImage());
+    if (image_header->IsAppImage()) {
+      CHECK_EQ(runtime->GetResolutionMethod(),
+               image_header->GetImageMethod(ImageHeader::kResolutionMethod));
+      CHECK_EQ(runtime->GetImtConflictMethod(),
+               image_header->GetImageMethod(ImageHeader::kImtConflictMethod));
+      CHECK_EQ(runtime->GetImtUnimplementedMethod(),
+               image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod));
+      CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveAllCalleeSaves),
+               image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod));
+      CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsOnly),
+               image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod));
+      CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsAndArgs),
+               image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod));
+      CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveEverything),
+               image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod));
+    } else if (!runtime->HasResolutionMethod()) {
+      runtime->SetInstructionSet(space->oat_file_non_owned_->GetOatHeader().GetInstructionSet());
+      runtime->SetResolutionMethod(image_header->GetImageMethod(ImageHeader::kResolutionMethod));
+      runtime->SetImtConflictMethod(image_header->GetImageMethod(ImageHeader::kImtConflictMethod));
+      runtime->SetImtUnimplementedMethod(
+          image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod));
+      runtime->SetCalleeSaveMethod(
+          image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod),
+          Runtime::kSaveAllCalleeSaves);
+      runtime->SetCalleeSaveMethod(
+          image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod), Runtime::kSaveRefsOnly);
+      runtime->SetCalleeSaveMethod(
+          image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod),
+          Runtime::kSaveRefsAndArgs);
+      runtime->SetCalleeSaveMethod(
+          image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod), Runtime::kSaveEverything);
+    }
+
+    VLOG(image) << "ImageSpace::Init exiting " << *space.get();
+    if (VLOG_IS_ON(image)) {
+      logger.Dump(LOG(INFO));
+    }
+    return space;
+  }
+
+ private:
+  static MemMap* LoadImageFile(const char* image_filename,
+                               const char* image_location,
+                               const ImageHeader& image_header,
+                               uint8_t* address,
+                               int fd,
+                               TimingLogger& logger,
+                               std::string* error_msg) {
+    TimingLogger::ScopedTiming timing("MapImageFile", &logger);
+    const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode();
+    if (storage_mode == ImageHeader::kStorageModeUncompressed) {
+      return MemMap::MapFileAtAddress(address,
+                                      image_header.GetImageSize(),
+                                      PROT_READ | PROT_WRITE,
+                                      MAP_PRIVATE,
+                                      fd,
+                                      0,
+                                      /*low_4gb*/true,
+                                      /*reuse*/false,
+                                      image_filename,
+                                      error_msg);
+    }
+
+    if (storage_mode != ImageHeader::kStorageModeLZ4 &&
+        storage_mode != ImageHeader::kStorageModeLZ4HC) {
+      *error_msg = StringPrintf("Invalid storage mode in image header %d",
+                                static_cast<int>(storage_mode));
+      return nullptr;
+    }
+
+    // Reserve output and decompress into it.
+    std::unique_ptr<MemMap> map(MemMap::MapAnonymous(image_location,
+                                                     address,
+                                                     image_header.GetImageSize(),
+                                                     PROT_READ | PROT_WRITE,
+                                                     /*low_4gb*/true,
+                                                     /*reuse*/false,
+                                                     error_msg));
+    if (map != nullptr) {
+      const size_t stored_size = image_header.GetDataSize();
+      const size_t decompress_offset = sizeof(ImageHeader);  // Skip the header.
+      std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size,
+                                                       PROT_READ,
+                                                       MAP_PRIVATE,
+                                                       fd,
+                                                       /*offset*/0,
+                                                       /*low_4gb*/false,
+                                                       image_filename,
+                                                       error_msg));
+      if (temp_map == nullptr) {
+        DCHECK(!error_msg->empty());
+        return nullptr;
+      }
+      memcpy(map->Begin(), &image_header, sizeof(ImageHeader));
+      const uint64_t start = NanoTime();
+      // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
+      TimingLogger::ScopedTiming timing2("LZ4 decompress image", &logger);
+      const size_t decompressed_size = LZ4_decompress_safe(
+          reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader),
+          reinterpret_cast<char*>(map->Begin()) + decompress_offset,
+          stored_size,
+          map->Size() - decompress_offset);
+      VLOG(image) << "Decompressing image took " << PrettyDuration(NanoTime() - start);
+      if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) {
+        *error_msg = StringPrintf(
+            "Decompressed size does not match expected image size %zu vs %zu",
+            decompressed_size + sizeof(ImageHeader),
+            image_header.GetImageSize());
+        return nullptr;
+      }
+    }
+
+    return map.release();
+  }
+
+  class FixupVisitor : public ValueObject {
+   public:
+    FixupVisitor(const RelocationRange& boot_image,
+                 const RelocationRange& boot_oat,
+                 const RelocationRange& app_image,
+                 const RelocationRange& app_oat)
+        : boot_image_(boot_image),
+          boot_oat_(boot_oat),
+          app_image_(app_image),
+          app_oat_(app_oat) {}
+
+    // Return the relocated address of a heap object.
+    template <typename T>
+    ALWAYS_INLINE T* ForwardObject(T* src) const {
+      const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src);
+      if (boot_image_.InSource(uint_src)) {
+        return reinterpret_cast<T*>(boot_image_.ToDest(uint_src));
+      }
+      if (app_image_.InSource(uint_src)) {
+        return reinterpret_cast<T*>(app_image_.ToDest(uint_src));
+      }
+      // Since we are fixing up the app image, there should only be pointers to the app image and
+      // boot image.
+      DCHECK(src == nullptr) << reinterpret_cast<const void*>(src);
+      return src;
+    }
+
+    // Return the relocated address of a code pointer (contained by an oat file).
+    ALWAYS_INLINE const void* ForwardCode(const void* src) const {
+      const uintptr_t uint_src = reinterpret_cast<uintptr_t>(src);
+      if (boot_oat_.InSource(uint_src)) {
+        return reinterpret_cast<const void*>(boot_oat_.ToDest(uint_src));
+      }
+      if (app_oat_.InSource(uint_src)) {
+        return reinterpret_cast<const void*>(app_oat_.ToDest(uint_src));
+      }
+      DCHECK(src == nullptr) << src;
+      return src;
+    }
+
+    // Must be called on pointers that already have been relocated to the destination relocation.
+    ALWAYS_INLINE bool IsInAppImage(mirror::Object* object) const {
+      return app_image_.InDest(reinterpret_cast<uintptr_t>(object));
+    }
+
+   protected:
+    // Source section.
+    const RelocationRange boot_image_;
+    const RelocationRange boot_oat_;
+    const RelocationRange app_image_;
+    const RelocationRange app_oat_;
+  };
+
+  // Adapt for mirror::Class::FixupNativePointers.
+  class FixupObjectAdapter : public FixupVisitor {
+   public:
+    template<typename... Args>
+    explicit FixupObjectAdapter(Args... args) : FixupVisitor(args...) {}
+
+    template <typename T>
+    T* operator()(T* obj) const {
+      return ForwardObject(obj);
+    }
+  };
+
+  class FixupRootVisitor : public FixupVisitor {
+   public:
+    template<typename... Args>
+    explicit FixupRootVisitor(Args... args) : FixupVisitor(args...) {}
+
+    ALWAYS_INLINE void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+        SHARED_REQUIRES(Locks::mutator_lock_) {
+      if (!root->IsNull()) {
+        VisitRoot(root);
+      }
+    }
+
+    ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+        SHARED_REQUIRES(Locks::mutator_lock_) {
+      mirror::Object* ref = root->AsMirrorPtr();
+      mirror::Object* new_ref = ForwardObject(ref);
+      if (ref != new_ref) {
+        root->Assign(new_ref);
+      }
+    }
+  };
+
+  class FixupObjectVisitor : public FixupVisitor {
+   public:
+    template<typename... Args>
+    explicit FixupObjectVisitor(gc::accounting::ContinuousSpaceBitmap* visited,
+                                const PointerSize pointer_size,
+                                Args... args)
+        : FixupVisitor(args...),
+          pointer_size_(pointer_size),
+          visited_(visited) {}
+
+    // Fix up separately since we also need to fix up method entrypoints.
+    ALWAYS_INLINE void VisitRootIfNonNull(
+        mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const {}
+
+    ALWAYS_INLINE void VisitRoot(mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED)
+        const {}
+
+    ALWAYS_INLINE void operator()(mirror::Object* obj,
+                                  MemberOffset offset,
+                                  bool is_static ATTRIBUTE_UNUSED) const
+        NO_THREAD_SAFETY_ANALYSIS {
+      // There could be overlap between ranges, we must avoid visiting the same reference twice.
+      // Avoid the class field since we already fixed it up in FixupClassVisitor.
+      if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) {
+        // Space is not yet added to the heap, don't do a read barrier.
+        mirror::Object* ref = obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
+            offset);
+        // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the
+        // image.
+        obj->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(offset, ForwardObject(ref));
+      }
+    }
+
+    // Visit a pointer array and forward corresponding native data. Ignores pointer arrays in the
+    // boot image. Uses the bitmap to ensure the same array is not visited multiple times.
+    template <typename Visitor>
+    void UpdatePointerArrayContents(mirror::PointerArray* array, const Visitor& visitor) const
+        NO_THREAD_SAFETY_ANALYSIS {
+      DCHECK(array != nullptr);
+      DCHECK(visitor.IsInAppImage(array));
+      // The bit for the array contents is different than the bit for the array. Since we may have
+      // already visited the array as a long / int array from walking the bitmap without knowing it
+      // was a pointer array.
+      static_assert(kObjectAlignment == 8u, "array bit may be in another object");
+      mirror::Object* const contents_bit = reinterpret_cast<mirror::Object*>(
+          reinterpret_cast<uintptr_t>(array) + kObjectAlignment);
+      // If the bit is not set then the contents have not yet been updated.
+      if (!visited_->Test(contents_bit)) {
+        array->Fixup<kVerifyNone, kWithoutReadBarrier>(array, pointer_size_, visitor);
+        visited_->Set(contents_bit);
+      }
+    }
+
+    // java.lang.ref.Reference visitor.
+    void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const
+        SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) {
+      mirror::Object* obj = ref->GetReferent<kWithoutReadBarrier>();
+      ref->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(
+          mirror::Reference::ReferentOffset(),
+          ForwardObject(obj));
+    }
+
+    void operator()(mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS {
+      if (visited_->Test(obj)) {
+        // Already visited.
+        return;
+      }
+      visited_->Set(obj);
+
+      // Handle class specially first since we need it to be updated to properly visit the rest of
+      // the instance fields.
+      {
+        mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+        DCHECK(klass != nullptr) << "Null class in image";
+        // No AsClass since our fields aren't quite fixed up yet.
+        mirror::Class* new_klass = down_cast<mirror::Class*>(ForwardObject(klass));
+        if (klass != new_klass) {
+          obj->SetClass<kVerifyNone>(new_klass);
+        }
+        if (new_klass != klass && IsInAppImage(new_klass)) {
+          // Make sure the klass contents are fixed up since we depend on it to walk the fields.
+          operator()(new_klass);
+        }
+      }
+
+      obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>(
+          *this,
+          *this);
+      // Note that this code relies on no circular dependencies.
+      // We want to use our own class loader and not the one in the image.
+      if (obj->IsClass<kVerifyNone, kWithoutReadBarrier>()) {
+        mirror::Class* as_klass = obj->AsClass<kVerifyNone, kWithoutReadBarrier>();
+        FixupObjectAdapter visitor(boot_image_, boot_oat_, app_image_, app_oat_);
+        as_klass->FixupNativePointers<kVerifyNone, kWithoutReadBarrier>(as_klass,
+                                                                        pointer_size_,
+                                                                        visitor);
+        // Deal with the pointer arrays. Use the helper function since multiple classes can reference
+        // the same arrays.
+        mirror::PointerArray* const vtable = as_klass->GetVTable<kVerifyNone, kWithoutReadBarrier>();
+        if (vtable != nullptr && IsInAppImage(vtable)) {
+          operator()(vtable);
+          UpdatePointerArrayContents(vtable, visitor);
+        }
+        mirror::IfTable* iftable = as_klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>();
+        // Ensure iftable arrays are fixed up since we need GetMethodArray to return the valid
+        // contents.
+        if (iftable != nullptr && IsInAppImage(iftable)) {
+          operator()(iftable);
+          for (int32_t i = 0, count = iftable->Count(); i < count; ++i) {
+            if (iftable->GetMethodArrayCount<kVerifyNone, kWithoutReadBarrier>(i) > 0) {
+              mirror::PointerArray* methods =
+                  iftable->GetMethodArray<kVerifyNone, kWithoutReadBarrier>(i);
+              if (visitor.IsInAppImage(methods)) {
+                operator()(methods);
+                DCHECK(methods != nullptr);
+                UpdatePointerArrayContents(methods, visitor);
+              }
             }
           }
         }
       }
     }
-  }
 
- private:
-  const PointerSize pointer_size_;
-  gc::accounting::ContinuousSpaceBitmap* const visited_;
-};
+   private:
+    const PointerSize pointer_size_;
+    gc::accounting::ContinuousSpaceBitmap* const visited_;
+  };
 
-class ForwardObjectAdapter {
- public:
-  ALWAYS_INLINE explicit ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {}
+  class ForwardObjectAdapter {
+   public:
+    ALWAYS_INLINE explicit ForwardObjectAdapter(const FixupVisitor* visitor) : visitor_(visitor) {}
 
-  template <typename T>
-  ALWAYS_INLINE T* operator()(T* src) const {
-    return visitor_->ForwardObject(src);
-  }
-
- private:
-  const FixupVisitor* const visitor_;
-};
-
-class ForwardCodeAdapter {
- public:
-  ALWAYS_INLINE explicit ForwardCodeAdapter(const FixupVisitor* visitor)
-      : visitor_(visitor) {}
-
-  template <typename T>
-  ALWAYS_INLINE T* operator()(T* src) const {
-    return visitor_->ForwardCode(src);
-  }
-
- private:
-  const FixupVisitor* const visitor_;
-};
-
-class FixupArtMethodVisitor : public FixupVisitor, public ArtMethodVisitor {
- public:
-  template<typename... Args>
-  explicit FixupArtMethodVisitor(bool fixup_heap_objects, PointerSize pointer_size, Args... args)
-      : FixupVisitor(args...),
-        fixup_heap_objects_(fixup_heap_objects),
-        pointer_size_(pointer_size) {}
-
-  virtual void Visit(ArtMethod* method) NO_THREAD_SAFETY_ANALYSIS {
-    // TODO: Separate visitor for runtime vs normal methods.
-    if (UNLIKELY(method->IsRuntimeMethod())) {
-      ImtConflictTable* table = method->GetImtConflictTable(pointer_size_);
-      if (table != nullptr) {
-        ImtConflictTable* new_table = ForwardObject(table);
-        if (table != new_table) {
-          method->SetImtConflictTable(new_table, pointer_size_);
-        }
-      }
-      const void* old_code = method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_);
-      const void* new_code = ForwardCode(old_code);
-      if (old_code != new_code) {
-        method->SetEntryPointFromQuickCompiledCodePtrSize(new_code, pointer_size_);
-      }
-    } else {
-      if (fixup_heap_objects_) {
-        method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_);
-      }
-      method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_);
+    template <typename T>
+    ALWAYS_INLINE T* operator()(T* src) const {
+      return visitor_->ForwardObject(src);
     }
-  }
 
- private:
-  const bool fixup_heap_objects_;
-  const PointerSize pointer_size_;
-};
+   private:
+    const FixupVisitor* const visitor_;
+  };
 
-class FixupArtFieldVisitor : public FixupVisitor, public ArtFieldVisitor {
- public:
-  template<typename... Args>
-  explicit FixupArtFieldVisitor(Args... args) : FixupVisitor(args...) {}
+  class ForwardCodeAdapter {
+   public:
+    ALWAYS_INLINE explicit ForwardCodeAdapter(const FixupVisitor* visitor)
+        : visitor_(visitor) {}
 
-  virtual void Visit(ArtField* field) NO_THREAD_SAFETY_ANALYSIS {
-    field->UpdateObjects(ForwardObjectAdapter(this));
-  }
-};
+    template <typename T>
+    ALWAYS_INLINE T* operator()(T* src) const {
+      return visitor_->ForwardCode(src);
+    }
 
-// Relocate an image space mapped at target_base which possibly used to be at a different base
-// address. Only needs a single image space, not one for both source and destination.
-// In place means modifying a single ImageSpace in place rather than relocating from one ImageSpace
-// to another.
-static bool RelocateInPlace(ImageHeader& image_header,
-                            uint8_t* target_base,
-                            accounting::ContinuousSpaceBitmap* bitmap,
-                            const OatFile* app_oat_file,
-                            std::string* error_msg) {
-  DCHECK(error_msg != nullptr);
-  if (!image_header.IsPic()) {
-    if (image_header.GetImageBegin() == target_base) {
+   private:
+    const FixupVisitor* const visitor_;
+  };
+
+  class FixupArtMethodVisitor : public FixupVisitor, public ArtMethodVisitor {
+   public:
+    template<typename... Args>
+    explicit FixupArtMethodVisitor(bool fixup_heap_objects, PointerSize pointer_size, Args... args)
+        : FixupVisitor(args...),
+          fixup_heap_objects_(fixup_heap_objects),
+          pointer_size_(pointer_size) {}
+
+    virtual void Visit(ArtMethod* method) NO_THREAD_SAFETY_ANALYSIS {
+      // TODO: Separate visitor for runtime vs normal methods.
+      if (UNLIKELY(method->IsRuntimeMethod())) {
+        ImtConflictTable* table = method->GetImtConflictTable(pointer_size_);
+        if (table != nullptr) {
+          ImtConflictTable* new_table = ForwardObject(table);
+          if (table != new_table) {
+            method->SetImtConflictTable(new_table, pointer_size_);
+          }
+        }
+        const void* old_code = method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_);
+        const void* new_code = ForwardCode(old_code);
+        if (old_code != new_code) {
+          method->SetEntryPointFromQuickCompiledCodePtrSize(new_code, pointer_size_);
+        }
+      } else {
+        if (fixup_heap_objects_) {
+          method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_);
+        }
+        method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_);
+      }
+    }
+
+   private:
+    const bool fixup_heap_objects_;
+    const PointerSize pointer_size_;
+  };
+
+  class FixupArtFieldVisitor : public FixupVisitor, public ArtFieldVisitor {
+   public:
+    template<typename... Args>
+    explicit FixupArtFieldVisitor(Args... args) : FixupVisitor(args...) {}
+
+    virtual void Visit(ArtField* field) NO_THREAD_SAFETY_ANALYSIS {
+      field->UpdateObjects(ForwardObjectAdapter(this));
+    }
+  };
+
+  // Relocate an image space mapped at target_base which possibly used to be at a different base
+  // address. Only needs a single image space, not one for both source and destination.
+  // In place means modifying a single ImageSpace in place rather than relocating from one ImageSpace
+  // to another.
+  static bool RelocateInPlace(ImageHeader& image_header,
+                              uint8_t* target_base,
+                              accounting::ContinuousSpaceBitmap* bitmap,
+                              const OatFile* app_oat_file,
+                              std::string* error_msg) {
+    DCHECK(error_msg != nullptr);
+    if (!image_header.IsPic()) {
+      if (image_header.GetImageBegin() == target_base) {
+        return true;
+      }
+      *error_msg = StringPrintf("Cannot relocate non-pic image for oat file %s",
+                                (app_oat_file != nullptr) ? app_oat_file->GetLocation().c_str() : "");
+      return false;
+    }
+    // Set up sections.
+    uint32_t boot_image_begin = 0;
+    uint32_t boot_image_end = 0;
+    uint32_t boot_oat_begin = 0;
+    uint32_t boot_oat_end = 0;
+    const PointerSize pointer_size = image_header.GetPointerSize();
+    gc::Heap* const heap = Runtime::Current()->GetHeap();
+    heap->GetBootImagesSize(&boot_image_begin, &boot_image_end, &boot_oat_begin, &boot_oat_end);
+    if (boot_image_begin == boot_image_end) {
+      *error_msg = "Can not relocate app image without boot image space";
+      return false;
+    }
+    if (boot_oat_begin == boot_oat_end) {
+      *error_msg = "Can not relocate app image without boot oat file";
+      return false;
+    }
+    const uint32_t boot_image_size = boot_image_end - boot_image_begin;
+    const uint32_t boot_oat_size = boot_oat_end - boot_oat_begin;
+    const uint32_t image_header_boot_image_size = image_header.GetBootImageSize();
+    const uint32_t image_header_boot_oat_size = image_header.GetBootOatSize();
+    if (boot_image_size != image_header_boot_image_size) {
+      *error_msg = StringPrintf("Boot image size %" PRIu64 " does not match expected size %"
+                                    PRIu64,
+                                static_cast<uint64_t>(boot_image_size),
+                                static_cast<uint64_t>(image_header_boot_image_size));
+      return false;
+    }
+    if (boot_oat_size != image_header_boot_oat_size) {
+      *error_msg = StringPrintf("Boot oat size %" PRIu64 " does not match expected size %"
+                                    PRIu64,
+                                static_cast<uint64_t>(boot_oat_size),
+                                static_cast<uint64_t>(image_header_boot_oat_size));
+      return false;
+    }
+    TimingLogger logger(__FUNCTION__, true, false);
+    RelocationRange boot_image(image_header.GetBootImageBegin(),
+                               boot_image_begin,
+                               boot_image_size);
+    RelocationRange boot_oat(image_header.GetBootOatBegin(),
+                             boot_oat_begin,
+                             boot_oat_size);
+    RelocationRange app_image(reinterpret_cast<uintptr_t>(image_header.GetImageBegin()),
+                              reinterpret_cast<uintptr_t>(target_base),
+                              image_header.GetImageSize());
+    // Use the oat data section since this is where the OatFile::Begin is.
+    RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()),
+                            // Not necessarily in low 4GB.
+                            reinterpret_cast<uintptr_t>(app_oat_file->Begin()),
+                            image_header.GetOatDataEnd() - image_header.GetOatDataBegin());
+    VLOG(image) << "App image " << app_image;
+    VLOG(image) << "App oat " << app_oat;
+    VLOG(image) << "Boot image " << boot_image;
+    VLOG(image) << "Boot oat " << boot_oat;
+    // True if we need to fixup any heap pointers, otherwise only code pointers.
+    const bool fixup_image = boot_image.Delta() != 0 || app_image.Delta() != 0;
+    const bool fixup_code = boot_oat.Delta() != 0 || app_oat.Delta() != 0;
+    if (!fixup_image && !fixup_code) {
+      // Nothing to fix up.
       return true;
     }
-    *error_msg = StringPrintf("Cannot relocate non-pic image for oat file %s",
-                              (app_oat_file != nullptr) ? app_oat_file->GetLocation().c_str() : "");
-    return false;
-  }
-  // Set up sections.
-  uint32_t boot_image_begin = 0;
-  uint32_t boot_image_end = 0;
-  uint32_t boot_oat_begin = 0;
-  uint32_t boot_oat_end = 0;
-  const PointerSize pointer_size = image_header.GetPointerSize();
-  gc::Heap* const heap = Runtime::Current()->GetHeap();
-  heap->GetBootImagesSize(&boot_image_begin, &boot_image_end, &boot_oat_begin, &boot_oat_end);
-  if (boot_image_begin == boot_image_end) {
-    *error_msg = "Can not relocate app image without boot image space";
-    return false;
-  }
-  if (boot_oat_begin == boot_oat_end) {
-    *error_msg = "Can not relocate app image without boot oat file";
-    return false;
-  }
-  const uint32_t boot_image_size = boot_image_end - boot_image_begin;
-  const uint32_t boot_oat_size = boot_oat_end - boot_oat_begin;
-  const uint32_t image_header_boot_image_size = image_header.GetBootImageSize();
-  const uint32_t image_header_boot_oat_size = image_header.GetBootOatSize();
-  if (boot_image_size != image_header_boot_image_size) {
-    *error_msg = StringPrintf("Boot image size %" PRIu64 " does not match expected size %"
-                                  PRIu64,
-                              static_cast<uint64_t>(boot_image_size),
-                              static_cast<uint64_t>(image_header_boot_image_size));
-    return false;
-  }
-  if (boot_oat_size != image_header_boot_oat_size) {
-    *error_msg = StringPrintf("Boot oat size %" PRIu64 " does not match expected size %"
-                                  PRIu64,
-                              static_cast<uint64_t>(boot_oat_size),
-                              static_cast<uint64_t>(image_header_boot_oat_size));
-    return false;
-  }
-  TimingLogger logger(__FUNCTION__, true, false);
-  RelocationRange boot_image(image_header.GetBootImageBegin(),
-                             boot_image_begin,
-                             boot_image_size);
-  RelocationRange boot_oat(image_header.GetBootOatBegin(),
-                           boot_oat_begin,
-                           boot_oat_size);
-  RelocationRange app_image(reinterpret_cast<uintptr_t>(image_header.GetImageBegin()),
-                            reinterpret_cast<uintptr_t>(target_base),
-                            image_header.GetImageSize());
-  // Use the oat data section since this is where the OatFile::Begin is.
-  RelocationRange app_oat(reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()),
-                          // Not necessarily in low 4GB.
-                          reinterpret_cast<uintptr_t>(app_oat_file->Begin()),
-                          image_header.GetOatDataEnd() - image_header.GetOatDataBegin());
-  VLOG(image) << "App image " << app_image;
-  VLOG(image) << "App oat " << app_oat;
-  VLOG(image) << "Boot image " << boot_image;
-  VLOG(image) << "Boot oat " << boot_oat;
-  // True if we need to fixup any heap pointers, otherwise only code pointers.
-  const bool fixup_image = boot_image.Delta() != 0 || app_image.Delta() != 0;
-  const bool fixup_code = boot_oat.Delta() != 0 || app_oat.Delta() != 0;
-  if (!fixup_image && !fixup_code) {
-    // Nothing to fix up.
-    return true;
-  }
-  ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
-  // Need to update the image to be at the target base.
-  const ImageSection& objects_section = image_header.GetImageSection(ImageHeader::kSectionObjects);
-  uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset());
-  uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End());
-  FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat);
-  if (fixup_image) {
-    // Two pass approach, fix up all classes first, then fix up non class-objects.
-    // The visited bitmap is used to ensure that pointer arrays are not forwarded twice.
-    std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> visited_bitmap(
-        gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap",
-                                                      target_base,
-                                                      image_header.GetImageSize()));
-    FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(),
-                                            pointer_size,
-                                            boot_image,
-                                            boot_oat,
-                                            app_image,
-                                            app_oat);
-    TimingLogger::ScopedTiming timing("Fixup classes", &logger);
-    // Fixup objects may read fields in the boot image, use the mutator lock here for sanity. Though
-    // its probably not required.
-    ScopedObjectAccess soa(Thread::Current());
-    timing.NewTiming("Fixup objects");
-    bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor);
-    // Fixup image roots.
-    CHECK(app_image.InSource(reinterpret_cast<uintptr_t>(
-        image_header.GetImageRoots<kWithoutReadBarrier>())));
-    image_header.RelocateImageObjects(app_image.Delta());
-    CHECK_EQ(image_header.GetImageBegin(), target_base);
-    // Fix up dex cache DexFile pointers.
-    auto* dex_caches = image_header.GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)->
-        AsObjectArray<mirror::DexCache, kVerifyNone, kWithoutReadBarrier>();
-    for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) {
-      mirror::DexCache* dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i);
-      // Fix up dex cache pointers.
-      GcRoot<mirror::String>* strings = dex_cache->GetStrings();
-      if (strings != nullptr) {
-        GcRoot<mirror::String>* new_strings = fixup_adapter.ForwardObject(strings);
-        if (strings != new_strings) {
-          dex_cache->SetStrings(new_strings);
+    ScopedDebugDisallowReadBarriers sddrb(Thread::Current());
+    // Need to update the image to be at the target base.
+    const ImageSection& objects_section = image_header.GetImageSection(ImageHeader::kSectionObjects);
+    uintptr_t objects_begin = reinterpret_cast<uintptr_t>(target_base + objects_section.Offset());
+    uintptr_t objects_end = reinterpret_cast<uintptr_t>(target_base + objects_section.End());
+    FixupObjectAdapter fixup_adapter(boot_image, boot_oat, app_image, app_oat);
+    if (fixup_image) {
+      // Two pass approach, fix up all classes first, then fix up non class-objects.
+      // The visited bitmap is used to ensure that pointer arrays are not forwarded twice.
+      std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> visited_bitmap(
+          gc::accounting::ContinuousSpaceBitmap::Create("Relocate bitmap",
+                                                        target_base,
+                                                        image_header.GetImageSize()));
+      FixupObjectVisitor fixup_object_visitor(visited_bitmap.get(),
+                                              pointer_size,
+                                              boot_image,
+                                              boot_oat,
+                                              app_image,
+                                              app_oat);
+      TimingLogger::ScopedTiming timing("Fixup classes", &logger);
+      // Fixup objects may read fields in the boot image, use the mutator lock here for sanity. Though
+      // its probably not required.
+      ScopedObjectAccess soa(Thread::Current());
+      timing.NewTiming("Fixup objects");
+      bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_object_visitor);
+      // Fixup image roots.
+      CHECK(app_image.InSource(reinterpret_cast<uintptr_t>(
+          image_header.GetImageRoots<kWithoutReadBarrier>())));
+      image_header.RelocateImageObjects(app_image.Delta());
+      CHECK_EQ(image_header.GetImageBegin(), target_base);
+      // Fix up dex cache DexFile pointers.
+      auto* dex_caches = image_header.GetImageRoot<kWithoutReadBarrier>(ImageHeader::kDexCaches)->
+          AsObjectArray<mirror::DexCache, kVerifyNone, kWithoutReadBarrier>();
+      for (int32_t i = 0, count = dex_caches->GetLength(); i < count; ++i) {
+        mirror::DexCache* dex_cache = dex_caches->Get<kVerifyNone, kWithoutReadBarrier>(i);
+        // Fix up dex cache pointers.
+        GcRoot<mirror::String>* strings = dex_cache->GetStrings();
+        if (strings != nullptr) {
+          GcRoot<mirror::String>* new_strings = fixup_adapter.ForwardObject(strings);
+          if (strings != new_strings) {
+            dex_cache->SetStrings(new_strings);
+          }
+          dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter);
         }
-        dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter);
-      }
-      GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
-      if (types != nullptr) {
-        GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types);
-        if (types != new_types) {
-          dex_cache->SetResolvedTypes(new_types);
+        GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
+        if (types != nullptr) {
+          GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types);
+          if (types != new_types) {
+            dex_cache->SetResolvedTypes(new_types);
+          }
+          dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter);
         }
-        dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter);
-      }
-      ArtMethod** methods = dex_cache->GetResolvedMethods();
-      if (methods != nullptr) {
-        ArtMethod** new_methods = fixup_adapter.ForwardObject(methods);
-        if (methods != new_methods) {
-          dex_cache->SetResolvedMethods(new_methods);
-        }
-        for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) {
-          ArtMethod* orig = mirror::DexCache::GetElementPtrSize(new_methods, j, pointer_size);
-          ArtMethod* copy = fixup_adapter.ForwardObject(orig);
-          if (orig != copy) {
-            mirror::DexCache::SetElementPtrSize(new_methods, j, copy, pointer_size);
+        ArtMethod** methods = dex_cache->GetResolvedMethods();
+        if (methods != nullptr) {
+          ArtMethod** new_methods = fixup_adapter.ForwardObject(methods);
+          if (methods != new_methods) {
+            dex_cache->SetResolvedMethods(new_methods);
+          }
+          for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) {
+            ArtMethod* orig = mirror::DexCache::GetElementPtrSize(new_methods, j, pointer_size);
+            ArtMethod* copy = fixup_adapter.ForwardObject(orig);
+            if (orig != copy) {
+              mirror::DexCache::SetElementPtrSize(new_methods, j, copy, pointer_size);
+            }
           }
         }
-      }
-      ArtField** fields = dex_cache->GetResolvedFields();
-      if (fields != nullptr) {
-        ArtField** new_fields = fixup_adapter.ForwardObject(fields);
-        if (fields != new_fields) {
-          dex_cache->SetResolvedFields(new_fields);
-        }
-        for (size_t j = 0, num = dex_cache->NumResolvedFields(); j != num; ++j) {
-          ArtField* orig = mirror::DexCache::GetElementPtrSize(new_fields, j, pointer_size);
-          ArtField* copy = fixup_adapter.ForwardObject(orig);
-          if (orig != copy) {
-            mirror::DexCache::SetElementPtrSize(new_fields, j, copy, pointer_size);
+        ArtField** fields = dex_cache->GetResolvedFields();
+        if (fields != nullptr) {
+          ArtField** new_fields = fixup_adapter.ForwardObject(fields);
+          if (fields != new_fields) {
+            dex_cache->SetResolvedFields(new_fields);
+          }
+          for (size_t j = 0, num = dex_cache->NumResolvedFields(); j != num; ++j) {
+            ArtField* orig = mirror::DexCache::GetElementPtrSize(new_fields, j, pointer_size);
+            ArtField* copy = fixup_adapter.ForwardObject(orig);
+            if (orig != copy) {
+              mirror::DexCache::SetElementPtrSize(new_fields, j, copy, pointer_size);
+            }
           }
         }
       }
     }
-  }
-  {
-    // Only touches objects in the app image, no need for mutator lock.
-    TimingLogger::ScopedTiming timing("Fixup methods", &logger);
-    FixupArtMethodVisitor method_visitor(fixup_image,
-                                         pointer_size,
-                                         boot_image,
-                                         boot_oat,
-                                         app_image,
-                                         app_oat);
-    image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size);
-  }
-  if (fixup_image) {
     {
       // Only touches objects in the app image, no need for mutator lock.
-      TimingLogger::ScopedTiming timing("Fixup fields", &logger);
-      FixupArtFieldVisitor field_visitor(boot_image, boot_oat, app_image, app_oat);
-      image_header.VisitPackedArtFields(&field_visitor, target_base);
+      TimingLogger::ScopedTiming timing("Fixup methods", &logger);
+      FixupArtMethodVisitor method_visitor(fixup_image,
+                                           pointer_size,
+                                           boot_image,
+                                           boot_oat,
+                                           app_image,
+                                           app_oat);
+      image_header.VisitPackedArtMethods(&method_visitor, target_base, pointer_size);
     }
-    {
-      TimingLogger::ScopedTiming timing("Fixup imt", &logger);
-      image_header.VisitPackedImTables(fixup_adapter, target_base, pointer_size);
+    if (fixup_image) {
+      {
+        // Only touches objects in the app image, no need for mutator lock.
+        TimingLogger::ScopedTiming timing("Fixup fields", &logger);
+        FixupArtFieldVisitor field_visitor(boot_image, boot_oat, app_image, app_oat);
+        image_header.VisitPackedArtFields(&field_visitor, target_base);
+      }
+      {
+        TimingLogger::ScopedTiming timing("Fixup imt", &logger);
+        image_header.VisitPackedImTables(fixup_adapter, target_base, pointer_size);
+      }
+      {
+        TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger);
+        image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size);
+      }
+      // In the app image case, the image methods are actually in the boot image.
+      image_header.RelocateImageMethods(boot_image.Delta());
+      const auto& class_table_section = image_header.GetImageSection(ImageHeader::kSectionClassTable);
+      if (class_table_section.Size() > 0u) {
+        // Note that we require that ReadFromMemory does not make an internal copy of the elements.
+        // This also relies on visit roots not doing any verification which could fail after we update
+        // the roots to be the image addresses.
+        ScopedObjectAccess soa(Thread::Current());
+        WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
+        ClassTable temp_table;
+        temp_table.ReadFromMemory(target_base + class_table_section.Offset());
+        FixupRootVisitor root_visitor(boot_image, boot_oat, app_image, app_oat);
+        temp_table.VisitRoots(root_visitor);
+      }
     }
-    {
-      TimingLogger::ScopedTiming timing("Fixup conflict tables", &logger);
-      image_header.VisitPackedImtConflictTables(fixup_adapter, target_base, pointer_size);
+    if (VLOG_IS_ON(image)) {
+      logger.Dump(LOG(INFO));
     }
-    // In the app image case, the image methods are actually in the boot image.
-    image_header.RelocateImageMethods(boot_image.Delta());
-    const auto& class_table_section = image_header.GetImageSection(ImageHeader::kSectionClassTable);
-    if (class_table_section.Size() > 0u) {
-      // Note that we require that ReadFromMemory does not make an internal copy of the elements.
-      // This also relies on visit roots not doing any verification which could fail after we update
-      // the roots to be the image addresses.
-      ScopedObjectAccess soa(Thread::Current());
-      WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
-      ClassTable temp_table;
-      temp_table.ReadFromMemory(target_base + class_table_section.Offset());
-      FixupRootVisitor root_visitor(boot_image, boot_oat, app_image, app_oat);
-      temp_table.VisitRoots(root_visitor);
-    }
-  }
-  if (VLOG_IS_ON(image)) {
-    logger.Dump(LOG(INFO));
-  }
-  return true;
-}
-
-static MemMap* LoadImageFile(const char* image_filename,
-                             const char* image_location,
-                             const ImageHeader& image_header,
-                             uint8_t* address,
-                             int fd,
-                             TimingLogger& logger,
-                             std::string* error_msg) {
-  TimingLogger::ScopedTiming timing("MapImageFile", &logger);
-  const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode();
-  if (storage_mode == ImageHeader::kStorageModeUncompressed) {
-    return MemMap::MapFileAtAddress(address,
-                                    image_header.GetImageSize(),
-                                    PROT_READ | PROT_WRITE,
-                                    MAP_PRIVATE,
-                                    fd,
-                                    0,
-                                    /*low_4gb*/true,
-                                    /*reuse*/false,
-                                    image_filename,
-                                    error_msg);
+    return true;
   }
 
-  if (storage_mode != ImageHeader::kStorageModeLZ4 &&
-      storage_mode != ImageHeader::kStorageModeLZ4HC) {
-    *error_msg = StringPrintf("Invalid storage mode in image header %d",
-                              static_cast<int>(storage_mode));
-    return nullptr;
-  }
+  static std::unique_ptr<OatFile> OpenOatFile(const ImageSpace& image,
+                                              const char* image_path,
+                                              std::string* error_msg) {
+    const ImageHeader& image_header = image.GetImageHeader();
+    std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path);
 
-  // Reserve output and decompress into it.
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous(image_location,
-                                                   address,
-                                                   image_header.GetImageSize(),
-                                                   PROT_READ | PROT_WRITE,
-                                                   /*low_4gb*/true,
-                                                   /*reuse*/false,
-                                                   error_msg));
-  if (map != nullptr) {
-    const size_t stored_size = image_header.GetDataSize();
-    const size_t decompress_offset = sizeof(ImageHeader);  // Skip the header.
-    std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size,
-                                                     PROT_READ,
-                                                     MAP_PRIVATE,
-                                                     fd,
-                                                     /*offset*/0,
-                                                     /*low_4gb*/false,
-                                                     image_filename,
-                                                     error_msg));
-    if (temp_map == nullptr) {
-      DCHECK(!error_msg->empty());
+    CHECK(image_header.GetOatDataBegin() != nullptr);
+
+    std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename,
+                                                    oat_filename,
+                                                    image_header.GetOatDataBegin(),
+                                                    image_header.GetOatFileBegin(),
+                                                    !Runtime::Current()->IsAotCompiler(),
+                                                    /*low_4gb*/false,
+                                                    nullptr,
+                                                    error_msg));
+    if (oat_file == nullptr) {
+      *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s",
+                                oat_filename.c_str(),
+                                image.GetName(),
+                                error_msg->c_str());
       return nullptr;
     }
-    memcpy(map->Begin(), &image_header, sizeof(ImageHeader));
-    const uint64_t start = NanoTime();
-    // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
-    TimingLogger::ScopedTiming timing2("LZ4 decompress image", &logger);
-    const size_t decompressed_size = LZ4_decompress_safe(
-        reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader),
-        reinterpret_cast<char*>(map->Begin()) + decompress_offset,
-        stored_size,
-        map->Size() - decompress_offset);
-    VLOG(image) << "Decompressing image took " << PrettyDuration(NanoTime() - start);
-    if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) {
-      *error_msg = StringPrintf(
-          "Decompressed size does not match expected image size %zu vs %zu",
-          decompressed_size + sizeof(ImageHeader),
-          image_header.GetImageSize());
-      return nullptr;
-    }
-  }
-
-  return map.release();
-}
-
-ImageSpace* ImageSpace::Init(const char* image_filename,
-                             const char* image_location,
-                             bool validate_oat_file,
-                             const OatFile* oat_file,
-                             std::string* error_msg) {
-  CHECK(image_filename != nullptr);
-  CHECK(image_location != nullptr);
-
-  TimingLogger logger(__PRETTY_FUNCTION__, true, VLOG_IS_ON(image));
-  VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename;
-
-  std::unique_ptr<File> file;
-  {
-    TimingLogger::ScopedTiming timing("OpenImageFile", &logger);
-    file.reset(OS::OpenFileForReading(image_filename));
-    if (file == nullptr) {
-      *error_msg = StringPrintf("Failed to open '%s'", image_filename);
-      return nullptr;
-    }
-  }
-  ImageHeader temp_image_header;
-  ImageHeader* image_header = &temp_image_header;
-  {
-    TimingLogger::ScopedTiming timing("ReadImageHeader", &logger);
-    bool success = file->ReadFully(image_header, sizeof(*image_header));
-    if (!success || !image_header->IsValid()) {
-      *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
-      return nullptr;
-    }
-  }
-  // Check that the file is larger or equal to the header size + data size.
-  const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
-  if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
-    *error_msg = StringPrintf("Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
-                              image_file_size,
-                              sizeof(ImageHeader) + image_header->GetDataSize());
-    return nullptr;
-  }
-
-  if (oat_file != nullptr) {
-    // If we have an oat file, check the oat file checksum. The oat file is only non-null for the
-    // app image case. Otherwise, we open the oat file after the image and check the checksum there.
-    const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
-    const uint32_t image_oat_checksum = image_header->GetOatChecksum();
+    uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
+    uint32_t image_oat_checksum = image_header.GetOatChecksum();
     if (oat_checksum != image_oat_checksum) {
-      *error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
+      *error_msg = StringPrintf("Failed to match oat file checksum 0x%x to expected oat checksum 0x%x"
+                                " in image %s",
                                 oat_checksum,
                                 image_oat_checksum,
-                                image_filename);
+                                image.GetName());
       return nullptr;
     }
-  }
-
-  if (VLOG_IS_ON(startup)) {
-    LOG(INFO) << "Dumping image sections";
-    for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
-      const auto section_idx = static_cast<ImageHeader::ImageSections>(i);
-      auto& section = image_header->GetImageSection(section_idx);
-      LOG(INFO) << section_idx << " start="
-                << reinterpret_cast<void*>(image_header->GetImageBegin() + section.Offset()) << " "
-                << section;
-    }
-  }
-
-  const auto& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap);
-  // The location we want to map from is the first aligned page after the end of the stored
-  // (possibly compressed) data.
-  const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(),
-                                             kPageSize);
-  const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size();
-  if (end_of_bitmap != image_file_size) {
-    *error_msg = StringPrintf(
-        "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size,
-        end_of_bitmap);
-    return nullptr;
-  }
-
-  std::unique_ptr<MemMap> map;
-  // GetImageBegin is the preferred address to map the image. If we manage to map the
-  // image at the image begin, the amount of fixup work required is minimized.
-  map.reset(LoadImageFile(image_filename,
-                          image_location,
-                          *image_header,
-                          image_header->GetImageBegin(),
-                          file->Fd(),
-                          logger,
-                          error_msg));
-  // If the header specifies PIC mode, we can also map at a random low_4gb address since we can
-  // relocate in-place.
-  if (map == nullptr && image_header->IsPic()) {
-    map.reset(LoadImageFile(image_filename,
-                            image_location,
-                            *image_header,
-                            /* address */ nullptr,
-                            file->Fd(),
-                            logger,
-                            error_msg));
-  }
-  // Were we able to load something and continue?
-  if (map == nullptr) {
-    DCHECK(!error_msg->empty());
-    return nullptr;
-  }
-  DCHECK_EQ(0, memcmp(image_header, map->Begin(), sizeof(ImageHeader)));
-
-  std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr,
-                                                                    bitmap_section.Size(),
-                                                                    PROT_READ, MAP_PRIVATE,
-                                                                    file->Fd(),
-                                                                    image_bitmap_offset,
-                                                                    /*low_4gb*/false,
-                                                                    /*reuse*/false,
-                                                                    image_filename,
-                                                                    error_msg));
-  if (image_bitmap_map == nullptr) {
-    *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
-    return nullptr;
-  }
-  // Loaded the map, use the image header from the file now in case we patch it with
-  // RelocateInPlace.
-  image_header = reinterpret_cast<ImageHeader*>(map->Begin());
-  const uint32_t bitmap_index = bitmap_index_.FetchAndAddSequentiallyConsistent(1);
-  std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
-                                       image_filename,
-                                       bitmap_index));
-  // Bitmap only needs to cover until the end of the mirror objects section.
-  const ImageSection& image_objects = image_header->GetImageSection(ImageHeader::kSectionObjects);
-  // We only want the mirror object, not the ArtFields and ArtMethods.
-  uint8_t* const image_end = map->Begin() + image_objects.End();
-  std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap;
-  {
-    TimingLogger::ScopedTiming timing("CreateImageBitmap", &logger);
-    bitmap.reset(
-      accounting::ContinuousSpaceBitmap::CreateFromMemMap(
-          bitmap_name,
-          image_bitmap_map.release(),
-          reinterpret_cast<uint8_t*>(map->Begin()),
-          image_objects.End()));
-    if (bitmap == nullptr) {
-      *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str());
+    int32_t image_patch_delta = image_header.GetPatchDelta();
+    int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta();
+    if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) {
+      // We should have already relocated by this point. Bail out.
+      *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d "
+                                "in image %s",
+                                oat_patch_delta,
+                                image_patch_delta,
+                                image.GetName());
       return nullptr;
     }
+
+    return oat_file;
   }
-  {
-    TimingLogger::ScopedTiming timing("RelocateImage", &logger);
-    if (!RelocateInPlace(*image_header,
-                         map->Begin(),
-                         bitmap.get(),
-                         oat_file,
-                         error_msg)) {
-      return nullptr;
+
+  static bool ValidateOatFile(const ImageSpace& space,
+                              const OatFile& oat_file,
+                              std::string* error_msg) {
+    for (const OatFile::OatDexFile* oat_dex_file : oat_file.GetOatDexFiles()) {
+      const std::string& dex_file_location = oat_dex_file->GetDexFileLocation();
+      uint32_t dex_file_location_checksum;
+      if (!DexFile::GetChecksum(dex_file_location.c_str(), &dex_file_location_checksum, error_msg)) {
+        *error_msg = StringPrintf("Failed to get checksum of dex file '%s' referenced by image %s: "
+                                  "%s",
+                                  dex_file_location.c_str(),
+                                  space.GetName(),
+                                  error_msg->c_str());
+        return false;
+      }
+      if (dex_file_location_checksum != oat_dex_file->GetDexFileLocationChecksum()) {
+        *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file '%s' and "
+                                  "dex file '%s' (0x%x != 0x%x)",
+                                  oat_file.GetLocation().c_str(),
+                                  dex_file_location.c_str(),
+                                  oat_dex_file->GetDexFileLocationChecksum(),
+                                  dex_file_location_checksum);
+        return false;
+      }
     }
+    return true;
   }
-  // We only want the mirror object, not the ArtFields and ArtMethods.
-  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
-                                                   image_location,
-                                                   map.release(),
-                                                   bitmap.release(),
-                                                   image_end));
+};
 
-  // VerifyImageAllocations() will be called later in Runtime::Init()
-  // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_
-  // and ArtField::java_lang_reflect_ArtField_, which are used from
-  // Object::SizeOf() which VerifyImageAllocations() calls, are not
-  // set yet at this point.
-  if (oat_file == nullptr) {
-    TimingLogger::ScopedTiming timing("OpenOatFile", &logger);
-    space->oat_file_.reset(space->OpenOatFile(image_filename, error_msg));
-    if (space->oat_file_ == nullptr) {
-      DCHECK(!error_msg->empty());
-      return nullptr;
-    }
-    space->oat_file_non_owned_ = space->oat_file_.get();
-  } else {
-    space->oat_file_non_owned_ = oat_file;
+static constexpr uint64_t kLowSpaceValue = 50 * MB;
+static constexpr uint64_t kTmpFsSentinelValue = 384 * MB;
+
+// Read the free space of the cache partition and make a decision whether to keep the generated
+// image. This is to try to mitigate situations where the system might run out of space later.
+static bool CheckSpace(const std::string& cache_filename, std::string* error_msg) {
+  // Using statvfs vs statvfs64 because of b/18207376, and it is enough for all practical purposes.
+  struct statvfs buf;
+
+  int res = TEMP_FAILURE_RETRY(statvfs(cache_filename.c_str(), &buf));
+  if (res != 0) {
+    // Could not stat. Conservatively tell the system to delete the image.
+    *error_msg = "Could not stat the filesystem, assuming low-memory situation.";
+    return false;
   }
 
-  if (validate_oat_file) {
-    TimingLogger::ScopedTiming timing("ValidateOatFile", &logger);
-    if (!space->ValidateOatFile(error_msg)) {
-     DCHECK(!error_msg->empty());
-      return nullptr;
-    }
-  }
+  uint64_t fs_overall_size = buf.f_bsize * static_cast<uint64_t>(buf.f_blocks);
+  // Zygote is privileged, but other things are not. Use bavail.
+  uint64_t fs_free_size = buf.f_bsize * static_cast<uint64_t>(buf.f_bavail);
 
-  Runtime* runtime = Runtime::Current();
-
-  // If oat_file is null, then it is the boot image space. Use oat_file_non_owned_ from the space
-  // to set the runtime methods.
-  CHECK_EQ(oat_file != nullptr, image_header->IsAppImage());
-  if (image_header->IsAppImage()) {
-    CHECK_EQ(runtime->GetResolutionMethod(),
-             image_header->GetImageMethod(ImageHeader::kResolutionMethod));
-    CHECK_EQ(runtime->GetImtConflictMethod(),
-             image_header->GetImageMethod(ImageHeader::kImtConflictMethod));
-    CHECK_EQ(runtime->GetImtUnimplementedMethod(),
-             image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod));
-    CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveAllCalleeSaves),
-             image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod));
-    CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsOnly),
-             image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod));
-    CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveRefsAndArgs),
-             image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod));
-    CHECK_EQ(runtime->GetCalleeSaveMethod(Runtime::kSaveEverything),
-             image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod));
-  } else if (!runtime->HasResolutionMethod()) {
-    runtime->SetInstructionSet(space->oat_file_non_owned_->GetOatHeader().GetInstructionSet());
-    runtime->SetResolutionMethod(image_header->GetImageMethod(ImageHeader::kResolutionMethod));
-    runtime->SetImtConflictMethod(image_header->GetImageMethod(ImageHeader::kImtConflictMethod));
-    runtime->SetImtUnimplementedMethod(
-        image_header->GetImageMethod(ImageHeader::kImtUnimplementedMethod));
-    runtime->SetCalleeSaveMethod(
-        image_header->GetImageMethod(ImageHeader::kSaveAllCalleeSavesMethod),
-        Runtime::kSaveAllCalleeSaves);
-    runtime->SetCalleeSaveMethod(
-        image_header->GetImageMethod(ImageHeader::kSaveRefsOnlyMethod), Runtime::kSaveRefsOnly);
-    runtime->SetCalleeSaveMethod(
-        image_header->GetImageMethod(ImageHeader::kSaveRefsAndArgsMethod),
-        Runtime::kSaveRefsAndArgs);
-    runtime->SetCalleeSaveMethod(
-        image_header->GetImageMethod(ImageHeader::kSaveEverythingMethod), Runtime::kSaveEverything);
-  }
-
-  VLOG(image) << "ImageSpace::Init exiting " << *space.get();
-  if (VLOG_IS_ON(image)) {
-    logger.Dump(LOG(INFO));
-  }
-  return space.release();
-}
-
-OatFile* ImageSpace::OpenOatFile(const char* image_path, std::string* error_msg) const {
-  const ImageHeader& image_header = GetImageHeader();
-  std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path);
-
-  CHECK(image_header.GetOatDataBegin() != nullptr);
-
-  OatFile* oat_file = OatFile::Open(oat_filename,
-                                    oat_filename,
-                                    image_header.GetOatDataBegin(),
-                                    image_header.GetOatFileBegin(),
-                                    !Runtime::Current()->IsAotCompiler(),
-                                    /*low_4gb*/false,
-                                    nullptr,
-                                    error_msg);
-  if (oat_file == nullptr) {
-    *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s",
-                              oat_filename.c_str(), GetName(), error_msg->c_str());
-    return nullptr;
-  }
-  uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
-  uint32_t image_oat_checksum = image_header.GetOatChecksum();
-  if (oat_checksum != image_oat_checksum) {
-    *error_msg = StringPrintf("Failed to match oat file checksum 0x%x to expected oat checksum 0x%x"
-                              " in image %s", oat_checksum, image_oat_checksum, GetName());
-    return nullptr;
-  }
-  int32_t image_patch_delta = image_header.GetPatchDelta();
-  int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta();
-  if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) {
-    // We should have already relocated by this point. Bail out.
-    *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d "
-                              "in image %s", oat_patch_delta, image_patch_delta, GetName());
-    return nullptr;
-  }
-
-  return oat_file;
-}
-
-bool ImageSpace::ValidateOatFile(std::string* error_msg) const {
-  CHECK(oat_file_.get() != nullptr);
-  for (const OatFile::OatDexFile* oat_dex_file : oat_file_->GetOatDexFiles()) {
-    const std::string& dex_file_location = oat_dex_file->GetDexFileLocation();
-    uint32_t dex_file_location_checksum;
-    if (!DexFile::GetChecksum(dex_file_location.c_str(), &dex_file_location_checksum, error_msg)) {
-      *error_msg = StringPrintf("Failed to get checksum of dex file '%s' referenced by image %s: "
-                                "%s", dex_file_location.c_str(), GetName(), error_msg->c_str());
-      return false;
-    }
-    if (dex_file_location_checksum != oat_dex_file->GetDexFileLocationChecksum()) {
-      *error_msg = StringPrintf("ValidateOatFile found checksum mismatch between oat file '%s' and "
-                                "dex file '%s' (0x%x != 0x%x)",
-                                oat_file_->GetLocation().c_str(), dex_file_location.c_str(),
-                                oat_dex_file->GetDexFileLocationChecksum(),
-                                dex_file_location_checksum);
+  // Take the overall size as an indicator for a tmpfs, which is being used for the decryption
+  // environment. We do not want to fail quickening the boot image there, as it is beneficial
+  // for time-to-UI.
+  if (fs_overall_size > kTmpFsSentinelValue) {
+    if (fs_free_size < kLowSpaceValue) {
+      *error_msg = StringPrintf("Low-memory situation: only %4.2f megabytes available, need at "
+                                "least %" PRIu64 ".",
+                                static_cast<double>(fs_free_size) / MB,
+                                kLowSpaceValue / MB);
       return false;
     }
   }
   return true;
 }
 
+std::unique_ptr<ImageSpace> ImageSpace::CreateBootImage(const char* image_location,
+                                                        const InstructionSet image_isa,
+                                                        bool secondary_image,
+                                                        std::string* error_msg) {
+  ScopedTrace trace(__FUNCTION__);
+
+  // Step 0: Extra zygote work.
+
+  // Step 0.a: If we're the zygote, mark boot.
+  const bool is_zygote = Runtime::Current()->IsZygote();
+  if (is_zygote && !secondary_image) {
+    MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
+  }
+
+  // Step 0.b: If we're the zygote, check for free space, and prune the cache preemptively,
+  //           if necessary. While the runtime may be fine (it is pretty tolerant to
+  //           out-of-disk-space situations), other parts of the platform are not.
+  //
+  //           The advantage of doing this proactively is that the later steps are simplified,
+  //           i.e., we do not need to code retries.
+  std::string system_filename;
+  bool has_system = false;
+  std::string cache_filename;
+  bool has_cache = false;
+  bool dalvik_cache_exists = false;
+  bool is_global_cache = true;
+  std::string dalvik_cache;
+  bool found_image = FindImageFilenameImpl(image_location,
+                                           image_isa,
+                                           &has_system,
+                                           &system_filename,
+                                           &dalvik_cache_exists,
+                                           &dalvik_cache,
+                                           &is_global_cache,
+                                           &has_cache,
+                                           &cache_filename);
+
+  if (is_zygote && dalvik_cache_exists) {
+    DCHECK(!dalvik_cache.empty());
+    std::string local_error_msg;
+    if (!CheckSpace(dalvik_cache, &local_error_msg)) {
+      LOG(WARNING) << local_error_msg << " Preemptively pruning the dalvik cache.";
+      PruneDalvikCache(image_isa);
+
+      // Re-evaluate the image.
+      found_image = FindImageFilenameImpl(image_location,
+                                          image_isa,
+                                          &has_system,
+                                          &system_filename,
+                                          &dalvik_cache_exists,
+                                          &dalvik_cache,
+                                          &is_global_cache,
+                                          &has_cache,
+                                          &cache_filename);
+    }
+  }
+
+  // Collect all the errors.
+  std::vector<std::string> error_msgs;
+
+  // Step 1: Check if we have an existing and relocated image.
+
+  // Step 1.a: Have files in system and cache. Then they need to match.
+  if (found_image && has_system && has_cache) {
+    std::string local_error_msg;
+    // Check that the files are matching.
+    if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str(), &local_error_msg)) {
+      std::unique_ptr<ImageSpace> relocated_space =
+          ImageSpaceLoader::Load(image_location,
+                                 cache_filename,
+                                 is_zygote,
+                                 is_global_cache,
+                                 /* is_system */ false,
+                                 /* relocated_version_used */ true,
+                                 &local_error_msg);
+      if (relocated_space != nullptr) {
+        return relocated_space;
+      }
+    }
+    error_msgs.push_back(local_error_msg);
+  }
+
+  // Step 1.b: Only have a cache file.
+  if (found_image && !has_system && has_cache) {
+    std::string local_error_msg;
+    std::unique_ptr<ImageSpace> cache_space =
+        ImageSpaceLoader::Load(image_location,
+                               cache_filename,
+                               is_zygote,
+                               is_global_cache,
+                               /* is_system */ false,
+                               /* relocated_version_used */ true,
+                               &local_error_msg);
+    if (cache_space != nullptr) {
+      return cache_space;
+    }
+    error_msgs.push_back(local_error_msg);
+  }
+
+  // Step 2: We have an existing image in /system.
+
+  // Step 2.a: We are not required to relocate it. Then we can use it directly.
+  bool relocate = Runtime::Current()->ShouldRelocate();
+
+  if (found_image && has_system && !relocate) {
+    std::string local_error_msg;
+    std::unique_ptr<ImageSpace> system_space =
+        ImageSpaceLoader::Load(image_location,
+                               system_filename,
+                               is_zygote,
+                               is_global_cache,
+                               /* is_system */ true,
+                               /* relocated_version_used */ false,
+                               &local_error_msg);
+    if (system_space != nullptr) {
+      return system_space;
+    }
+    error_msgs.push_back(local_error_msg);
+  }
+
+  // Step 2.b: We require a relocated image. Then we must patch it. This step fails if this is a
+  //           secondary image.
+  if (found_image && has_system && relocate) {
+    std::string local_error_msg;
+    if (!Runtime::Current()->IsImageDex2OatEnabled()) {
+      local_error_msg = "Patching disabled.";
+    } else if (secondary_image) {
+      local_error_msg = "Cannot patch a secondary image.";
+    } else if (ImageCreationAllowed(is_global_cache, &local_error_msg)) {
+      bool patch_success =
+          RelocateImage(image_location, cache_filename.c_str(), image_isa, &local_error_msg);
+      if (patch_success) {
+        std::unique_ptr<ImageSpace> patched_space =
+            ImageSpaceLoader::Load(image_location,
+                                   cache_filename,
+                                   is_zygote,
+                                   is_global_cache,
+                                   /* is_system */ false,
+                                   /* relocated_version_used */ true,
+                                   &local_error_msg);
+        if (patched_space != nullptr) {
+          return patched_space;
+        }
+      }
+    }
+    error_msgs.push_back(StringPrintf("Cannot relocate image %s to %s: %s",
+                                      image_location,
+                                      cache_filename.c_str(),
+                                      local_error_msg.c_str()));
+  }
+
+  // Step 3: We do not have an existing image in /system, so generate an image into the dalvik
+  //         cache. This step fails if this is a secondary image.
+  if (!has_system) {
+    std::string local_error_msg;
+    if (!Runtime::Current()->IsImageDex2OatEnabled()) {
+      local_error_msg = "Image compilation disabled.";
+    } else if (secondary_image) {
+      local_error_msg = "Cannot compile a secondary image.";
+    } else if (ImageCreationAllowed(is_global_cache, &local_error_msg)) {
+      bool compilation_success = GenerateImage(cache_filename, image_isa, &local_error_msg);
+      if (compilation_success) {
+        std::unique_ptr<ImageSpace> compiled_space =
+            ImageSpaceLoader::Load(image_location,
+                                   cache_filename,
+                                   is_zygote,
+                                   is_global_cache,
+                                   /* is_system */ false,
+                                   /* relocated_version_used */ true,
+                                   &local_error_msg);
+        if (compiled_space != nullptr) {
+          return compiled_space;
+        }
+      }
+    }
+    error_msgs.push_back(StringPrintf("Cannot compile image to %s: %s",
+                                      cache_filename.c_str(),
+                                      local_error_msg.c_str()));
+  }
+
+  // We failed. Prune the cache the free up space, create a compound error message and return no
+  // image.
+  PruneDalvikCache(image_isa);
+
+  std::ostringstream oss;
+  bool first = true;
+  for (auto msg : error_msgs) {
+    if (!first) {
+      oss << "\n    ";
+    }
+    oss << msg;
+  }
+  *error_msg = oss.str();
+
+  return nullptr;
+}
+
+std::unique_ptr<ImageSpace> ImageSpace::CreateFromAppImage(const char* image,
+                                                           const OatFile* oat_file,
+                                                           std::string* error_msg) {
+  return ImageSpaceLoader::Init(image,
+                                image,
+                                /*validate_oat_file*/false,
+                                oat_file,
+                                /*out*/error_msg);
+}
+
 const OatFile* ImageSpace::GetOatFile() const {
   return oat_file_non_owned_;
 }
@@ -1570,16 +1667,6 @@
   }
 }
 
-ImageSpace* ImageSpace::CreateFromAppImage(const char* image,
-                                           const OatFile* oat_file,
-                                           std::string* error_msg) {
-  return gc::space::ImageSpace::Init(image,
-                                     image,
-                                     /*validate_oat_file*/false,
-                                     oat_file,
-                                     /*out*/error_msg);
-}
-
 void ImageSpace::DumpSections(std::ostream& os) const {
   const uint8_t* base = Begin();
   const ImageHeader& header = GetImageHeader();
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index c9741d0..534232d 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -43,24 +43,19 @@
   // creation of the alloc space. The ReleaseOatFile will later be
   // used to transfer ownership of the OatFile to the ClassLinker when
   // it is initialized.
-  static ImageSpace* CreateBootImage(const char* image,
+  static std::unique_ptr<ImageSpace> CreateBootImage(const char* image,
                                      InstructionSet image_isa,
                                      bool secondary_image,
                                      std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Try to open an existing app image space.
-  static ImageSpace* CreateFromAppImage(const char* image,
-                                        const OatFile* oat_file,
-                                        std::string* error_msg)
+  static std::unique_ptr<ImageSpace> CreateFromAppImage(const char* image,
+                                                        const OatFile* oat_file,
+                                                        std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Reads the image header from the specified image location for the
-  // instruction set image_isa or dies trying.
-  static ImageHeader* ReadImageHeaderOrDie(const char* image_location,
-                                           InstructionSet image_isa);
-
-  // Reads the image header from the specified image location for the
   // instruction set image_isa. Returns null on failure, with
   // reason in error_msg.
   static ImageHeader* ReadImageHeader(const char* image_location,
@@ -158,21 +153,13 @@
   // relative to its DexFile inputs. Otherwise (for /data), validate the inputs and generate the
   // OatFile in /data/dalvik-cache if necessary. If the oat_file is null, it uses the oat file from
   // the image.
-  static ImageSpace* Init(const char* image_filename,
-                          const char* image_location,
-                          bool validate_oat_file,
-                          const OatFile* oat_file,
-                          std::string* error_msg)
+  static std::unique_ptr<ImageSpace> Init(const char* image_filename,
+                                          const char* image_location,
+                                          bool validate_oat_file,
+                                          const OatFile* oat_file,
+                                          std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
-  OatFile* OpenOatFile(const char* image, std::string* error_msg) const
-      SHARED_REQUIRES(Locks::mutator_lock_);
-
-  bool ValidateOatFile(std::string* error_msg) const
-      SHARED_REQUIRES(Locks::mutator_lock_);
-
-  friend class Space;
-
   static Atomic<uint32_t> bitmap_index_;
 
   std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap_;
@@ -194,6 +181,9 @@
 
   const std::string image_location_;
 
+  friend class ImageSpaceLoader;
+  friend class Space;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ImageSpace);
 };
diff --git a/runtime/gc/space/image_space_fs.h b/runtime/gc/space/image_space_fs.h
index fc9a3cf..fa941c0 100644
--- a/runtime/gc/space/image_space_fs.h
+++ b/runtime/gc/space/image_space_fs.h
@@ -89,9 +89,11 @@
 static void PruneDalvikCache(InstructionSet isa) {
   CHECK_NE(isa, kNone);
   // Prune the base /data/dalvik-cache.
-  impl::DeleteDirectoryContents(GetDalvikCacheOrDie(".", false), false);
+  // Note: GetDalvikCache may return the empty string if the directory doesn't
+  // exist. It is safe to pass "" to DeleteDirectoryContents, so this is okay.
+  impl::DeleteDirectoryContents(GetDalvikCache("."), false);
   // Prune /data/dalvik-cache/<isa>.
-  impl::DeleteDirectoryContents(GetDalvikCacheOrDie(GetInstructionSetString(isa), false), false);
+  impl::DeleteDirectoryContents(GetDalvikCache(GetInstructionSetString(isa)), false);
 
   // Be defensive. There should be a runtime created here, but this may be called in a test.
   if (Runtime::Current() != nullptr) {
@@ -104,7 +106,8 @@
 // present, it usually means the boot didn't complete. We wipe the entire dalvik
 // cache if that's the case.
 static void MarkZygoteStart(const InstructionSet isa, const uint32_t max_failed_boots) {
-  const std::string isa_subdir = GetDalvikCacheOrDie(GetInstructionSetString(isa), false);
+  const std::string isa_subdir = GetDalvikCache(GetInstructionSetString(isa));
+  CHECK(!isa_subdir.empty()) << "Dalvik cache not found";
   const std::string boot_marker = isa_subdir + "/.booting";
   const char* file_name = boot_marker.c_str();
 
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 2c2a2b8..fe6332d 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -713,21 +713,15 @@
   CHECK(oat_filename != nullptr);
   CHECK(error_msg != nullptr);
 
-  // TODO: The work done in GetDalvikCache is overkill for what we need.
-  // Ideally a new API for getting the DalvikCacheDirectory the way we want
-  // (without existence testing, creation, or death) is provided with the rest
-  // of the GetDalvikCache family of functions. Until such an API is in place,
-  // we use GetDalvikCache to avoid duplicating the logic for determining the
-  // dalvik cache directory.
-  std::string dalvik_cache_dir;
-  bool ignored;
-  GetDalvikCache("", false, &dalvik_cache_dir, &ignored, &ignored, &ignored);
+  std::string cache_dir = GetDalvikCache(GetInstructionSetString(isa));
+  if (cache_dir.empty()) {
+    *error_msg = "Dalvik cache directory does not exist";
+    return false;
+  }
 
   // TODO: The oat file assistant should be the definitive place for
   // determining the oat file name from the dex location, not
   // GetDalvikCacheFilename.
-  std::string cache_dir = StringPrintf("%s%s",
-      dalvik_cache_dir.c_str(), GetInstructionSetString(isa));
   return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg);
 }
 
@@ -786,8 +780,12 @@
             image_header.GetOatDataBegin());
         cached_image_info_.patch_delta = image_header.GetPatchDelta();
       } else {
+        std::string error_msg;
         std::unique_ptr<ImageHeader> image_header(
-            gc::space::ImageSpace::ReadImageHeaderOrDie(cached_image_info_.location.c_str(), isa_));
+            gc::space::ImageSpace::ReadImageHeader(cached_image_info_.location.c_str(),
+                                                   isa_,
+                                                   &error_msg));
+        CHECK(image_header != nullptr) << error_msg;
         cached_image_info_.oat_checksum = image_header->GetOatChecksum();
         cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
             image_header->GetOatDataBegin());
@@ -813,8 +811,10 @@
   } else {
     for (gc::space::ImageSpace* image_space : image_spaces) {
       std::string location = image_space->GetImageLocation();
+      std::string error_msg;
       std::unique_ptr<ImageHeader> image_header(
-          gc::space::ImageSpace::ReadImageHeaderOrDie(location.c_str(), isa));
+          gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, &error_msg));
+      CHECK(image_header != nullptr) << error_msg;
       checksum ^= image_header->GetOatChecksum();
     }
   }
@@ -828,7 +828,7 @@
   return combined_image_checksum_;
 }
 
-gc::space::ImageSpace* OatFileAssistant::OpenImageSpace(const OatFile* oat_file) {
+std::unique_ptr<gc::space::ImageSpace> OatFileAssistant::OpenImageSpace(const OatFile* oat_file) {
   DCHECK(oat_file != nullptr);
   std::string art_file = ArtFileName(oat_file);
   if (art_file.empty()) {
@@ -836,9 +836,8 @@
   }
   std::string error_msg;
   ScopedObjectAccess soa(Thread::Current());
-  gc::space::ImageSpace* ret = gc::space::ImageSpace::CreateFromAppImage(art_file.c_str(),
-                                                                         oat_file,
-                                                                         &error_msg);
+  std::unique_ptr<gc::space::ImageSpace> ret =
+      gc::space::ImageSpace::CreateFromAppImage(art_file.c_str(), oat_file, &error_msg);
   if (ret == nullptr && (VLOG_IS_ON(image) || OS::FileExists(art_file.c_str()))) {
     LOG(INFO) << "Failed to open app image " << art_file.c_str() << " " << error_msg;
   }
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index effeabb..3f018dc 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -180,7 +180,7 @@
   std::unique_ptr<OatFile> GetBestOatFile();
 
   // Open and returns an image space associated with the oat file.
-  static gc::space::ImageSpace* OpenImageSpace(const OatFile* oat_file);
+  static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file);
 
   // Loads the dex files in the given oat file for the given dex location.
   // The oat file should be up to date for the given dex location.
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 7680517..2e67ffe 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -632,8 +632,8 @@
   if (source_oat_file != nullptr) {
     bool added_image_space = false;
     if (source_oat_file->IsExecutable()) {
-      std::unique_ptr<gc::space::ImageSpace> image_space(
-          kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr);
+      std::unique_ptr<gc::space::ImageSpace> image_space =
+          kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr;
       if (image_space != nullptr) {
         ScopedObjectAccess soa(self);
         StackHandleScope<1> hs(self);
diff --git a/runtime/simulator/code_simulator_arm64.h b/runtime/simulator/code_simulator_arm64.h
index 69388b1..59ea34f 100644
--- a/runtime/simulator/code_simulator_arm64.h
+++ b/runtime/simulator/code_simulator_arm64.h
@@ -20,10 +20,10 @@
 #include "memory"
 #include "simulator/code_simulator.h"
 
-// TODO: make vixl clean wrt -Wshadow.
+// TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wshadow"
-#include "a64/simulator-a64.h"
+#include "aarch64/simulator-aarch64.h"
 #pragma GCC diagnostic pop
 
 namespace art {
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 515ba9f..8ab8cd2 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -1087,58 +1087,18 @@
   }
 }
 
-static std::string GetDalvikCacheImpl(const char* subdir,
-                                      const bool create_if_absent,
-                                      const bool abort_on_error) {
+std::string GetDalvikCache(const char* subdir) {
   CHECK(subdir != nullptr);
   const char* android_data = GetAndroidData();
   const std::string dalvik_cache_root(StringPrintf("%s/dalvik-cache/", android_data));
   const std::string dalvik_cache = dalvik_cache_root + subdir;
   if (!OS::DirectoryExists(dalvik_cache.c_str())) {
-    if (!create_if_absent) {
-      // TODO: Check callers. Traditional behavior is to not to abort, even when abort_on_error.
-      return "";
-    }
-
-    // Don't create the system's /data/dalvik-cache/... because it needs special permissions.
-    if (strcmp(android_data, "/data") == 0) {
-      if (abort_on_error) {
-        LOG(FATAL) << "Failed to find dalvik-cache directory " << dalvik_cache
-                   << ", cannot create /data dalvik-cache.";
-        UNREACHABLE();
-      }
-      return "";
-    }
-
-    int result = mkdir(dalvik_cache_root.c_str(), 0700);
-    if (result != 0 && errno != EEXIST) {
-      if (abort_on_error) {
-        PLOG(FATAL) << "Failed to create dalvik-cache root directory " << dalvik_cache_root;
-        UNREACHABLE();
-      }
-      return "";
-    }
-
-    result = mkdir(dalvik_cache.c_str(), 0700);
-    if (result != 0) {
-      if (abort_on_error) {
-        PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache;
-        UNREACHABLE();
-      }
-      return "";
-    }
+    // TODO: Check callers. Traditional behavior is to not abort.
+    return "";
   }
   return dalvik_cache;
 }
 
-std::string GetDalvikCache(const char* subdir, const bool create_if_absent) {
-  return GetDalvikCacheImpl(subdir, create_if_absent, false);
-}
-
-std::string GetDalvikCacheOrDie(const char* subdir, const bool create_if_absent) {
-  return GetDalvikCacheImpl(subdir, create_if_absent, true);
-}
-
 bool GetDalvikCacheFilename(const char* location, const char* cache_location,
                             std::string* filename, std::string* error_msg) {
   if (location[0] != '/') {
diff --git a/runtime/utils.h b/runtime/utils.h
index 699b732..fd1ba23 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -251,11 +251,8 @@
 const char* GetAndroidDataSafe(std::string* error_msg);
 
 // Returns the dalvik-cache location, with subdir appended. Returns the empty string if the cache
-// could not be found (or created).
-std::string GetDalvikCache(const char* subdir, bool create_if_absent = true);
-// Returns the dalvik-cache location, or dies trying. subdir will be
-// appended to the cache location.
-std::string GetDalvikCacheOrDie(const char* subdir, bool create_if_absent = true);
+// could not be found.
+std::string GetDalvikCache(const char* subdir);
 // Return true if we found the dalvik cache and stored it in the dalvik_cache argument.
 // have_android_data will be set to true if we have an ANDROID_DATA that exists,
 // dalvik_cache_exists will be true if there is a dalvik-cache directory that is present.
diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc
index 55b6e01..d2100d1 100644
--- a/runtime/utils_test.cc
+++ b/runtime/utils_test.cc
@@ -337,11 +337,9 @@
 }
 
 TEST_F(UtilsTest, GetDalvikCache) {
-  EXPECT_STREQ("", GetDalvikCache("should-not-exist123", false).c_str());
+  EXPECT_STREQ("", GetDalvikCache("should-not-exist123").c_str());
 
-  EXPECT_STREQ((android_data_ + "/dalvik-cache/.").c_str(), GetDalvikCache(".", false).c_str());
-  EXPECT_STREQ((android_data_ + "/dalvik-cache/should-not-be-there").c_str(),
-               GetDalvikCache("should-not-be-there", true).c_str());
+  EXPECT_STREQ((android_data_ + "/dalvik-cache/.").c_str(), GetDalvikCache(".").c_str());
 }
 
 
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 05ac5f4..b87e142 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -555,10 +555,13 @@
 # Tests that should fail in the read barrier configuration with the Optimizing compiler (AOT).
 # 484: Baker's fast path based read barrier compiler instrumentation generates code containing
 #      more parallel moves on x86, thus some Checker assertions may fail.
+# 527: On ARM64 and ARM, the read barrier instrumentation does not support the HIntermediateAddress
+#      instruction yet (b/26601270).
 # 537: Expects an array copy to be intrinsified on x86-64, but calling-on-slowpath intrinsics are
 #      not yet handled in the read barrier configuration.
 TEST_ART_BROKEN_OPTIMIZING_READ_BARRIER_RUN_TESTS := \
   484-checker-register-hints \
+  527-checker-array-access-split \
   537-checker-arraycopy
 
 # Tests that should fail in the read barrier configuration with JIT (Optimizing compiler).