Dex2oat support for multiple oat file and image file outputs.

Multiple changes to dex2oat and the runtime to support a --multi-image
option. This generates a separate oat file and image file output for
each dex file input.

Change-Id: Ie1d6f0b8afa8aed5790065b8c2eb177990c60129
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk
index edf107e..c9af1c6 100644
--- a/build/Android.common_test.mk
+++ b/build/Android.common_test.mk
@@ -114,6 +114,9 @@
 # Do you want run-tests with the --debuggable flag
 ART_TEST_RUN_TEST_DEBUGGABLE ?= $(ART_TEST_FULL)
 
+# Do you want to test multi-part boot-image functionality?
+ART_TEST_RUN_TEST_MULTI_IMAGE ?= $(ART_TEST_FULL)
+
 # Define the command run on test failure. $(1) is the name of the test. Executed by the shell.
 define ART_TEST_FAILED
   ( [ -f $(ART_HOST_TEST_DIR)/skipped/$(1) ] || \
diff --git a/build/Android.oat.mk b/build/Android.oat.mk
index 50600ef..884f698 100644
--- a/build/Android.oat.mk
+++ b/build/Android.oat.mk
@@ -42,6 +42,7 @@
 # $(3): 2ND_ or undefined, 2ND_ for 32-bit host builds.
 # $(4): wrapper, e.g., valgrind.
 # $(5): dex2oat suffix, e.g, valgrind requires 32 right now.
+# $(6): multi-image.
 # NB depending on HOST_CORE_DEX_LOCATIONS so we are sure to have the dex files in frameworks for
 # run-test --no-image
 define create-core-oat-host-rules
@@ -92,14 +93,25 @@
     $$(error found $(2) expected pic or no-pic)
   endif
 
-  core_image_name := $($(3)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_IMG_SUFFIX)
-  core_oat_name := $($(3)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_OAT_SUFFIX)
+  # If $(6) is true, generate a multi-image.
+  ifeq ($(6),true)
+    core_multi_infix := -multi
+    core_multi_param := --multi-image --no-inline-from=core-oj-hostdex.jar
+    core_multi_group := _multi
+  else
+    core_multi_infix :=
+    core_multi_param :=
+    core_multi_group :=
+  endif
+
+  core_image_name := $($(3)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_IMG_SUFFIX)
+  core_oat_name := $($(3)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_OAT_SUFFIX)
 
   # Using the bitness suffix makes it easier to add as a dependency for the run-test mk.
   ifeq ($(3),)
-    $(4)HOST_CORE_IMAGE_$(1)_$(2)_64 := $$(core_image_name)
+    $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_64 := $$(core_image_name)
   else
-    $(4)HOST_CORE_IMAGE_$(1)_$(2)_32 := $$(core_image_name)
+    $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_32 := $$(core_image_name)
   endif
   $(4)HOST_CORE_IMG_OUTS += $$(core_image_name)
   $(4)HOST_CORE_OAT_OUTS += $$(core_oat_name)
@@ -111,6 +123,7 @@
 $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
 $$(core_image_name): PRIVATE_CORE_IMG_NAME := $$(core_image_name)
 $$(core_image_name): PRIVATE_CORE_OAT_NAME := $$(core_oat_name)
+$$(core_image_name): PRIVATE_CORE_MULTI_PARAM := $$(core_multi_param)
 $$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency)
 	@echo "host dex2oat: $$@"
 	@mkdir -p $$(dir $$@)
@@ -122,7 +135,7 @@
 	  --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(3)ART_HOST_ARCH) \
 	  $$(LOCAL_$(3)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
 	  --host --android-root=$$(HOST_OUT) --include-patch-information --generate-debug-info \
-	  $$(PRIVATE_CORE_COMPILE_OPTIONS)
+	  $$(PRIVATE_CORE_MULTI_PARAM) $$(PRIVATE_CORE_COMPILE_OPTIONS)
 
 $$(core_oat_name): $$(core_image_name)
 
@@ -138,32 +151,40 @@
 # $(1): compiler - default, optimizing, jit, interpreter or interpreter-access-checks.
 # $(2): wrapper.
 # $(3): dex2oat suffix.
+# $(4): multi-image.
 define create-core-oat-host-rule-combination
-  $(call create-core-oat-host-rules,$(1),no-pic,,$(2),$(3))
-  $(call create-core-oat-host-rules,$(1),pic,,$(2),$(3))
+  $(call create-core-oat-host-rules,$(1),no-pic,,$(2),$(3),$(4))
+  $(call create-core-oat-host-rules,$(1),pic,,$(2),$(3),$(4))
 
   ifneq ($(HOST_PREFER_32_BIT),true)
-    $(call create-core-oat-host-rules,$(1),no-pic,2ND_,$(2),$(3))
-    $(call create-core-oat-host-rules,$(1),pic,2ND_,$(2),$(3))
+    $(call create-core-oat-host-rules,$(1),no-pic,2ND_,$(2),$(3),$(4))
+    $(call create-core-oat-host-rules,$(1),pic,2ND_,$(2),$(3),$(4))
   endif
 endef
 
-$(eval $(call create-core-oat-host-rule-combination,default,,))
-$(eval $(call create-core-oat-host-rule-combination,optimizing,,))
-$(eval $(call create-core-oat-host-rule-combination,interpreter,,))
-$(eval $(call create-core-oat-host-rule-combination,interp-ac,,))
-$(eval $(call create-core-oat-host-rule-combination,jit,,))
+$(eval $(call create-core-oat-host-rule-combination,default,,,false))
+$(eval $(call create-core-oat-host-rule-combination,optimizing,,,false))
+$(eval $(call create-core-oat-host-rule-combination,interpreter,,,false))
+$(eval $(call create-core-oat-host-rule-combination,interp-ac,,,false))
+$(eval $(call create-core-oat-host-rule-combination,jit,,,false))
+$(eval $(call create-core-oat-host-rule-combination,default,,,true))
+$(eval $(call create-core-oat-host-rule-combination,optimizing,,,true))
+$(eval $(call create-core-oat-host-rule-combination,interpreter,,,true))
+$(eval $(call create-core-oat-host-rule-combination,interp-ac,,,true))
+$(eval $(call create-core-oat-host-rule-combination,jit,,,true))
 
 valgrindHOST_CORE_IMG_OUTS :=
 valgrindHOST_CORE_OAT_OUTS :=
-$(eval $(call create-core-oat-host-rule-combination,default,valgrind,32))
-$(eval $(call create-core-oat-host-rule-combination,optimizing,valgrind,32))
-$(eval $(call create-core-oat-host-rule-combination,interpreter,valgrind,32))
-$(eval $(call create-core-oat-host-rule-combination,interp-ac,valgrind,32))
-$(eval $(call create-core-oat-host-rule-combination,jit,valgrind,32))
+$(eval $(call create-core-oat-host-rule-combination,default,valgrind,32,false))
+$(eval $(call create-core-oat-host-rule-combination,optimizing,valgrind,32,false))
+$(eval $(call create-core-oat-host-rule-combination,interpreter,valgrind,32,false))
+$(eval $(call create-core-oat-host-rule-combination,interp-ac,valgrind,32,false))
+$(eval $(call create-core-oat-host-rule-combination,jit,valgrind,32,false))
 
 valgrind-test-art-host-dex2oat-host: $(valgrindHOST_CORE_IMG_OUTS)
 
+test-art-host-dex2oat-host: $(HOST_CORE_IMG_OUTS)
+
 define create-core-oat-target-rules
   core_compile_options :=
   core_image_name :=
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h
index 4aced5b..4dcaf80 100644
--- a/cmdline/cmdline.h
+++ b/cmdline/cmdline.h
@@ -80,8 +80,7 @@
   }
 }
 
-static Runtime* StartRuntime(const char* boot_image_location,
-                             InstructionSet instruction_set) {
+static Runtime* StartRuntime(const char* boot_image_location, InstructionSet instruction_set) {
   CHECK(boot_image_location != nullptr);
 
   RuntimeOptions options;
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index c7c1907..278c490 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -208,7 +208,8 @@
                                             false,
                                             timer_.get(),
                                             -1,
-                                            ""));
+                                            /* profile_file */ "",
+                                            /* dex_to_oat_map */ nullptr));
   // We typically don't generate an image in unit tests, disable this optimization by default.
   compiler_driver_->SetSupportBootImageFixup(false);
 }
diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc
index f48947d..32d7518 100644
--- a/compiler/dex/quick/dex_file_method_inliner.cc
+++ b/compiler/dex/quick/dex_file_method_inliner.cc
@@ -22,6 +22,7 @@
 #include "base/macros.h"
 #include "base/mutex-inl.h"
 #include "dex/compiler_ir.h"
+#include "driver/compiler_driver.h"
 #include "thread-inl.h"
 #include "dex/mir_graph.h"
 #include "dex/quick/mir_to_lir.h"
@@ -777,6 +778,17 @@
 
 bool DexFileMethodInliner::GenInline(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                                      uint32_t method_idx) {
+  // Check that we're allowed to inline.
+  {
+    CompilationUnit* cu = mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit();
+    if (!cu->compiler_driver->MayInline(dex_file_, cu->dex_file)) {
+      VLOG(compiler) << "Won't inline " << method_idx << " in "
+                     << cu->dex_file->GetLocation() << " from "
+                     << dex_file_->GetLocation();
+      return false;
+    }
+  }
+
   InlineMethod method;
   {
     ReaderMutexLock mu(Thread::Current(), lock_);
diff --git a/compiler/dex/quick/quick_cfi_test.cc b/compiler/dex/quick/quick_cfi_test.cc
index 24daf2f..bcf20c7 100644
--- a/compiler/dex/quick/quick_cfi_test.cc
+++ b/compiler/dex/quick/quick_cfi_test.cc
@@ -58,6 +58,7 @@
       CompilerOptions::kDefaultNumDexMethodsThreshold,
       CompilerOptions::kDefaultInlineDepthLimit,
       CompilerOptions::kDefaultInlineMaxCodeUnits,
+      nullptr,
       false,
       CompilerOptions::kDefaultTopKProfileThreshold,
       false,
@@ -74,9 +75,25 @@
     std::unique_ptr<const InstructionSetFeatures> isa_features;
     std::string error;
     isa_features.reset(InstructionSetFeatures::FromVariant(isa, "default", &error));
-    CompilerDriver driver(&compiler_options, &verification_results, &method_inliner_map,
-                          Compiler::kQuick, isa, isa_features.get(),
-                          false, nullptr, nullptr, nullptr, 0, false, false, "", false, 0, -1, "");
+    CompilerDriver driver(&compiler_options,
+                          &verification_results,
+                          &method_inliner_map,
+                          Compiler::kQuick,
+                          isa,
+                          isa_features.get(),
+                          false,
+                          nullptr,
+                          nullptr,
+                          nullptr,
+                          0,
+                          false,
+                          false,
+                          "",
+                          false,
+                          0,
+                          -1,
+                          "",
+                          nullptr);
     ClassLinker* linker = nullptr;
     CompilationUnit cu(&pool, isa, &driver, linker);
     DexFile::CodeItem code_item { 0, 0, 0, 0, 0, 0, { 0 } };  // NOLINT
diff --git a/compiler/dex/quick/x86/quick_assemble_x86_test.cc b/compiler/dex/quick/x86/quick_assemble_x86_test.cc
index e977ebf..9deabc0 100644
--- a/compiler/dex/quick/x86/quick_assemble_x86_test.cc
+++ b/compiler/dex/quick/x86/quick_assemble_x86_test.cc
@@ -41,6 +41,7 @@
         CompilerOptions::kDefaultNumDexMethodsThreshold,
         CompilerOptions::kDefaultInlineDepthLimit,
         CompilerOptions::kDefaultInlineMaxCodeUnits,
+        nullptr,
         false,
         CompilerOptions::kDefaultTopKProfileThreshold,
         false,
@@ -72,7 +73,8 @@
         false,
         0,
         -1,
-        ""));
+        "",
+        nullptr));
     cu_.reset(new CompilationUnit(pool_.get(), isa_, compiler_driver_.get(), nullptr));
     DexFile::CodeItem* code_item = static_cast<DexFile::CodeItem*>(
         cu_->arena.Alloc(sizeof(DexFile::CodeItem), kArenaAllocMisc));
diff --git a/compiler/driver/compiled_method_storage_test.cc b/compiler/driver/compiled_method_storage_test.cc
index c6dbd24..84fb432 100644
--- a/compiler/driver/compiled_method_storage_test.cc
+++ b/compiler/driver/compiled_method_storage_test.cc
@@ -45,7 +45,8 @@
                         false,
                         nullptr,
                         -1,
-                        "");
+                        "",
+                        nullptr);
   CompiledMethodStorage* storage = driver.GetCompiledMethodStorage();
 
   ASSERT_TRUE(storage->DedupeEnabled());  // The default.
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 5630b08..afb4b71 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -334,19 +334,21 @@
   DISALLOW_COPY_AND_ASSIGN(AOTCompilationStats);
 };
 
-CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options,
-                               VerificationResults* verification_results,
-                               DexFileToMethodInlinerMap* method_inliner_map,
-                               Compiler::Kind compiler_kind,
-                               InstructionSet instruction_set,
-                               const InstructionSetFeatures* instruction_set_features,
-                               bool boot_image, std::unordered_set<std::string>* image_classes,
-                               std::unordered_set<std::string>* compiled_classes,
-                               std::unordered_set<std::string>* compiled_methods,
-                               size_t thread_count, bool dump_stats, bool dump_passes,
-                               const std::string& dump_cfg_file_name, bool dump_cfg_append,
-                               CumulativeLogger* timer, int swap_fd,
-                               const std::string& profile_file)
+CompilerDriver::CompilerDriver(
+    const CompilerOptions* compiler_options,
+    VerificationResults* verification_results,
+    DexFileToMethodInlinerMap* method_inliner_map,
+    Compiler::Kind compiler_kind,
+    InstructionSet instruction_set,
+    const InstructionSetFeatures* instruction_set_features,
+    bool boot_image, std::unordered_set<std::string>* image_classes,
+    std::unordered_set<std::string>* compiled_classes,
+    std::unordered_set<std::string>* compiled_methods,
+    size_t thread_count, bool dump_stats, bool dump_passes,
+    const std::string& dump_cfg_file_name, bool dump_cfg_append,
+    CumulativeLogger* timer, int swap_fd,
+    const std::string& profile_file,
+    const std::unordered_map<const DexFile*, const char*>* dex_to_oat_map)
     : compiler_options_(compiler_options),
       verification_results_(verification_results),
       method_inliner_map_(method_inliner_map),
@@ -374,6 +376,7 @@
       compiler_context_(nullptr),
       support_boot_image_fixup_(instruction_set != kMips && instruction_set != kMips64),
       dex_files_for_oat_file_(nullptr),
+      dex_file_oat_filename_map_(dex_to_oat_map),
       compiled_method_storage_(swap_fd) {
   DCHECK(compiler_options_ != nullptr);
   DCHECK(verification_results_ != nullptr);
@@ -1538,6 +1541,12 @@
       use_dex_cache = true;
     }
   }
+  if (!use_dex_cache && IsBootImage()) {
+    if (!AreInSameOatFile(&(const_cast<mirror::Class*>(referrer_class)->GetDexFile()),
+                          &declaring_class->GetDexFile())) {
+      use_dex_cache = true;
+    }
+  }
   // The method is defined not within this dex file. We need a dex cache slot within the current
   // dex file or direct pointers.
   bool must_use_direct_pointers = false;
@@ -1571,12 +1580,14 @@
       *type = sharp_type;
     }
   } else {
-    auto* image_space = heap->GetBootImageSpace();
     bool method_in_image = false;
-    if (image_space != nullptr) {
+    const std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
+    for (gc::space::ImageSpace* image_space : image_spaces) {
       const auto& method_section = image_space->GetImageHeader().GetMethodsSection();
-      method_in_image = method_section.Contains(
-          reinterpret_cast<uint8_t*>(method) - image_space->Begin());
+      if (method_section.Contains(reinterpret_cast<uint8_t*>(method) - image_space->Begin())) {
+        method_in_image = true;
+        break;
+      }
     }
     if (method_in_image || compiling_boot || runtime->UseJit()) {
       // We know we must be able to get to the method in the image, so use that pointer.
@@ -2572,4 +2583,15 @@
   return inliner->IsStringInitMethodIndex(method_index);
 }
 
+bool CompilerDriver::MayInlineInternal(const DexFile* inlined_from,
+                                       const DexFile* inlined_into) const {
+  // We're not allowed to inline across dex files if we're the no-inline-from dex file.
+  if (inlined_from != inlined_into &&
+      compiler_options_->GetNoInlineFromDexFile() == inlined_from) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace art
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index f0360ce..fa0cb9a 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -97,7 +97,8 @@
                  size_t thread_count, bool dump_stats, bool dump_passes,
                  const std::string& dump_cfg_file_name, bool dump_cfg_append,
                  CumulativeLogger* timer, int swap_fd,
-                 const std::string& profile_file);
+                 const std::string& profile_file,
+                 const std::unordered_map<const DexFile*, const char*>* dex_to_oat_map);
 
   ~CompilerDriver();
 
@@ -113,6 +114,18 @@
         : ArrayRef<const DexFile* const>();
   }
 
+  // Are the given dex files compiled into the same oat file? Should only be called after
+  // GetDexFilesForOatFile, as the conservative answer (when we don't have a map) is true.
+  bool AreInSameOatFile(const DexFile* d1, const DexFile* d2) {
+    if (dex_file_oat_filename_map_ == nullptr) {
+      // TODO: Check for this wrt/ apps and boot image calls.
+      return true;
+    }
+    auto it1 = dex_file_oat_filename_map_->find(d1);
+    auto it2 = dex_file_oat_filename_map_->find(d2);
+    return it1 == it2;
+  }
+
   void CompileAll(jobject class_loader,
                   const std::vector<const DexFile*>& dex_files,
                   TimingLogger* timings)
@@ -471,6 +484,13 @@
   bool CanAssumeClassIsLoaded(mirror::Class* klass)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
+  bool MayInline(const DexFile* inlined_from, const DexFile* inlined_into) const {
+    if (!kIsTargetBuild) {
+      return MayInlineInternal(inlined_from, inlined_into);
+    }
+    return true;
+  }
+
  private:
   // Return whether the declaring class of `resolved_member` is
   // available to `referrer_class` for read or write access using two
@@ -587,6 +607,8 @@
                       ThreadPool* thread_pool, TimingLogger* timings)
       REQUIRES(!Locks::mutator_lock_);
 
+  bool MayInlineInternal(const DexFile* inlined_from, const DexFile* inlined_into) const;
+
   const CompilerOptions* const compiler_options_;
   VerificationResults* const verification_results_;
   DexFileToMethodInlinerMap* const method_inliner_map_;
@@ -621,9 +643,8 @@
 
   const bool boot_image_;
 
-  // If image_ is true, specifies the classes that will be included in
-  // the image. Note if image_classes_ is null, all classes are
-  // included in the image.
+  // If image_ is true, specifies the classes that will be included in the image.
+  // Note if image_classes_ is null, all classes are included in the image.
   std::unique_ptr<std::unordered_set<std::string>> image_classes_;
 
   // Specifies the classes that will be compiled. Note that if classes_to_compile_ is null,
@@ -663,6 +684,9 @@
   // List of dex files that will be stored in the oat file.
   const std::vector<const DexFile*>* dex_files_for_oat_file_;
 
+  // Map from dex files to the oat file (name) they will be compiled into.
+  const std::unordered_map<const DexFile*, const char*>* dex_file_oat_filename_map_;
+
   CompiledMethodStorage compiled_method_storage_;
 
   friend class CompileClassVisitor;
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 8c38cf2..209bb5a 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -31,6 +31,7 @@
       num_dex_methods_threshold_(kDefaultNumDexMethodsThreshold),
       inline_depth_limit_(kUnsetInlineDepthLimit),
       inline_max_code_units_(kUnsetInlineMaxCodeUnits),
+      no_inline_from_(nullptr),
       include_patch_information_(kDefaultIncludePatchInformation),
       top_k_profile_threshold_(kDefaultTopKProfileThreshold),
       debuggable_(false),
@@ -59,6 +60,7 @@
                                  size_t num_dex_methods_threshold,
                                  size_t inline_depth_limit,
                                  size_t inline_max_code_units,
+                                 const DexFile* no_inline_from,
                                  bool include_patch_information,
                                  double top_k_profile_threshold,
                                  bool debuggable,
@@ -79,6 +81,7 @@
     num_dex_methods_threshold_(num_dex_methods_threshold),
     inline_depth_limit_(inline_depth_limit),
     inline_max_code_units_(inline_max_code_units),
+    no_inline_from_(no_inline_from),
     include_patch_information_(include_patch_information),
     top_k_profile_threshold_(top_k_profile_threshold),
     debuggable_(debuggable),
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index 2b047a2..9ad1bee 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -72,6 +72,7 @@
                   size_t num_dex_methods_threshold,
                   size_t inline_depth_limit,
                   size_t inline_max_code_units,
+                  const DexFile* no_inline_from,
                   bool include_patch_information,
                   double top_k_profile_threshold,
                   bool debuggable,
@@ -217,6 +218,10 @@
     return abort_on_hard_verifier_failure_;
   }
 
+  const DexFile* GetNoInlineFromDexFile() const {
+    return no_inline_from_;
+  }
+
   bool ParseCompilerOption(const StringPiece& option, UsageFn Usage);
 
  private:
@@ -241,6 +246,10 @@
   size_t num_dex_methods_threshold_;
   size_t inline_depth_limit_;
   size_t inline_max_code_units_;
+
+  // A dex file from which we should not inline code.
+  const DexFile* no_inline_from_;
+
   bool include_patch_information_;
   // When using a profile file only the top K% of the profiled samples will be compiled.
   double top_k_profile_threshold_;
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index 5afe2db..6859605 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -72,11 +72,18 @@
   ScratchFile oat_file(OS::CreateEmptyFile(oat_filename.c_str()));
 
   const uintptr_t requested_image_base = ART_BASE_ADDRESS;
+  std::unordered_map<const DexFile*, const char*> dex_file_to_oat_filename_map;
+  std::vector<const char*> oat_filename_vector(1, oat_filename.c_str());
+  for (const DexFile* dex_file : class_linker->GetBootClassPath()) {
+    dex_file_to_oat_filename_map.emplace(dex_file, oat_filename.c_str());
+  }
   std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_,
                                                       requested_image_base,
                                                       /*compile_pic*/false,
                                                       /*compile_app_image*/false,
-                                                      storage_mode));
+                                                      storage_mode,
+                                                      oat_filename_vector,
+                                                      dex_file_to_oat_filename_map));
   // TODO: compile_pic should be a test argument.
   {
     {
@@ -131,12 +138,12 @@
   ASSERT_TRUE(dup_oat.get() != nullptr);
 
   {
-    bool success_image = writer->Write(kInvalidImageFd,
-                                       image_file.GetFilename(),
-                                       dup_oat->GetPath(),
-                                       dup_oat->GetPath());
+    std::vector<const char*> dup_oat_filename(1, dup_oat->GetPath().c_str());
+    std::vector<const char*> dup_image_filename(1, image_file.GetFilename().c_str());
+    bool success_image = writer->Write(kInvalidImageFd, dup_image_filename, dup_oat_filename);
     ASSERT_TRUE(success_image);
-    bool success_fixup = ElfWriter::Fixup(dup_oat.get(), writer->GetOatDataBegin());
+    bool success_fixup = ElfWriter::Fixup(dup_oat.get(),
+                                          writer->GetOatDataBegin(dup_oat_filename[0]));
     ASSERT_TRUE(success_fixup);
 
     ASSERT_EQ(dup_oat->FlushCloseOrErase(), 0) << "Could not flush and close oat file "
@@ -203,10 +210,11 @@
   class_linker_ = runtime_->GetClassLinker();
 
   gc::Heap* heap = Runtime::Current()->GetHeap();
-  ASSERT_TRUE(heap->HasImageSpace());
+  ASSERT_TRUE(heap->HasBootImageSpace());
   ASSERT_TRUE(heap->GetNonMovingSpace()->IsMallocSpace());
 
-  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
+  // We loaded the runtime with an explicit image, so it must exist.
+  gc::space::ImageSpace* image_space = heap->GetBootImageSpaces()[0];
   ASSERT_TRUE(image_space != nullptr);
   if (storage_mode == ImageHeader::kStorageModeUncompressed) {
     // Uncompressed, image should be smaller than file.
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 9545c83..17d0f61 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -154,147 +154,174 @@
 }
 
 bool ImageWriter::Write(int image_fd,
-                        const std::string& image_filename,
-                        const std::string& oat_filename,
-                        const std::string& oat_location) {
-  CHECK(!image_filename.empty());
+                        const std::vector<const char*>& image_filenames,
+                        const std::vector<const char*>& oat_filenames) {
+  CHECK(!image_filenames.empty());
+  CHECK(!oat_filenames.empty());
+  CHECK_EQ(image_filenames.size(), oat_filenames.size());
 
-  std::unique_ptr<File> oat_file(OS::OpenFileReadWrite(oat_filename.c_str()));
-  if (oat_file.get() == nullptr) {
-    PLOG(ERROR) << "Failed to open oat file " << oat_filename << " for " << oat_location;
-    return false;
-  }
-  std::string error_msg;
-  oat_file_ = OatFile::OpenReadable(oat_file.get(), oat_location, nullptr, &error_msg);
-  if (oat_file_ == nullptr) {
-    PLOG(ERROR) << "Failed to open writable oat file " << oat_filename << " for " << oat_location
-        << ": " << error_msg;
-    oat_file->Erase();
-    return false;
-  }
-  Runtime::Current()->GetOatFileManager().RegisterOatFile(
+  size_t oat_file_offset = 0;
+
+  for (size_t i = 0; i < oat_filenames.size(); ++i) {
+    const char* oat_filename = oat_filenames[i];
+    std::unique_ptr<File> oat_file(OS::OpenFileReadWrite(oat_filename));
+    if (oat_file.get() == nullptr) {
+      PLOG(ERROR) << "Failed to open oat file " << oat_filename;
+      return false;
+    }
+    std::string error_msg;
+    oat_file_ = OatFile::OpenReadable(oat_file.get(), oat_filename, nullptr, &error_msg);
+    if (oat_file_ == nullptr) {
+      PLOG(ERROR) << "Failed to open writable oat file " << oat_filename;
+      oat_file->Erase();
+      return false;
+    }
+    Runtime::Current()->GetOatFileManager().RegisterOatFile(
       std::unique_ptr<const OatFile>(oat_file_));
 
-  const OatHeader& oat_header = oat_file_->GetOatHeader();
-  oat_address_offsets_[kOatAddressInterpreterToInterpreterBridge] =
-      oat_header.GetInterpreterToInterpreterBridgeOffset();
-  oat_address_offsets_[kOatAddressInterpreterToCompiledCodeBridge] =
-      oat_header.GetInterpreterToCompiledCodeBridgeOffset();
-  oat_address_offsets_[kOatAddressJNIDlsymLookup] =
-      oat_header.GetJniDlsymLookupOffset();
-  oat_address_offsets_[kOatAddressQuickGenericJNITrampoline] =
-      oat_header.GetQuickGenericJniTrampolineOffset();
-  oat_address_offsets_[kOatAddressQuickIMTConflictTrampoline] =
-      oat_header.GetQuickImtConflictTrampolineOffset();
-  oat_address_offsets_[kOatAddressQuickResolutionTrampoline] =
-      oat_header.GetQuickResolutionTrampolineOffset();
-  oat_address_offsets_[kOatAddressQuickToInterpreterBridge] =
-      oat_header.GetQuickToInterpreterBridgeOffset();
+    const OatHeader& oat_header = oat_file_->GetOatHeader();
+    ImageInfo& image_info = GetImageInfo(oat_filename);
 
-  size_t oat_loaded_size = 0;
-  size_t oat_data_offset = 0;
-  ElfWriter::GetOatElfInformation(oat_file.get(), &oat_loaded_size, &oat_data_offset);
+    size_t oat_loaded_size = 0;
+    size_t oat_data_offset = 0;
+    ElfWriter::GetOatElfInformation(oat_file.get(), &oat_loaded_size, &oat_data_offset);
+
+    DCHECK_EQ(image_info.oat_offset_, oat_file_offset);
+    oat_file_offset += oat_loaded_size;
+
+    if (i == 0) {
+      // Primary oat file, read the trampolines.
+      image_info.oat_address_offsets_[kOatAddressInterpreterToInterpreterBridge] =
+          oat_header.GetInterpreterToInterpreterBridgeOffset();
+      image_info.oat_address_offsets_[kOatAddressInterpreterToCompiledCodeBridge] =
+          oat_header.GetInterpreterToCompiledCodeBridgeOffset();
+      image_info.oat_address_offsets_[kOatAddressJNIDlsymLookup] =
+          oat_header.GetJniDlsymLookupOffset();
+      image_info.oat_address_offsets_[kOatAddressQuickGenericJNITrampoline] =
+          oat_header.GetQuickGenericJniTrampolineOffset();
+      image_info.oat_address_offsets_[kOatAddressQuickIMTConflictTrampoline] =
+          oat_header.GetQuickImtConflictTrampolineOffset();
+      image_info.oat_address_offsets_[kOatAddressQuickResolutionTrampoline] =
+          oat_header.GetQuickResolutionTrampolineOffset();
+      image_info.oat_address_offsets_[kOatAddressQuickToInterpreterBridge] =
+          oat_header.GetQuickToInterpreterBridgeOffset();
+    } else {
+      // Other oat files use the primary trampolines.
+      // TODO: Dummy values to protect usage? b/26317072
+    }
+
+
+    {
+      ScopedObjectAccess soa(Thread::Current());
+      CreateHeader(oat_loaded_size, oat_data_offset);
+      CopyAndFixupNativeData();
+    }
+
+    SetOatChecksumFromElfFile(oat_file.get());
+
+    if (oat_file->FlushCloseOrErase() != 0) {
+      LOG(ERROR) << "Failed to flush and close oat file " << oat_filename;
+      return false;
+    }
+  }
 
   {
-    ScopedObjectAccess soa(Thread::Current());
-    CreateHeader(oat_loaded_size, oat_data_offset);
-    CopyAndFixupNativeData();
     // TODO: heap validation can't handle these fix up passes.
+    ScopedObjectAccess soa(Thread::Current());
     Runtime::Current()->GetHeap()->DisableObjectValidation();
     CopyAndFixupObjects();
   }
 
-  SetOatChecksumFromElfFile(oat_file.get());
-
-  if (oat_file->FlushCloseOrErase() != 0) {
-    LOG(ERROR) << "Failed to flush and close oat file " << oat_filename << " for " << oat_location;
-    return false;
-  }
-  std::unique_ptr<File> image_file;
-  if (image_fd != kInvalidImageFd) {
-    image_file.reset(new File(image_fd, image_filename, unix_file::kCheckSafeUsage));
-  } else {
-    image_file.reset(OS::CreateEmptyFile(image_filename.c_str()));
-  }
-  if (image_file == nullptr) {
-    LOG(ERROR) << "Failed to open image file " << image_filename;
-    return false;
-  }
-  if (fchmod(image_file->Fd(), 0644) != 0) {
-    PLOG(ERROR) << "Failed to make image file world readable: " << image_filename;
-    image_file->Erase();
-    return EXIT_FAILURE;
-  }
-
-  std::unique_ptr<char[]> compressed_data;
-  // Image data size excludes the bitmap and the header.
-  ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
-  const size_t image_data_size = image_header->GetImageSize() - sizeof(ImageHeader);
-  char* image_data = reinterpret_cast<char*>(image_->Begin()) + sizeof(ImageHeader);
-  size_t data_size;
-  const char* image_data_to_write;
-
-  CHECK_EQ(image_header->storage_mode_, image_storage_mode_);
-  switch (image_storage_mode_) {
-    case ImageHeader::kStorageModeLZ4: {
-      size_t compressed_max_size = LZ4_compressBound(image_data_size);
-      compressed_data.reset(new char[compressed_max_size]);
-      data_size = LZ4_compress(
-          reinterpret_cast<char*>(image_->Begin()) + sizeof(ImageHeader),
-          &compressed_data[0],
-          image_data_size);
-      image_data_to_write = &compressed_data[0];
-      VLOG(compiler) << "Compressed from " << image_data_size << " to " << data_size;
-      break;
+  for (size_t i = 0; i < image_filenames.size(); ++i) {
+    const char* image_filename = image_filenames[i];
+    const char* oat_filename = oat_filenames[i];
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    std::unique_ptr<File> image_file;
+    if (image_fd != kInvalidImageFd) {
+      image_file.reset(new File(image_fd, image_filename, unix_file::kCheckSafeUsage));
+    } else {
+      image_file.reset(OS::CreateEmptyFile(image_filename));
     }
-    case ImageHeader::kStorageModeUncompressed: {
-      data_size = image_data_size;
-      image_data_to_write = image_data;
-      break;
+    if (image_file == nullptr) {
+      LOG(ERROR) << "Failed to open image file " << image_filename;
+      return false;
     }
-    default: {
-      LOG(FATAL) << "Unsupported";
-      UNREACHABLE();
+    if (fchmod(image_file->Fd(), 0644) != 0) {
+      PLOG(ERROR) << "Failed to make image file world readable: " << image_filename;
+      image_file->Erase();
+      return EXIT_FAILURE;
     }
-  }
 
-  // Write header first, as uncompressed.
-  image_header->data_size_ = data_size;
-  if (!image_file->WriteFully(image_->Begin(), sizeof(ImageHeader))) {
-    PLOG(ERROR) << "Failed to write image file header " << image_filename;
-    image_file->Erase();
-    return false;
-  }
+    std::unique_ptr<char[]> compressed_data;
+    // Image data size excludes the bitmap and the header.
+    ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin());
+    const size_t image_data_size = image_header->GetImageSize() - sizeof(ImageHeader);
+    char* image_data = reinterpret_cast<char*>(image_info.image_->Begin()) + sizeof(ImageHeader);
+    size_t data_size;
+    const char* image_data_to_write;
 
-  // Write out the image + fields + methods.
-  const bool is_compressed = compressed_data != nullptr;
-  if (!image_file->WriteFully(image_data_to_write, data_size)) {
-    PLOG(ERROR) << "Failed to write image file data " << image_filename;
-    image_file->Erase();
-    return false;
-  }
+    CHECK_EQ(image_header->storage_mode_, image_storage_mode_);
+    switch (image_storage_mode_) {
+      case ImageHeader::kStorageModeLZ4: {
+        size_t compressed_max_size = LZ4_compressBound(image_data_size);
+        compressed_data.reset(new char[compressed_max_size]);
+        data_size = LZ4_compress(
+            reinterpret_cast<char*>(image_info.image_->Begin()) + sizeof(ImageHeader),
+            &compressed_data[0],
+            image_data_size);
+        image_data_to_write = &compressed_data[0];
+        VLOG(compiler) << "Compressed from " << image_data_size << " to " << data_size;
+        break;
+      }
+      case ImageHeader::kStorageModeUncompressed: {
+        data_size = image_data_size;
+        image_data_to_write = image_data;
+        break;
+      }
+      default: {
+        LOG(FATAL) << "Unsupported";
+        UNREACHABLE();
+      }
+    }
 
-  // Write out the image bitmap at the page aligned start of the image end, also uncompressed for
-  // convenience.
-  const ImageSection& bitmap_section = image_header->GetImageSection(
-      ImageHeader::kSectionImageBitmap);
-  // Align up since data size may be unaligned if the image is compressed.
-  size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + data_size, kPageSize);
-  if (!is_compressed) {
-    CHECK_EQ(bitmap_position_in_file, bitmap_section.Offset());
-  }
-  if (!image_file->Write(reinterpret_cast<char*>(image_bitmap_->Begin()),
-                         bitmap_section.Size(),
-                         bitmap_position_in_file)) {
-    PLOG(ERROR) << "Failed to write image file " << image_filename;
-    image_file->Erase();
-    return false;
-  }
-  CHECK_EQ(bitmap_position_in_file + bitmap_section.Size(),
-           static_cast<size_t>(image_file->GetLength()));
-  if (image_file->FlushCloseOrErase() != 0) {
-    PLOG(ERROR) << "Failed to flush and close image file " << image_filename;
-    return false;
+    // Write header first, as uncompressed.
+    image_header->data_size_ = data_size;
+    if (!image_file->WriteFully(image_info.image_->Begin(), sizeof(ImageHeader))) {
+      PLOG(ERROR) << "Failed to write image file header " << image_filename;
+      image_file->Erase();
+      return false;
+    }
+
+    // Write out the image + fields + methods.
+    const bool is_compressed = compressed_data != nullptr;
+    if (!image_file->WriteFully(image_data_to_write, data_size)) {
+      PLOG(ERROR) << "Failed to write image file data " << image_filename;
+      image_file->Erase();
+      return false;
+    }
+
+    // Write out the image bitmap at the page aligned start of the image end, also uncompressed for
+    // convenience.
+    const ImageSection& bitmap_section = image_header->GetImageSection(
+        ImageHeader::kSectionImageBitmap);
+    // Align up since data size may be unaligned if the image is compressed.
+    size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + data_size, kPageSize);
+    if (!is_compressed) {
+      CHECK_EQ(bitmap_position_in_file, bitmap_section.Offset());
+    }
+    if (!image_file->Write(reinterpret_cast<char*>(image_info.image_bitmap_->Begin()),
+                           bitmap_section.Size(),
+                           bitmap_position_in_file)) {
+      PLOG(ERROR) << "Failed to write image file " << image_filename;
+      image_file->Erase();
+      return false;
+    }
+    CHECK_EQ(bitmap_position_in_file + bitmap_section.Size(),
+             static_cast<size_t>(image_file->GetLength()));
+    if (image_file->FlushCloseOrErase() != 0) {
+      PLOG(ERROR) << "Failed to flush and close image file " << image_filename;
+      return false;
+    }
   }
   return true;
 }
@@ -319,12 +346,14 @@
   DCHECK(object != nullptr);
   DCHECK_NE(image_objects_offset_begin_, 0u);
 
-  size_t bin_slot_offset = bin_slot_offsets_[bin_slot.GetBin()];
+  const char* oat_filename = GetOatFilename(object);
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+  size_t bin_slot_offset = image_info.bin_slot_offsets_[bin_slot.GetBin()];
   size_t new_offset = bin_slot_offset + bin_slot.GetIndex();
   DCHECK_ALIGNED(new_offset, kObjectAlignment);
 
   SetImageOffset(object, new_offset);
-  DCHECK_LT(new_offset, image_end_);
+  DCHECK_LT(new_offset, image_info.image_end_);
 }
 
 bool ImageWriter::IsImageOffsetAssigned(mirror::Object* object) const {
@@ -338,7 +367,9 @@
   DCHECK(IsImageOffsetAssigned(object));
   LockWord lock_word = object->GetLockWord(false);
   size_t offset = lock_word.ForwardingAddress();
-  DCHECK_LT(offset, image_end_);
+  const char* oat_filename = GetOatFilename(object);
+  const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+  DCHECK_LT(offset, image_info.image_end_);
   return offset;
 }
 
@@ -377,15 +408,16 @@
 
 void ImageWriter::PrepareDexCacheArraySlots() {
   // Prepare dex cache array starts based on the ordering specified in the CompilerDriver.
-  uint32_t size = 0u;
-  for (const DexFile* dex_file : compiler_driver_.GetDexFilesForOatFile()) {
-    dex_cache_array_starts_.Put(dex_file, size);
-    DexCacheArraysLayout layout(target_ptr_size_, dex_file);
-    size += layout.Size();
-  }
   // Set the slot size early to avoid DCHECK() failures in IsImageBinSlotAssigned()
   // when AssignImageBinSlot() assigns their indexes out or order.
-  bin_slot_sizes_[kBinDexCacheArray] = size;
+  for (const DexFile* dex_file : compiler_driver_.GetDexFilesForOatFile()) {
+    auto it = dex_file_oat_filename_map_.find(dex_file);
+    DCHECK(it != dex_file_oat_filename_map_.end()) << dex_file->GetLocation();
+    ImageInfo& image_info = GetImageInfo(it->second);
+    image_info.dex_cache_array_starts_.Put(dex_file, image_info.bin_slot_sizes_[kBinDexCacheArray]);
+    DexCacheArraysLayout layout(target_ptr_size_, dex_file);
+    image_info.bin_slot_sizes_[kBinDexCacheArray] += layout.Size();
+  }
 
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   Thread* const self = Thread::Current();
@@ -399,24 +431,32 @@
     const DexFile* dex_file = dex_cache->GetDexFile();
     DexCacheArraysLayout layout(target_ptr_size_, dex_file);
     DCHECK(layout.Valid());
-    uint32_t start = dex_cache_array_starts_.Get(dex_file);
+    const char* oat_filename = GetOatFilenameForDexCache(dex_cache);
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    uint32_t start = image_info.dex_cache_array_starts_.Get(dex_file);
     DCHECK_EQ(dex_file->NumTypeIds() != 0u, dex_cache->GetResolvedTypes() != nullptr);
-    AddDexCacheArrayRelocation(dex_cache->GetResolvedTypes(), start + layout.TypesOffset());
+    AddDexCacheArrayRelocation(dex_cache->GetResolvedTypes(),
+                               start + layout.TypesOffset(),
+                               dex_cache);
     DCHECK_EQ(dex_file->NumMethodIds() != 0u, dex_cache->GetResolvedMethods() != nullptr);
-    AddDexCacheArrayRelocation(dex_cache->GetResolvedMethods(), start + layout.MethodsOffset());
+    AddDexCacheArrayRelocation(dex_cache->GetResolvedMethods(),
+                               start + layout.MethodsOffset(),
+                               dex_cache);
     DCHECK_EQ(dex_file->NumFieldIds() != 0u, dex_cache->GetResolvedFields() != nullptr);
-    AddDexCacheArrayRelocation(dex_cache->GetResolvedFields(), start + layout.FieldsOffset());
+    AddDexCacheArrayRelocation(dex_cache->GetResolvedFields(),
+                               start + layout.FieldsOffset(),
+                               dex_cache);
     DCHECK_EQ(dex_file->NumStringIds() != 0u, dex_cache->GetStrings() != nullptr);
-    AddDexCacheArrayRelocation(dex_cache->GetStrings(), start + layout.StringsOffset());
+    AddDexCacheArrayRelocation(dex_cache->GetStrings(), start + layout.StringsOffset(), dex_cache);
   }
 }
 
-void ImageWriter::AddDexCacheArrayRelocation(void* array, size_t offset) {
+void ImageWriter::AddDexCacheArrayRelocation(void* array, size_t offset, DexCache* dex_cache) {
   if (array != nullptr) {
     DCHECK(!IsInBootImage(array));
-    native_object_relocations_.emplace(
-        array,
-        NativeObjectRelocation { offset, kNativeObjectRelocationTypeDexCacheArray });
+    const char* oat_filename = GetOatFilenameForDexCache(dex_cache);
+    native_object_relocations_.emplace(array,
+        NativeObjectRelocation { oat_filename, offset, kNativeObjectRelocationTypeDexCacheArray });
   }
 }
 
@@ -531,18 +571,21 @@
     }  // else bin = kBinRegular
   }
 
+  const char* oat_filename = GetOatFilename(object);
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+
   size_t offset_delta = RoundUp(object_size, kObjectAlignment);  // 64-bit alignment
-  current_offset = bin_slot_sizes_[bin];  // How many bytes the current bin is at (aligned).
-  // Move the current bin size up to accomodate the object we just assigned a bin slot.
-  bin_slot_sizes_[bin] += offset_delta;
+  current_offset = image_info.bin_slot_sizes_[bin];  // How many bytes the current bin is at (aligned).
+  // Move the current bin size up to accommodate the object we just assigned a bin slot.
+  image_info.bin_slot_sizes_[bin] += offset_delta;
 
   BinSlot new_bin_slot(bin, current_offset);
   SetImageBinSlot(object, new_bin_slot);
 
-  ++bin_slot_count_[bin];
+  ++image_info.bin_slot_count_[bin];
 
   // Grow the image closer to the end by the object we just assigned.
-  image_end_ += offset_delta;
+  image_info.image_end_ += offset_delta;
 }
 
 bool ImageWriter::WillMethodBeDirty(ArtMethod* m) const {
@@ -565,7 +608,9 @@
     LockWord lock_word = object->GetLockWord(false);
     size_t offset = lock_word.ForwardingAddress();
     BinSlot bin_slot(offset);
-    DCHECK_LT(bin_slot.GetIndex(), bin_slot_sizes_[bin_slot.GetBin()])
+    const char* oat_filename = GetOatFilename(object);
+    const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+    DCHECK_LT(bin_slot.GetIndex(), image_info.bin_slot_sizes_[bin_slot.GetBin()])
         << "bin slot offset should not exceed the size of that bin";
   }
   return true;
@@ -580,39 +625,42 @@
   DCHECK_LE(offset, std::numeric_limits<uint32_t>::max());
 
   BinSlot bin_slot(static_cast<uint32_t>(offset));
-  DCHECK_LT(bin_slot.GetIndex(), bin_slot_sizes_[bin_slot.GetBin()]);
+  const char* oat_filename = GetOatFilename(object);
+  const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+  DCHECK_LT(bin_slot.GetIndex(), image_info.bin_slot_sizes_[bin_slot.GetBin()]);
 
   return bin_slot;
 }
 
 bool ImageWriter::AllocMemory() {
-  const size_t length = RoundUp(image_objects_offset_begin_ +
-                                    GetBinSizeSum() +
-                                    intern_table_bytes_ +
-                                    class_table_bytes_,
-                                kPageSize);
-  std::string error_msg;
-  image_.reset(MemMap::MapAnonymous("image writer image",
-                                    nullptr,
-                                    length,
-                                    PROT_READ | PROT_WRITE,
-                                    false,
-                                    false,
-                                    &error_msg));
-  if (UNLIKELY(image_.get() == nullptr)) {
-    LOG(ERROR) << "Failed to allocate memory for image file generation: " << error_msg;
-    return false;
-  }
+  for (const char* oat_filename : oat_filenames_) {
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    const size_t length = RoundUp(image_objects_offset_begin_ +
+                                  GetBinSizeSum(image_info) +
+                                  intern_table_bytes_ +
+                                  class_table_bytes_,
+                                  kPageSize);
+    std::string error_msg;
+    image_info.image_.reset(MemMap::MapAnonymous("image writer image",
+                                                 nullptr,
+                                                 length,
+                                                 PROT_READ | PROT_WRITE,
+                                                 false,
+                                                 false,
+                                                 &error_msg));
+    if (UNLIKELY(image_info.image_.get() == nullptr)) {
+      LOG(ERROR) << "Failed to allocate memory for image file generation: " << error_msg;
+      return false;
+    }
 
-  // Create the image bitmap, only needs to cover mirror object section which is up to image_end_.
-  CHECK_LE(image_end_, length);
-  image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create(
-      "image bitmap",
-      image_->Begin(),
-      RoundUp(image_end_, kPageSize)));
-  if (image_bitmap_.get() == nullptr) {
-    LOG(ERROR) << "Failed to allocate memory for image bitmap";
-    return false;
+    // Create the image bitmap, only needs to cover mirror object section which is up to image_end_.
+    CHECK_LE(image_info.image_end_, length);
+    image_info.image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create(
+        "image bitmap", image_info.image_->Begin(), RoundUp(image_info.image_end_, kPageSize)));
+    if (image_info.image_bitmap_.get() == nullptr) {
+      LOG(ERROR) << "Failed to allocate memory for image bitmap";
+      return false;
+    }
   }
   return true;
 }
@@ -885,7 +933,7 @@
   AssignImageBinSlot(obj);
 }
 
-ObjectArray<Object>* ImageWriter::CreateImageRoots() const {
+ObjectArray<Object>* ImageWriter::CreateImageRoots(const char* oat_filename) const {
   Runtime* runtime = Runtime::Current();
   ClassLinker* class_linker = runtime->GetClassLinker();
   Thread* self = Thread::Current();
@@ -893,6 +941,15 @@
   Handle<Class> object_array_class(hs.NewHandle(
       class_linker->FindSystemClass(self, "[Ljava/lang/Object;")));
 
+  std::unordered_set<const DexFile*> image_dex_files;
+  for (auto& pair : dex_file_oat_filename_map_) {
+    const DexFile* image_dex_file = pair.first;
+    const char* image_oat_filename = pair.second;
+    if (strcmp(oat_filename, image_oat_filename) == 0) {
+      image_dex_files.insert(image_dex_file);
+    }
+  }
+
   // build an Object[] of all the DexCaches used in the source_space_.
   // Since we can't hold the dex lock when allocating the dex_caches
   // ObjectArray, we lock the dex lock twice, first to get the number
@@ -905,7 +962,10 @@
     for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) {
       mirror::DexCache* dex_cache =
           down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root));
-      dex_cache_count += IsInBootImage(dex_cache) ? 0u : 1u;
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      if (!IsInBootImage(dex_cache)) {
+        dex_cache_count += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u;
+      }
     }
   }
   Handle<ObjectArray<Object>> dex_caches(
@@ -918,7 +978,10 @@
     for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) {
       mirror::DexCache* dex_cache =
           down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root));
-      non_image_dex_caches += IsInBootImage(dex_cache) ? 0u : 1u;
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      if (!IsInBootImage(dex_cache)) {
+        non_image_dex_caches += image_dex_files.find(dex_file) != image_dex_files.end() ? 1u : 0u;
+      }
     }
     CHECK_EQ(dex_cache_count, non_image_dex_caches)
         << "The number of non-image dex caches changed.";
@@ -926,7 +989,8 @@
     for (const ClassLinker::DexCacheData& data : class_linker->GetDexCachesData()) {
       mirror::DexCache* dex_cache =
           down_cast<mirror::DexCache*>(self->DecodeJObject(data.weak_root));
-      if (!IsInBootImage(dex_cache)) {
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      if (!IsInBootImage(dex_cache) && image_dex_files.find(dex_file) != image_dex_files.end()) {
         dex_caches->Set<false>(i, dex_cache);
         ++i;
       }
@@ -997,9 +1061,12 @@
       }
       // Visit and assign offsets for fields and field arrays.
       auto* as_klass = h_obj->AsClass();
+      mirror::DexCache* dex_cache = as_klass->GetDexCache();
       LengthPrefixedArray<ArtField>* fields[] = {
           as_klass->GetSFieldsPtr(), as_klass->GetIFieldsPtr(),
       };
+      const char* oat_file = GetOatFilenameForDexCache(dex_cache);
+      ImageInfo& image_info = GetImageInfo(oat_file);
       for (LengthPrefixedArray<ArtField>* cur_fields : fields) {
         // Total array length including header.
         if (cur_fields != nullptr) {
@@ -1008,11 +1075,10 @@
           auto it = native_object_relocations_.find(cur_fields);
           CHECK(it == native_object_relocations_.end()) << "Field array " << cur_fields
                                                   << " already forwarded";
-          size_t& offset = bin_slot_sizes_[kBinArtField];
+          size_t& offset = image_info.bin_slot_sizes_[kBinArtField];
           DCHECK(!IsInBootImage(cur_fields));
-          native_object_relocations_.emplace(
-              cur_fields,
-              NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtFieldArray });
+          native_object_relocations_.emplace(cur_fields,
+              NativeObjectRelocation {oat_file, offset, kNativeObjectRelocationTypeArtFieldArray });
           offset += header_size;
           // Forward individual fields so that we can quickly find where they belong.
           for (size_t i = 0, count = cur_fields->size(); i < count; ++i) {
@@ -1022,9 +1088,8 @@
             CHECK(it2 == native_object_relocations_.end()) << "Field at index=" << i
                 << " already assigned " << PrettyField(field) << " static=" << field->IsStatic();
             DCHECK(!IsInBootImage(field));
-            native_object_relocations_.emplace(
-                field,
-                NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtField });
+            native_object_relocations_.emplace(field,
+                NativeObjectRelocation {oat_file, offset, kNativeObjectRelocationTypeArtField });
             offset += sizeof(ArtField);
           }
         }
@@ -1053,17 +1118,17 @@
         auto it = native_object_relocations_.find(array);
         CHECK(it == native_object_relocations_.end())
             << "Method array " << array << " already forwarded";
-        size_t& offset = bin_slot_sizes_[bin_type];
+        size_t& offset = image_info.bin_slot_sizes_[bin_type];
         DCHECK(!IsInBootImage(array));
-        native_object_relocations_.emplace(
-            array, NativeObjectRelocation {
-              offset,
-              any_dirty ? kNativeObjectRelocationTypeArtMethodArrayDirty
-                        : kNativeObjectRelocationTypeArtMethodArrayClean
-            });
+        native_object_relocations_.emplace(array,
+            NativeObjectRelocation {
+                oat_file,
+                offset,
+                any_dirty ? kNativeObjectRelocationTypeArtMethodArrayDirty
+                          : kNativeObjectRelocationTypeArtMethodArrayClean });
         offset += header_size;
         for (auto& m : as_klass->GetMethods(target_ptr_size_)) {
-          AssignMethodOffset(&m, type);
+          AssignMethodOffset(&m, type, oat_file);
         }
         (any_dirty ? dirty_methods_ : clean_methods_) += num_methods;
       }
@@ -1089,13 +1154,16 @@
   }
 }
 
-void ImageWriter::AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type) {
+void ImageWriter::AssignMethodOffset(ArtMethod* method,
+                                     NativeObjectRelocationType type,
+                                     const char* oat_filename) {
   DCHECK(!IsInBootImage(method));
   auto it = native_object_relocations_.find(method);
   CHECK(it == native_object_relocations_.end()) << "Method " << method << " already assigned "
       << PrettyMethod(method);
-  size_t& offset = bin_slot_sizes_[BinTypeForNativeRelocationType(type)];
-  native_object_relocations_.emplace(method, NativeObjectRelocation { offset, type });
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+  size_t& offset = image_info.bin_slot_sizes_[BinTypeForNativeRelocationType(type)];
+  native_object_relocations_.emplace(method, NativeObjectRelocation { oat_filename, offset, type });
   offset += ArtMethod::Size(target_ptr_size_);
 }
 
@@ -1128,18 +1196,20 @@
 
 void ImageWriter::CalculateNewObjectOffsets() {
   Thread* const self = Thread::Current();
-  StackHandleScope<1> hs(self);
-  Handle<ObjectArray<Object>> image_roots(hs.NewHandle(CreateImageRoots()));
+  StackHandleScopeCollection handles(self);
+  std::vector<Handle<ObjectArray<Object>>> image_roots;
+  for (const char* oat_filename : oat_filenames_) {
+    std::string image_filename = oat_filename;
+    image_roots.push_back(handles.NewHandle(CreateImageRoots(image_filename.c_str())));
+  }
 
   auto* runtime = Runtime::Current();
   auto* heap = runtime->GetHeap();
-  DCHECK_EQ(0U, image_end_);
 
   // Leave space for the header, but do not write it yet, we need to
   // know where image_roots is going to end up
-  image_end_ += RoundUp(sizeof(ImageHeader), kObjectAlignment);  // 64-bit-alignment
+  image_objects_offset_begin_ = RoundUp(sizeof(ImageHeader), kObjectAlignment);  // 64-bit-alignment
 
-  image_objects_offset_begin_ = image_end_;
   // Clear any pre-existing monitors which may have been in the monitor words, assign bin slots.
   heap->VisitObjects(WalkFieldsCallback, this);
   // Write the image runtime methods.
@@ -1156,10 +1226,12 @@
   const auto image_method_type = kNativeObjectRelocationTypeArtMethodArrayClean;
   auto it = native_object_relocations_.find(&image_method_array_);
   CHECK(it == native_object_relocations_.end());
-  size_t& offset = bin_slot_sizes_[BinTypeForNativeRelocationType(image_method_type)];
+  ImageInfo& default_image_info = GetImageInfo(default_oat_filename_);
+  size_t& offset =
+      default_image_info.bin_slot_sizes_[BinTypeForNativeRelocationType(image_method_type)];
   if (!compile_app_image_) {
     native_object_relocations_.emplace(&image_method_array_,
-                                       NativeObjectRelocation { offset, image_method_type });
+        NativeObjectRelocation { default_oat_filename_, offset, image_method_type });
   }
   size_t method_alignment = ArtMethod::Alignment(target_ptr_size_);
   const size_t array_size = LengthPrefixedArray<ArtMethod>::ComputeSize(
@@ -1171,43 +1243,74 @@
     CHECK(m->IsRuntimeMethod());
     DCHECK_EQ(compile_app_image_, IsInBootImage(m)) << "Trampolines should be in boot image";
     if (!IsInBootImage(m)) {
-      AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean);
+      AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean, default_oat_filename_);
     }
   }
   // Calculate size of the dex cache arrays slot and prepare offsets.
   PrepareDexCacheArraySlots();
 
   // Calculate bin slot offsets.
-  size_t bin_offset = image_objects_offset_begin_;
-  for (size_t i = 0; i != kBinSize; ++i) {
-    bin_slot_offsets_[i] = bin_offset;
-    bin_offset += bin_slot_sizes_[i];
-    if (i == kBinArtField) {
-      static_assert(kBinArtField + 1 == kBinArtMethodClean, "Methods follow fields.");
-      static_assert(alignof(ArtField) == 4u, "ArtField alignment is 4.");
-      DCHECK_ALIGNED(bin_offset, 4u);
-      DCHECK(method_alignment == 4u || method_alignment == 8u);
-      bin_offset = RoundUp(bin_offset, method_alignment);
+  for (const char* oat_filename : oat_filenames_) {
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    size_t bin_offset = image_objects_offset_begin_;
+    for (size_t i = 0; i != kBinSize; ++i) {
+      image_info.bin_slot_offsets_[i] = bin_offset;
+      bin_offset += image_info.bin_slot_sizes_[i];
+      if (i == kBinArtField) {
+        static_assert(kBinArtField + 1 == kBinArtMethodClean, "Methods follow fields.");
+        static_assert(alignof(ArtField) == 4u, "ArtField alignment is 4.");
+        DCHECK_ALIGNED(bin_offset, 4u);
+        DCHECK(method_alignment == 4u || method_alignment == 8u);
+        bin_offset = RoundUp(bin_offset, method_alignment);
+      }
     }
+    // NOTE: There may be additional padding between the bin slots and the intern table.
+    DCHECK_EQ(image_info.image_end_,
+              GetBinSizeSum(image_info, kBinMirrorCount) + image_objects_offset_begin_);
   }
-  // NOTE: There may be additional padding between the bin slots and the intern table.
 
-  DCHECK_EQ(image_end_, GetBinSizeSum(kBinMirrorCount) + image_objects_offset_begin_);
+  // Calculate image offsets.
+  size_t image_offset = 0;
+  for (const char* oat_filename : oat_filenames_) {
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    image_info.image_begin_ = global_image_begin_ + image_offset;
+    image_info.image_offset_ = image_offset;
+    size_t native_sections_size = image_info.bin_slot_sizes_[kBinArtField] +
+                                  image_info.bin_slot_sizes_[kBinArtMethodDirty] +
+                                  image_info.bin_slot_sizes_[kBinArtMethodClean] +
+                                  image_info.bin_slot_sizes_[kBinDexCacheArray] +
+                                  intern_table_bytes_ +
+                                  class_table_bytes_;
+    size_t image_objects = RoundUp(image_info.image_end_, kPageSize);
+    size_t bitmap_size =
+        RoundUp(gc::accounting::ContinuousSpaceBitmap::ComputeBitmapSize(image_objects), kPageSize);
+    size_t heap_size = gc::accounting::ContinuousSpaceBitmap::ComputeHeapSize(bitmap_size);
+    size_t max = std::max(heap_size, image_info.image_end_ + native_sections_size + bitmap_size);
+    image_info.image_size_ = RoundUp(max, kPageSize);
+    image_offset += image_info.image_size_;
+  }
 
   // Transform each object's bin slot into an offset which will be used to do the final copy.
   heap->VisitObjects(UnbinObjectsIntoOffsetCallback, this);
 
-  DCHECK_EQ(image_end_, GetBinSizeSum(kBinMirrorCount) + image_objects_offset_begin_);
+  // DCHECK_EQ(image_end_, GetBinSizeSum(kBinMirrorCount) + image_objects_offset_begin_);
 
-  image_roots_address_ = PointerToLowMemUInt32(GetImageAddress(image_roots.Get()));
+  size_t i = 0;
+  for (const char* oat_filename : oat_filenames_) {
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    image_info.image_roots_address_ = PointerToLowMemUInt32(GetImageAddress(image_roots[i].Get()));
+    i++;
+  }
 
   // Update the native relocations by adding their bin sums.
   for (auto& pair : native_object_relocations_) {
     NativeObjectRelocation& relocation = pair.second;
     Bin bin_type = BinTypeForNativeRelocationType(relocation.type);
-    relocation.offset += bin_slot_offsets_[bin_type];
+    ImageInfo& image_info = GetImageInfo(relocation.oat_filename);
+    relocation.offset += image_info.bin_slot_offsets_[bin_type];
   }
 
+  /* TODO: Reenable the intern table and class table. b/26317072
   // Calculate how big the intern table will be after being serialized.
   InternTable* const intern_table = runtime->GetInternTable();
   CHECK_EQ(intern_table->WeakSize(), 0u) << " should have strong interned all the strings";
@@ -1231,41 +1334,45 @@
       class_table_bytes_ += table->WriteToMemory(nullptr);
     }
   }
+  */
 
-  // Note that image_end_ is left at end of used mirror object section.
+  // Note that image_info.image_end_ is left at end of used mirror object section.
 }
 
 void ImageWriter::CreateHeader(size_t oat_loaded_size, size_t oat_data_offset) {
   CHECK_NE(0U, oat_loaded_size);
-  const uint8_t* oat_file_begin = GetOatFileBegin();
+  const char* oat_filename = oat_file_->GetLocation().c_str();
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+  const uint8_t* oat_file_begin = GetOatFileBegin(oat_filename);
   const uint8_t* oat_file_end = oat_file_begin + oat_loaded_size;
-  oat_data_begin_ = oat_file_begin + oat_data_offset;
-  const uint8_t* oat_data_end = oat_data_begin_ + oat_file_->Size();
+  image_info.oat_data_begin_ = const_cast<uint8_t*>(oat_file_begin) + oat_data_offset;
+  const uint8_t* oat_data_end = image_info.oat_data_begin_ + oat_file_->Size();
+  image_info.oat_size_ = oat_file_->Size();
 
   // Create the image sections.
   ImageSection sections[ImageHeader::kSectionCount];
   // Objects section
   auto* objects_section = &sections[ImageHeader::kSectionObjects];
-  *objects_section = ImageSection(0u, image_end_);
+  *objects_section = ImageSection(0u, image_info.image_end_);
   size_t cur_pos = objects_section->End();
   // Add field section.
   auto* field_section = &sections[ImageHeader::kSectionArtFields];
-  *field_section = ImageSection(cur_pos, bin_slot_sizes_[kBinArtField]);
-  CHECK_EQ(bin_slot_offsets_[kBinArtField], field_section->Offset());
+  *field_section = ImageSection(cur_pos, image_info.bin_slot_sizes_[kBinArtField]);
+  CHECK_EQ(image_info.bin_slot_offsets_[kBinArtField], field_section->Offset());
   cur_pos = field_section->End();
   // Round up to the alignment the required by the method section.
   cur_pos = RoundUp(cur_pos, ArtMethod::Alignment(target_ptr_size_));
   // Add method section.
   auto* methods_section = &sections[ImageHeader::kSectionArtMethods];
   *methods_section = ImageSection(cur_pos,
-                                  bin_slot_sizes_[kBinArtMethodClean] +
-                                      bin_slot_sizes_[kBinArtMethodDirty]);
-  CHECK_EQ(bin_slot_offsets_[kBinArtMethodClean], methods_section->Offset());
+                                  image_info.bin_slot_sizes_[kBinArtMethodClean] +
+                                      image_info.bin_slot_sizes_[kBinArtMethodDirty]);
+  CHECK_EQ(image_info.bin_slot_offsets_[kBinArtMethodClean], methods_section->Offset());
   cur_pos = methods_section->End();
   // Add dex cache arrays section.
   auto* dex_cache_arrays_section = &sections[ImageHeader::kSectionDexCacheArrays];
-  *dex_cache_arrays_section = ImageSection(cur_pos, bin_slot_sizes_[kBinDexCacheArray]);
-  CHECK_EQ(bin_slot_offsets_[kBinDexCacheArray], dex_cache_arrays_section->Offset());
+  *dex_cache_arrays_section = ImageSection(cur_pos, image_info.bin_slot_sizes_[kBinDexCacheArray]);
+  CHECK_EQ(image_info.bin_slot_offsets_[kBinDexCacheArray], dex_cache_arrays_section->Offset());
   cur_pos = dex_cache_arrays_section->End();
   // Round up to the alignment the string table expects. See HashSet::WriteToMemory.
   cur_pos = RoundUp(cur_pos, sizeof(uint64_t));
@@ -1282,42 +1389,51 @@
   // Image end goes right before the start of the image bitmap.
   const size_t image_end = static_cast<uint32_t>(cur_pos);
   // Finally bitmap section.
-  const size_t bitmap_bytes = image_bitmap_->Size();
+  const size_t bitmap_bytes = image_info.image_bitmap_->Size();
   auto* bitmap_section = &sections[ImageHeader::kSectionImageBitmap];
   *bitmap_section = ImageSection(RoundUp(cur_pos, kPageSize), RoundUp(bitmap_bytes, kPageSize));
   cur_pos = bitmap_section->End();
-  if (kIsDebugBuild) {
+  if (VLOG_IS_ON(compiler)) {
+    LOG(INFO) << "Creating header for " << oat_filename;
     size_t idx = 0;
     for (const ImageSection& section : sections) {
       LOG(INFO) << static_cast<ImageHeader::ImageSections>(idx) << " " << section;
       ++idx;
     }
     LOG(INFO) << "Methods: clean=" << clean_methods_ << " dirty=" << dirty_methods_;
+    LOG(INFO) << "Image roots address=" << std::hex << image_info.image_roots_address_ << std::dec;
+    LOG(INFO) << "Image begin=" << std::hex << reinterpret_cast<uintptr_t>(global_image_begin_)
+              << " Image offset=" << image_info.image_offset_ << std::dec;
+    LOG(INFO) << "Oat file begin=" << std::hex << reinterpret_cast<uintptr_t>(oat_file_begin)
+              << " Oat data begin=" << reinterpret_cast<uintptr_t>(image_info.oat_data_begin_)
+              << " Oat data end=" << reinterpret_cast<uintptr_t>(oat_data_end)
+              << " Oat file end=" << reinterpret_cast<uintptr_t>(oat_file_end);
   }
-  CHECK_EQ(AlignUp(image_begin_ + image_end, kPageSize), oat_file_begin) <<
-      "Oat file should be right after the image.";
+
   // Create the header, leave 0 for data size since we will fill this in as we are writing the
   // image.
-  new (image_->Begin()) ImageHeader(PointerToLowMemUInt32(image_begin_),
-                                                          image_end,
-                                                          sections,
-                                                          image_roots_address_,
-                                                          oat_file_->GetOatHeader().GetChecksum(),
-                                                          PointerToLowMemUInt32(oat_file_begin),
-                                                          PointerToLowMemUInt32(oat_data_begin_),
-                                                          PointerToLowMemUInt32(oat_data_end),
-                                                          PointerToLowMemUInt32(oat_file_end),
-                                                          target_ptr_size_,
-                                                          compile_pic_,
-                                                          image_storage_mode_,
-                                                          /*data_size*/0u);
+  new (image_info.image_->Begin()) ImageHeader(PointerToLowMemUInt32(image_info.image_begin_),
+                                               image_end,
+                                               sections,
+                                               image_info.image_roots_address_,
+                                               oat_file_->GetOatHeader().GetChecksum(),
+                                               PointerToLowMemUInt32(oat_file_begin),
+                                               PointerToLowMemUInt32(image_info.oat_data_begin_),
+                                               PointerToLowMemUInt32(oat_data_end),
+                                               PointerToLowMemUInt32(oat_file_end),
+                                               target_ptr_size_,
+                                               compile_pic_,
+                                               image_storage_mode_,
+                                               /*data_size*/0u);
 }
 
 ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) {
   auto it = native_object_relocations_.find(method);
   CHECK(it != native_object_relocations_.end()) << PrettyMethod(method) << " @ " << method;
-  CHECK_GE(it->second.offset, image_end_) << "ArtMethods should be after Objects";
-  return reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset);
+  const char* oat_filename = GetOatFilename(method->GetDexCache());
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+  CHECK_GE(it->second.offset, image_info.image_end_) << "ArtMethods should be after Objects";
+  return reinterpret_cast<ArtMethod*>(image_info.image_begin_ + it->second.offset);
 }
 
 class FixupRootVisitor : public RootVisitor {
@@ -1345,18 +1461,24 @@
 
   mirror::Object* ImageAddress(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) {
     const size_t offset = image_writer_->GetImageOffset(obj);
-    auto* const dest = reinterpret_cast<Object*>(image_writer_->image_begin_ + offset);
+    auto* const dest = reinterpret_cast<Object*>(image_writer_->global_image_begin_ + offset);
     VLOG(compiler) << "Update root from " << obj << " to " << dest;
     return dest;
   }
 };
 
 void ImageWriter::CopyAndFixupNativeData() {
+  const char* oat_filename = oat_file_->GetLocation().c_str();
+  ImageInfo& image_info = GetImageInfo(oat_filename);
   // Copy ArtFields and methods to their locations and update the array for convenience.
   for (auto& pair : native_object_relocations_) {
     NativeObjectRelocation& relocation = pair.second;
-    auto* dest = image_->Begin() + relocation.offset;
-    DCHECK_GE(dest, image_->Begin() + image_end_);
+    // Only work with fields and methods that are in the current oat file.
+    if (strcmp(relocation.oat_filename, oat_filename) != 0) {
+      continue;
+    }
+    auto* dest = image_info.image_->Begin() + relocation.offset;
+    DCHECK_GE(dest, image_info.image_->Begin() + image_info.image_end_);
     DCHECK(!IsInBootImage(pair.first));
     switch (relocation.type) {
       case kNativeObjectRelocationTypeArtField: {
@@ -1368,7 +1490,8 @@
       case kNativeObjectRelocationTypeArtMethodClean:
       case kNativeObjectRelocationTypeArtMethodDirty: {
         CopyAndFixupMethod(reinterpret_cast<ArtMethod*>(pair.first),
-                           reinterpret_cast<ArtMethod*>(dest));
+                           reinterpret_cast<ArtMethod*>(dest),
+                           image_info);
         break;
       }
       // For arrays, copy just the header since the elements will get copied by their corresponding
@@ -1391,30 +1514,36 @@
     }
   }
   // Fixup the image method roots.
-  auto* image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
+  auto* image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin());
   const ImageSection& methods_section = image_header->GetMethodsSection();
   for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) {
     ArtMethod* method = image_methods_[i];
     CHECK(method != nullptr);
+    // Only place runtime methods in the image of the default oat file.
+    if (method->IsRuntimeMethod() && strcmp(default_oat_filename_, oat_filename) != 0) {
+      continue;
+    }
     if (!IsInBootImage(method)) {
       auto it = native_object_relocations_.find(method);
-      CHECK(it != native_object_relocations_.end()) << "No fowarding for " << PrettyMethod(method);
+      CHECK(it != native_object_relocations_.end()) << "No forwarding for " << PrettyMethod(method);
       NativeObjectRelocation& relocation = it->second;
       CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in "
           << methods_section;
       CHECK(relocation.IsArtMethodRelocation()) << relocation.type;
-      method = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset);
+      method = reinterpret_cast<ArtMethod*>(global_image_begin_ + it->second.offset);
     }
     image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), method);
   }
   FixupRootVisitor root_visitor(this);
 
+  /* TODO: Reenable the intern table and class table
   // Write the intern table into the image.
   const ImageSection& intern_table_section = image_header->GetImageSection(
       ImageHeader::kSectionInternedStrings);
   Runtime* const runtime = Runtime::Current();
   InternTable* const intern_table = runtime->GetInternTable();
-  uint8_t* const intern_table_memory_ptr = image_->Begin() + intern_table_section.Offset();
+  uint8_t* const intern_table_memory_ptr =
+      image_info.image_->Begin() + intern_table_section.Offset();
   const size_t intern_table_bytes = intern_table->WriteToMemory(intern_table_memory_ptr);
   CHECK_EQ(intern_table_bytes, intern_table_bytes_);
   // Fixup the pointers in the newly written intern table to contain image addresses.
@@ -1430,10 +1559,11 @@
   // Write the class table(s) into the image. class_table_bytes_ may be 0 if there are multiple
   // class loaders. Writing multiple class tables into the image is currently unsupported.
   if (class_table_bytes_ > 0u) {
-    ClassLinker* const class_linker = runtime->GetClassLinker();
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
     const ImageSection& class_table_section = image_header->GetImageSection(
         ImageHeader::kSectionClassTable);
-    uint8_t* const class_table_memory_ptr = image_->Begin() + class_table_section.Offset();
+    uint8_t* const class_table_memory_ptr =
+        image_info.image_->Begin() + class_table_section.Offset();
     ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
     size_t class_table_bytes = 0;
     for (mirror::ClassLoader* loader : class_loaders_) {
@@ -1453,6 +1583,7 @@
     }
     CHECK_EQ(class_table_bytes, class_table_bytes_);
   }
+  */
 }
 
 void ImageWriter::CopyAndFixupObjects() {
@@ -1500,7 +1631,8 @@
         }
         UNREACHABLE();
       } else {
-        elem = image_begin_ + it->second.offset;
+        ImageInfo& image_info = GetImageInfo(it->second.oat_filename);
+        elem = image_info.image_begin_ + it->second.offset;
       }
     }
     dest_array->SetElementPtrSize<false, true>(i, elem, target_ptr_size_);
@@ -1512,14 +1644,16 @@
     return;
   }
   size_t offset = GetImageOffset(obj);
-  auto* dst = reinterpret_cast<Object*>(image_->Begin() + offset);
-  DCHECK_LT(offset, image_end_);
+  const char* oat_filename = GetOatFilename(obj);
+  ImageInfo& image_info = GetImageInfo(oat_filename);
+  auto* dst = reinterpret_cast<Object*>(image_info.image_->Begin() + offset);
+  DCHECK_LT(offset, image_info.image_end_);
   const auto* src = reinterpret_cast<const uint8_t*>(obj);
 
-  image_bitmap_->Set(dst);  // Mark the obj as live.
+  image_info.image_bitmap_->Set(dst);  // Mark the obj as live.
 
   const size_t n = obj->SizeOf();
-  DCHECK_LE(offset + n, image_->Size());
+  DCHECK_LE(offset + n, image_info.image_->Size());
   memcpy(dst, src, n);
 
   // Write in a hash code of objects which have inflated monitors or a hash code in their monitor
@@ -1595,34 +1729,55 @@
 }
 
 template <typename T>
-T* ImageWriter::NativeLocationInImage(T* obj) {
-  return (obj == nullptr || IsInBootImage(obj))
-      ? obj
-      : reinterpret_cast<T*>(image_begin_ + NativeOffsetInImage(obj));
+T* ImageWriter::NativeLocationInImage(T* obj, const char* oat_filename) {
+  if (obj == nullptr || IsInBootImage(obj)) {
+    return obj;
+  } else {
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    return reinterpret_cast<T*>(image_info.image_begin_ + NativeOffsetInImage(obj));
+  }
 }
 
 template <typename T>
-T* ImageWriter::NativeCopyLocation(T* obj) {
-  return (obj == nullptr || IsInBootImage(obj))
-      ? obj
-      : reinterpret_cast<T*>(image_->Begin() + NativeOffsetInImage(obj));
+T* ImageWriter::NativeCopyLocation(T* obj, mirror::DexCache* dex_cache) {
+  if (obj == nullptr || IsInBootImage(obj)) {
+    return obj;
+  } else {
+    const char* oat_filename = GetOatFilenameForDexCache(dex_cache);
+    ImageInfo& image_info = GetImageInfo(oat_filename);
+    return reinterpret_cast<T*>(image_info.image_->Begin() + NativeOffsetInImage(obj));
+  }
 }
 
 class NativeLocationVisitor {
  public:
-  explicit NativeLocationVisitor(ImageWriter* image_writer) : image_writer_(image_writer) {}
+  explicit NativeLocationVisitor(ImageWriter* image_writer, const char* oat_filename)
+      : image_writer_(image_writer), oat_filename_(oat_filename) {}
 
   template <typename T>
-  T* operator()(T* ptr) const {
-    return image_writer_->NativeLocationInImage(ptr);
+  T* operator()(T* ptr) const SHARED_REQUIRES(Locks::mutator_lock_) {
+    return image_writer_->NativeLocationInImage(ptr, oat_filename_);
+  }
+
+  ArtMethod* operator()(ArtMethod* method) const SHARED_REQUIRES(Locks::mutator_lock_) {
+    const char* oat_filename = method->IsRuntimeMethod() ? image_writer_->GetDefaultOatFilename() :
+        image_writer_->GetOatFilenameForDexCache(method->GetDexCache());
+    return image_writer_->NativeLocationInImage(method, oat_filename);
+  }
+
+  ArtField* operator()(ArtField* field) const SHARED_REQUIRES(Locks::mutator_lock_) {
+    const char* oat_filename = image_writer_->GetOatFilenameForDexCache(field->GetDexCache());
+    return image_writer_->NativeLocationInImage(field, oat_filename);
   }
 
  private:
   ImageWriter* const image_writer_;
+  const char* oat_filename_;
 };
 
 void ImageWriter::FixupClass(mirror::Class* orig, mirror::Class* copy) {
-  orig->FixupNativePointers(copy, target_ptr_size_, NativeLocationVisitor(this));
+  const char* oat_filename = GetOatFilename(orig);
+  orig->FixupNativePointers(copy, target_ptr_size_, NativeLocationVisitor(this, oat_filename));
   FixupClassVisitor visitor(this, copy);
   static_cast<mirror::Object*>(orig)->VisitReferences(visitor, visitor);
 }
@@ -1661,7 +1816,7 @@
       CHECK(it != native_object_relocations_.end())
           << "Missing relocation for AbstractMethod.artMethod " << PrettyMethod(src_method);
       dest->SetArtMethod(
-          reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset));
+          reinterpret_cast<ArtMethod*>(global_image_begin_ + it->second.offset));
     } else if (!klass->IsArrayClass()) {
       ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
       if (klass == class_linker->GetClassRoot(ClassLinker::kJavaLangDexCache)) {
@@ -1702,41 +1857,52 @@
   // 64-bit values here, clearing the top 32 bits for 32-bit targets. The zero-extension is
   // done by casting to the unsigned type uintptr_t before casting to int64_t, i.e.
   //     static_cast<int64_t>(reinterpret_cast<uintptr_t>(image_begin_ + offset))).
+  const char* oat_filename = GetOatFilenameForDexCache(orig_dex_cache);
   GcRoot<mirror::String>* orig_strings = orig_dex_cache->GetStrings();
   if (orig_strings != nullptr) {
     copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::StringsOffset(),
-                                               NativeLocationInImage(orig_strings),
+                                               NativeLocationInImage(orig_strings, oat_filename),
                                                /*pointer size*/8u);
-    orig_dex_cache->FixupStrings(NativeCopyLocation(orig_strings), ImageAddressVisitor(this));
+    orig_dex_cache->FixupStrings(NativeCopyLocation(orig_strings, orig_dex_cache),
+                                 ImageAddressVisitor(this));
   }
   GcRoot<mirror::Class>* orig_types = orig_dex_cache->GetResolvedTypes();
   if (orig_types != nullptr) {
     copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedTypesOffset(),
-                                               NativeLocationInImage(orig_types),
+                                               NativeLocationInImage(orig_types, oat_filename),
                                                /*pointer size*/8u);
-    orig_dex_cache->FixupResolvedTypes(NativeCopyLocation(orig_types), ImageAddressVisitor(this));
+    orig_dex_cache->FixupResolvedTypes(NativeCopyLocation(orig_types, orig_dex_cache),
+                                       ImageAddressVisitor(this));
   }
   ArtMethod** orig_methods = orig_dex_cache->GetResolvedMethods();
   if (orig_methods != nullptr) {
     copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedMethodsOffset(),
-                                               NativeLocationInImage(orig_methods),
+                                               NativeLocationInImage(orig_methods, oat_filename),
                                                /*pointer size*/8u);
-    ArtMethod** copy_methods = NativeCopyLocation(orig_methods);
+    ArtMethod** copy_methods = NativeCopyLocation(orig_methods, orig_dex_cache);
     for (size_t i = 0, num = orig_dex_cache->NumResolvedMethods(); i != num; ++i) {
       ArtMethod* orig = mirror::DexCache::GetElementPtrSize(orig_methods, i, target_ptr_size_);
-      ArtMethod* copy = NativeLocationInImage(orig);
+      const char* method_oat_filename;
+      if (orig == nullptr || orig->IsRuntimeMethod()) {
+        method_oat_filename = default_oat_filename_;
+      } else {
+        method_oat_filename = GetOatFilenameForDexCache(orig->GetDexCache());
+      }
+      ArtMethod* copy = NativeLocationInImage(orig, method_oat_filename);
       mirror::DexCache::SetElementPtrSize(copy_methods, i, copy, target_ptr_size_);
     }
   }
   ArtField** orig_fields = orig_dex_cache->GetResolvedFields();
   if (orig_fields != nullptr) {
     copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedFieldsOffset(),
-                                               NativeLocationInImage(orig_fields),
+                                               NativeLocationInImage(orig_fields, oat_filename),
                                                /*pointer size*/8u);
-    ArtField** copy_fields = NativeCopyLocation(orig_fields);
+    ArtField** copy_fields = NativeCopyLocation(orig_fields, orig_dex_cache);
     for (size_t i = 0, num = orig_dex_cache->NumResolvedFields(); i != num; ++i) {
       ArtField* orig = mirror::DexCache::GetElementPtrSize(orig_fields, i, target_ptr_size_);
-      ArtField* copy = NativeLocationInImage(orig);
+      const char* field_oat_filename =
+          orig == nullptr ? default_oat_filename_ : GetOatFilenameForDexCache(orig->GetDexCache());
+      ArtField* copy = NativeLocationInImage(orig, field_oat_filename);
       mirror::DexCache::SetElementPtrSize(copy_fields, i, copy, target_ptr_size_);
     }
   }
@@ -1747,9 +1913,10 @@
   // If we are compiling an app image, we need to use the stubs of the boot image.
   if (compile_app_image_) {
     // Use the current image pointers.
-    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-    DCHECK(image_space != nullptr);
-    const OatFile* oat_file = image_space->GetOatFile();
+    std::vector<gc::space::ImageSpace*> image_spaces =
+        Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    DCHECK(!image_spaces.empty());
+    const OatFile* oat_file = image_spaces[0]->GetOatFile();
     CHECK(oat_file != nullptr);
     const OatHeader& header = oat_file->GetOatHeader();
     switch (type) {
@@ -1772,10 +1939,13 @@
         UNREACHABLE();
     }
   }
-  return GetOatAddressForOffset(oat_address_offsets_[type]);
+  const ImageInfo& primary_image_info = GetImageInfo(0);
+  return GetOatAddressForOffset(primary_image_info.oat_address_offsets_[type], primary_image_info);
 }
 
-const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_interpreted) {
+const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method,
+                                         const ImageInfo& image_info,
+                                         bool* quick_is_interpreted) {
   DCHECK(!method->IsResolutionMethod()) << PrettyMethod(method);
   DCHECK(!method->IsImtConflictMethod()) << PrettyMethod(method);
   DCHECK(!method->IsImtUnimplementedMethod()) << PrettyMethod(method);
@@ -1788,7 +1958,7 @@
   // Quick entrypoint:
   uint32_t quick_oat_code_offset = PointerToLowMemUInt32(
       method->GetEntryPointFromQuickCompiledCodePtrSize(target_ptr_size_));
-  const uint8_t* quick_code = GetOatAddressForOffset(quick_oat_code_offset);
+  const uint8_t* quick_code = GetOatAddressForOffset(quick_oat_code_offset, image_info);
   *quick_is_interpreted = false;
   if (quick_code != nullptr && (!method->IsStatic() || method->IsConstructor() ||
       method->GetDeclaringClass()->IsInitialized())) {
@@ -1808,42 +1978,32 @@
     quick_code = GetOatAddress(kOatAddressQuickResolutionTrampoline);
   }
   if (!IsInBootOatFile(quick_code)) {
-    DCHECK_GE(quick_code, oat_data_begin_);
+    // DCHECK_GE(quick_code, oat_data_begin_);
   }
   return quick_code;
 }
 
-const uint8_t* ImageWriter::GetQuickEntryPoint(ArtMethod* method) {
-  // Calculate the quick entry point following the same logic as FixupMethod() below.
-  // The resolution method has a special trampoline to call.
-  Runtime* runtime = Runtime::Current();
-  if (UNLIKELY(method == runtime->GetResolutionMethod())) {
-    return GetOatAddress(kOatAddressQuickResolutionTrampoline);
-  } else if (UNLIKELY(method == runtime->GetImtConflictMethod() ||
-                      method == runtime->GetImtUnimplementedMethod())) {
-    return GetOatAddress(kOatAddressQuickIMTConflictTrampoline);
-  } else {
-    // We assume all methods have code. If they don't currently then we set them to the use the
-    // resolution trampoline. Abstract methods never have code and so we need to make sure their
-    // use results in an AbstractMethodError. We use the interpreter to achieve this.
-    if (UNLIKELY(!method->IsInvokable())) {
-      return GetOatAddress(kOatAddressQuickToInterpreterBridge);
-    } else {
-      bool quick_is_interpreted;
-      return GetQuickCode(method, &quick_is_interpreted);
-    }
-  }
-}
-
-void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) {
+void ImageWriter::CopyAndFixupMethod(ArtMethod* orig,
+                                     ArtMethod* copy,
+                                     const ImageInfo& image_info) {
   memcpy(copy, orig, ArtMethod::Size(target_ptr_size_));
 
   copy->SetDeclaringClass(GetImageAddress(orig->GetDeclaringClassUnchecked()));
 
+  const char* oat_filename;
+  if (orig->IsRuntimeMethod()) {
+    oat_filename = default_oat_filename_;
+  } else {
+    auto it = dex_file_oat_filename_map_.find(orig->GetDexFile());
+    DCHECK(it != dex_file_oat_filename_map_.end()) << orig->GetDexFile()->GetLocation();
+    oat_filename = it->second;
+  }
   ArtMethod** orig_resolved_methods = orig->GetDexCacheResolvedMethods(target_ptr_size_);
-  copy->SetDexCacheResolvedMethods(NativeLocationInImage(orig_resolved_methods), target_ptr_size_);
+  copy->SetDexCacheResolvedMethods(NativeLocationInImage(orig_resolved_methods, oat_filename),
+                                   target_ptr_size_);
   GcRoot<mirror::Class>* orig_resolved_types = orig->GetDexCacheResolvedTypes(target_ptr_size_);
-  copy->SetDexCacheResolvedTypes(NativeLocationInImage(orig_resolved_types), target_ptr_size_);
+  copy->SetDexCacheResolvedTypes(NativeLocationInImage(orig_resolved_types, oat_filename),
+                                 target_ptr_size_);
 
   // OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to
   // oat_begin_
@@ -1877,7 +2037,7 @@
           GetOatAddress(kOatAddressQuickToInterpreterBridge), target_ptr_size_);
     } else {
       bool quick_is_interpreted;
-      const uint8_t* quick_code = GetQuickCode(orig, &quick_is_interpreted);
+      const uint8_t* quick_code = GetQuickCode(orig, image_info, &quick_is_interpreted);
       copy->SetEntryPointFromQuickCompiledCodePtrSize(quick_code, target_ptr_size_);
 
       // JNI entrypoint:
@@ -1914,13 +2074,16 @@
   CHECK(oat_header != nullptr);
   CHECK(oat_header->IsValid());
 
-  ImageHeader* image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
+  ImageInfo& image_info = GetImageInfo(oat_file_->GetLocation().c_str());
+  ImageHeader* image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin());
   image_header->SetOatChecksum(oat_header->GetChecksum());
 }
 
-size_t ImageWriter::GetBinSizeSum(ImageWriter::Bin up_to) const {
+size_t ImageWriter::GetBinSizeSum(ImageWriter::ImageInfo& image_info, ImageWriter::Bin up_to) const {
   DCHECK_LE(up_to, kBinSize);
-  return std::accumulate(&bin_slot_sizes_[0], &bin_slot_sizes_[up_to], /*init*/0);
+  return std::accumulate(&image_info.bin_slot_sizes_[0],
+                         &image_info.bin_slot_sizes_[up_to],
+                         /*init*/0);
 }
 
 ImageWriter::BinSlot::BinSlot(uint32_t lockword) : lockword_(lockword) {
@@ -1946,15 +2109,18 @@
   return lockword_ & ~kBinMask;
 }
 
-uint8_t* ImageWriter::GetOatFileBegin() const {
-  DCHECK_GT(intern_table_bytes_, 0u);
-  size_t native_sections_size = bin_slot_sizes_[kBinArtField] +
-                                bin_slot_sizes_[kBinArtMethodDirty] +
-                                bin_slot_sizes_[kBinArtMethodClean] +
-                                bin_slot_sizes_[kBinDexCacheArray] +
-                                intern_table_bytes_ +
-                                class_table_bytes_;
-  return image_begin_ + RoundUp(image_end_ + native_sections_size, kPageSize);
+uint8_t* ImageWriter::GetOatFileBegin(const char* oat_filename) const {
+  // DCHECK_GT(intern_table_bytes_, 0u);  TODO: Reenable intern table and class table.
+  uintptr_t last_image_end = 0;
+  for (const char* oat_fn : oat_filenames_) {
+    const ImageInfo& image_info = GetConstImageInfo(oat_fn);
+    DCHECK(image_info.image_begin_ != nullptr);
+    uintptr_t this_end = reinterpret_cast<uintptr_t>(image_info.image_begin_) +
+        image_info.image_size_;
+    last_image_end = std::max(this_end, last_image_end);
+  }
+  const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+  return reinterpret_cast<uint8_t*>(last_image_end) + image_info.oat_offset_;
 }
 
 ImageWriter::Bin ImageWriter::BinTypeForNativeRelocationType(NativeObjectRelocationType type) {
@@ -1974,4 +2140,61 @@
   UNREACHABLE();
 }
 
+const char* ImageWriter::GetOatFilename(mirror::Object* obj) const {
+  if (compile_app_image_) {
+    return default_oat_filename_;
+  } else {
+    return GetOatFilenameForDexCache(obj->IsDexCache() ? obj->AsDexCache() :
+        obj->IsClass() ? obj->AsClass()->GetDexCache() : obj->GetClass()->GetDexCache());
+  }
+}
+
+const char* ImageWriter::GetOatFilenameForDexCache(mirror::DexCache* dex_cache) const {
+  if (compile_app_image_ || dex_cache == nullptr) {
+    return default_oat_filename_;
+  } else {
+    auto it = dex_file_oat_filename_map_.find(dex_cache->GetDexFile());
+    DCHECK(it != dex_file_oat_filename_map_.end()) << dex_cache->GetDexFile()->GetLocation();
+    return it->second;
+  }
+}
+
+ImageWriter::ImageInfo& ImageWriter::GetImageInfo(const char* oat_filename) {
+  auto it = image_info_map_.find(oat_filename);
+  DCHECK(it != image_info_map_.end());
+  return it->second;
+}
+
+const ImageWriter::ImageInfo& ImageWriter::GetConstImageInfo(const char* oat_filename) const {
+  auto it = image_info_map_.find(oat_filename);
+  DCHECK(it != image_info_map_.end());
+  return it->second;
+}
+
+const ImageWriter::ImageInfo& ImageWriter::GetImageInfo(size_t index) const {
+  DCHECK_LT(index, oat_filenames_.size());
+  return GetConstImageInfo(oat_filenames_[index]);
+}
+
+void ImageWriter::UpdateOatFile(const char* oat_filename) {
+  std::unique_ptr<File> oat_file(OS::OpenFileForReading(oat_filename));
+  DCHECK(oat_file != nullptr);
+  size_t oat_loaded_size = 0;
+  size_t oat_data_offset = 0;
+  ElfWriter::GetOatElfInformation(oat_file.get(), &oat_loaded_size, &oat_data_offset);
+
+  ImageInfo& cur_image_info = GetImageInfo(oat_filename);
+
+  // Update the oat_offset of the next image info.
+  auto it = std::find(oat_filenames_.begin(), oat_filenames_.end(), oat_filename);
+  DCHECK(it != oat_filenames_.end());
+
+  it++;
+  if (it != oat_filenames_.end()) {
+    // There is a following one.
+    ImageInfo& next_image_info = GetImageInfo(*it);
+    next_image_info.oat_offset_ = cur_image_info.oat_offset_ + oat_loaded_size;
+  }
+}
+
 }  // namespace art
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index f1b2965..78297ae 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -56,30 +56,31 @@
               uintptr_t image_begin,
               bool compile_pic,
               bool compile_app_image,
-              ImageHeader::StorageMode image_storage_mode)
+              ImageHeader::StorageMode image_storage_mode,
+              const std::vector<const char*> oat_filenames,
+              const std::unordered_map<const DexFile*, const char*>& dex_file_oat_filename_map)
       : compiler_driver_(compiler_driver),
-        image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
-        image_end_(0),
+        global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
         image_objects_offset_begin_(0),
-        image_roots_address_(0),
         oat_file_(nullptr),
-        oat_data_begin_(nullptr),
         compile_pic_(compile_pic),
         compile_app_image_(compile_app_image),
         boot_image_space_(nullptr),
         target_ptr_size_(InstructionSetPointerSize(compiler_driver_.GetInstructionSet())),
-        bin_slot_sizes_(),
-        bin_slot_offsets_(),
-        bin_slot_count_(),
         intern_table_bytes_(0u),
         image_method_array_(ImageHeader::kImageMethodsCount),
         dirty_methods_(0u),
         clean_methods_(0u),
         class_table_bytes_(0u),
-        image_storage_mode_(image_storage_mode) {
+        image_storage_mode_(image_storage_mode),
+        dex_file_oat_filename_map_(dex_file_oat_filename_map),
+        oat_filenames_(oat_filenames),
+        default_oat_filename_(oat_filenames[0]) {
     CHECK_NE(image_begin, 0U);
+    for (const char* oat_filename : oat_filenames) {
+      image_info_map_.emplace(oat_filename, ImageInfo());
+    }
     std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
-    std::fill_n(oat_address_offsets_, arraysize(oat_address_offsets_), 0);
   }
 
   ~ImageWriter() {
@@ -88,14 +89,25 @@
   bool PrepareImageAddressSpace();
 
   bool IsImageAddressSpaceReady() const {
-    return image_roots_address_ != 0u;
+    bool ready = !image_info_map_.empty();
+    for (auto& pair : image_info_map_) {
+      const ImageInfo& image_info = pair.second;
+      if (image_info.image_roots_address_ == 0u) {
+        return false;
+      }
+    }
+    return ready;
   }
 
   template <typename T>
   T* GetImageAddress(T* object) const SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (object == nullptr || IsInBootImage(object))
-        ? object
-        : reinterpret_cast<T*>(image_begin_ + GetImageOffset(object));
+    if (object == nullptr || IsInBootImage(object)) {
+      return object;
+    } else {
+      const char* oat_filename = GetOatFilename(object);
+      const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+      return reinterpret_cast<T*>(image_info.image_begin_ + GetImageOffset(object));
+    }
   }
 
   ArtMethod* GetImageMethodAddress(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_);
@@ -103,26 +115,36 @@
   template <typename PtrType>
   PtrType GetDexCacheArrayElementImageAddress(const DexFile* dex_file, uint32_t offset)
       const SHARED_REQUIRES(Locks::mutator_lock_) {
-    auto it = dex_cache_array_starts_.find(dex_file);
-    DCHECK(it != dex_cache_array_starts_.end());
+    auto oat_it = dex_file_oat_filename_map_.find(dex_file);
+    DCHECK(oat_it != dex_file_oat_filename_map_.end());
+    const ImageInfo& image_info = GetConstImageInfo(oat_it->second);
+    auto it = image_info.dex_cache_array_starts_.find(dex_file);
+    DCHECK(it != image_info.dex_cache_array_starts_.end());
     return reinterpret_cast<PtrType>(
-        image_begin_ + bin_slot_offsets_[kBinDexCacheArray] + it->second + offset);
+        image_info.image_begin_ + image_info.bin_slot_offsets_[kBinDexCacheArray] +
+            it->second + offset);
   }
 
-  uint8_t* GetOatFileBegin() const;
+  uint8_t* GetOatFileBegin(const char* oat_filename) const;
 
   // If image_fd is not kInvalidImageFd, then we use that for the file. Otherwise we open
-  // image_filename.
+  // the names in image_filenames.
   bool Write(int image_fd,
-             const std::string& image_filename,
-             const std::string& oat_filename,
-             const std::string& oat_location)
+             const std::vector<const char*>& image_filenames,
+             const std::vector<const char*>& oat_filenames)
       REQUIRES(!Locks::mutator_lock_);
 
-  uintptr_t GetOatDataBegin() {
-    return reinterpret_cast<uintptr_t>(oat_data_begin_);
+  uintptr_t GetOatDataBegin(const char* oat_filename) {
+    return reinterpret_cast<uintptr_t>(GetImageInfo(oat_filename).oat_data_begin_);
   }
 
+  const char* GetOatFilenameForDexCache(mirror::DexCache* dex_cache) const
+      SHARED_REQUIRES(Locks::mutator_lock_);
+
+  // Update the oat size for the given oat file. This will make the oat_offset for the next oat
+  // file valid.
+  void UpdateOatFile(const char* oat_filename);
+
  private:
   bool AllocMemory();
 
@@ -214,6 +236,58 @@
     const uint32_t lockword_;
   };
 
+  struct ImageInfo {
+    explicit ImageInfo()
+        : image_begin_(nullptr),
+          image_end_(RoundUp(sizeof(ImageHeader), kObjectAlignment)),
+          image_roots_address_(0),
+          image_offset_(0),
+          image_size_(0),
+          oat_offset_(0),
+          bin_slot_sizes_(),
+          bin_slot_offsets_(),
+          bin_slot_count_() {}
+
+    std::unique_ptr<MemMap> image_;  // Memory mapped for generating the image.
+
+    // Target begin of this image. Notes: It is not valid to write here, this is the address
+    // of the target image, not necessarily where image_ is mapped. The address is only valid
+    // after layouting (otherwise null).
+    uint8_t* image_begin_;
+
+    size_t image_end_;  // Offset to the free space in image_, initially size of image header.
+    uint32_t image_roots_address_;  // The image roots address in the image.
+    size_t image_offset_;  // Offset of this image from the start of the first image.
+
+    // Image size is the *address space* covered by this image. As the live bitmap is aligned
+    // to the page size, the live bitmap will cover more address space than necessary. But live
+    // bitmaps may not overlap, so an image has a "shadow," which is accounted for in the size.
+    // The next image may only start at image_begin_ + image_size_ (which is guaranteed to be
+    // page-aligned).
+    size_t image_size_;
+
+    // Oat data.
+    size_t oat_offset_;  // Offset of the oat file for this image from start of oat files. This is
+                         // valid when the previous oat file has been written.
+    uint8_t* oat_data_begin_;           // Start of oatdata in the corresponding oat file. This is
+                                        // valid when the images have been layed out.
+    size_t oat_size_;                   // Size of the corresponding oat data.
+
+    // Image bitmap which lets us know where the objects inside of the image reside.
+    std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> image_bitmap_;
+
+    // The start offsets of the dex cache arrays.
+    SafeMap<const DexFile*, size_t> dex_cache_array_starts_;
+
+    // Offset from oat_data_begin_ to the stubs.
+    uint32_t oat_address_offsets_[kOatAddressCount];
+
+    // Bin slot tracking for dirty object packing.
+    size_t bin_slot_sizes_[kBinSize];  // Number of bytes in a bin.
+    size_t bin_slot_offsets_[kBinSize];  // Number of bytes in previous bins.
+    size_t bin_slot_count_[kBinSize];  // Number of objects in a bin.
+  };
+
   // We use the lock word to store the offset of the object in the image.
   void AssignImageOffset(mirror::Object* object, BinSlot bin_slot)
       SHARED_REQUIRES(Locks::mutator_lock_);
@@ -233,7 +307,8 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
   BinSlot GetImageBinSlot(mirror::Object* object) const SHARED_REQUIRES(Locks::mutator_lock_);
 
-  void AddDexCacheArrayRelocation(void* array, size_t offset) SHARED_REQUIRES(Locks::mutator_lock_);
+  void AddDexCacheArrayRelocation(void* array, size_t offset, mirror::DexCache* dex_cache)
+      SHARED_REQUIRES(Locks::mutator_lock_);
   void AddMethodPointerArray(mirror::PointerArray* arr) SHARED_REQUIRES(Locks::mutator_lock_);
 
   static void* GetImageAddressCallback(void* writer, mirror::Object* obj)
@@ -244,19 +319,21 @@
   mirror::Object* GetLocalAddress(mirror::Object* object) const
       SHARED_REQUIRES(Locks::mutator_lock_) {
     size_t offset = GetImageOffset(object);
-    uint8_t* dst = image_->Begin() + offset;
+    const char* oat_filename = GetOatFilename(object);
+    const ImageInfo& image_info = GetConstImageInfo(oat_filename);
+    uint8_t* dst = image_info.image_->Begin() + offset;
     return reinterpret_cast<mirror::Object*>(dst);
   }
 
   // Returns the address in the boot image if we are compiling the app image.
   const uint8_t* GetOatAddress(OatAddress type) const;
 
-  const uint8_t* GetOatAddressForOffset(uint32_t offset) const {
+  const uint8_t* GetOatAddressForOffset(uint32_t offset, const ImageInfo& image_info) const {
     // With Quick, code is within the OatFile, as there are all in one
-    // .o ELF object.
-    DCHECK_LE(offset, oat_file_->Size());
-    DCHECK(oat_data_begin_ != nullptr);
-    return offset == 0u ? nullptr : oat_data_begin_ + offset;
+    // .o ELF object. But interpret it as signed.
+    DCHECK_LE(static_cast<int32_t>(offset), static_cast<int32_t>(image_info.oat_size_));
+    DCHECK(image_info.oat_data_begin_ != nullptr);
+    return offset == 0u ? nullptr : image_info.oat_data_begin_ + static_cast<int32_t>(offset);
   }
 
   // Returns true if the class was in the original requested image classes list.
@@ -282,7 +359,7 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
   void CreateHeader(size_t oat_loaded_size, size_t oat_data_offset)
       SHARED_REQUIRES(Locks::mutator_lock_);
-  mirror::ObjectArray<mirror::Object>* CreateImageRoots() const
+  mirror::ObjectArray<mirror::Object>* CreateImageRoots(const char* oat_filename) const
       SHARED_REQUIRES(Locks::mutator_lock_);
   void CalculateObjectBinSlots(mirror::Object* obj)
       SHARED_REQUIRES(Locks::mutator_lock_);
@@ -304,7 +381,7 @@
   static void CopyAndFixupObjectsCallback(mirror::Object* obj, void* arg)
       SHARED_REQUIRES(Locks::mutator_lock_);
   void CopyAndFixupObject(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_);
-  void CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy)
+  void CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy, const ImageInfo& image_info)
       SHARED_REQUIRES(Locks::mutator_lock_);
   void FixupClass(mirror::Class* orig, mirror::Class* copy)
       SHARED_REQUIRES(Locks::mutator_lock_);
@@ -319,23 +396,24 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Get quick code for non-resolution/imt_conflict/abstract method.
-  const uint8_t* GetQuickCode(ArtMethod* method, bool* quick_is_interpreted)
-      SHARED_REQUIRES(Locks::mutator_lock_);
-
-  const uint8_t* GetQuickEntryPoint(ArtMethod* method)
+  const uint8_t* GetQuickCode(ArtMethod* method,
+                              const ImageInfo& image_info,
+                              bool* quick_is_interpreted)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Patches references in OatFile to expect runtime addresses.
   void SetOatChecksumFromElfFile(File* elf_file);
 
   // Calculate the sum total of the bin slot sizes in [0, up_to). Defaults to all bins.
-  size_t GetBinSizeSum(Bin up_to = kBinSize) const;
+  size_t GetBinSizeSum(ImageInfo& image_info, Bin up_to = kBinSize) const;
 
   // Return true if a method is likely to be dirtied at runtime.
   bool WillMethodBeDirty(ArtMethod* m) const SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Assign the offset for an ArtMethod.
-  void AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type)
+  void AssignMethodOffset(ArtMethod* method,
+                          NativeObjectRelocationType type,
+                          const char* oat_filename)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Return true if klass is loaded by the boot class loader but not in the boot image.
@@ -359,11 +437,11 @@
 
   // Location of where the object will be when the image is loaded at runtime.
   template <typename T>
-  T* NativeLocationInImage(T* obj);
+  T* NativeLocationInImage(T* obj, const char* oat_filename) SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Location of where the temporary copy of the object currently is.
   template <typename T>
-  T* NativeCopyLocation(T* obj);
+  T* NativeCopyLocation(T* obj, mirror::DexCache* dex_cache) SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Return true of obj is inside of the boot image space. This may only return true if we are
   // compiling an app image.
@@ -372,46 +450,35 @@
   // Return true if ptr is within the boot oat file.
   bool IsInBootOatFile(const void* ptr) const;
 
+  const char* GetOatFilename(mirror::Object* object) const SHARED_REQUIRES(Locks::mutator_lock_);
+
+  const char* GetDefaultOatFilename() const {
+    return default_oat_filename_;
+  }
+
+  ImageInfo& GetImageInfo(const char* oat_filename);
+  const ImageInfo& GetConstImageInfo(const char* oat_filename) const;
+  const ImageInfo& GetImageInfo(size_t index) const;
+
   const CompilerDriver& compiler_driver_;
 
-  // Beginning target image address for the output image.
-  uint8_t* image_begin_;
-
-  // Offset to the free space in image_.
-  size_t image_end_;
+  // Beginning target image address for the first image.
+  uint8_t* global_image_begin_;
 
   // Offset from image_begin_ to where the first object is in image_.
   size_t image_objects_offset_begin_;
 
-  // The image roots address in the image.
-  uint32_t image_roots_address_;
-
   // oat file with code for this image
   OatFile* oat_file_;
 
-  // Memory mapped for generating the image.
-  std::unique_ptr<MemMap> image_;
-
   // Pointer arrays that need to be updated. Since these are only some int and long arrays, we need
   // to keep track. These include vtable arrays, iftable arrays, and dex caches.
   std::unordered_map<mirror::PointerArray*, Bin> pointer_arrays_;
 
-  // The start offsets of the dex cache arrays.
-  SafeMap<const DexFile*, size_t> dex_cache_array_starts_;
-
   // Saved hash codes. We use these to restore lockwords which were temporarily used to have
   // forwarding addresses as well as copying over hash codes.
   std::unordered_map<mirror::Object*, uint32_t> saved_hashcode_map_;
 
-  // Beginning target oat address for the pointers from the output image to its oat file.
-  const uint8_t* oat_data_begin_;
-
-  // Image bitmap which lets us know where the objects inside of the image reside.
-  std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> image_bitmap_;
-
-  // Offset from oat_data_begin_ to the stubs.
-  uint32_t oat_address_offsets_[kOatAddressCount];
-
   // Boolean flags.
   const bool compile_pic_;
   const bool compile_app_image_;
@@ -422,10 +489,8 @@
   // Size of pointers on the target architecture.
   size_t target_ptr_size_;
 
-  // Bin slot tracking for dirty object packing
-  size_t bin_slot_sizes_[kBinSize];  // Number of bytes in a bin
-  size_t bin_slot_offsets_[kBinSize];  // Number of bytes in previous bins.
-  size_t bin_slot_count_[kBinSize];  // Number of objects in a bin
+  // Mapping of oat filename to image data.
+  std::unordered_map<std::string, ImageInfo> image_info_map_;
 
   // Cached size of the intern table for when we allocate memory.
   size_t intern_table_bytes_;
@@ -434,6 +499,7 @@
   // have one entry per art field for convenience. ArtFields are placed right after the end of the
   // image objects (aka sum of bin_slot_sizes_). ArtMethods are placed right after the ArtFields.
   struct NativeObjectRelocation {
+    const char* oat_filename;
     uintptr_t offset;
     NativeObjectRelocationType type;
 
@@ -468,6 +534,11 @@
   // Which mode the image is stored as, see image.h
   const ImageHeader::StorageMode image_storage_mode_;
 
+  // Map of dex files to the oat filenames that they were compiled into.
+  const std::unordered_map<const DexFile*, const char*>& dex_file_oat_filename_map_;
+  const std::vector<const char*> oat_filenames_;
+  const char* default_oat_filename_;
+
   friend class ContainsBootClassLoaderNonImageClassVisitor;
   friend class FixupClassVisitor;
   friend class FixupRootVisitor;
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index d001495..b323d24 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -84,6 +84,7 @@
       CompilerOptions::kDefaultNumDexMethodsThreshold,
       CompilerOptions::kDefaultInlineDepthLimit,
       CompilerOptions::kDefaultInlineMaxCodeUnits,
+      /* no_inline_from */ nullptr,
       /* include_patch_information */ false,
       CompilerOptions::kDefaultTopKProfileThreshold,
       Runtime::Current()->IsDebuggable(),
@@ -154,7 +155,8 @@
       /* dump_cfg_append */ false,
       cumulative_logger_.get(),
       /* swap_fd */ -1,
-      /* profile_file */ ""));
+      /* profile_file */ "",
+      /* dex to oat map */ nullptr));
   // Disable dedupe so we can remove compiled methods.
   compiler_driver_->SetDedupeEnabled(false);
   compiler_driver_->SetSupportBootImageFixup(false);
diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h
index 92cf8ca..877a674 100644
--- a/compiler/linker/relative_patcher_test.h
+++ b/compiler/linker/relative_patcher_test.h
@@ -47,7 +47,7 @@
         driver_(&compiler_options_, &verification_results_, &inliner_map_,
                 Compiler::kQuick, instruction_set, nullptr,
                 false, nullptr, nullptr, nullptr, 1u,
-                false, false, "", false, nullptr, -1, ""),
+                false, false, "", false, nullptr, -1, "", nullptr),
         error_msg_(),
         instruction_set_(instruction_set),
         features_(InstructionSetFeatures::FromVariant(instruction_set, variant, &error_msg_)),
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index cd0f0d2..58f46d6 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -121,7 +121,8 @@
                                               false,
                                               timer_.get(),
                                               -1,
-                                              ""));
+                                              "",
+                                              nullptr));
   }
 
   bool WriteElf(File* file,
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 53ac77b..025e35e 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -716,6 +716,14 @@
 
   bool VisitMethod(size_t class_def_method_index, const ClassDataItemIterator& it)
       SHARED_REQUIRES(Locks::mutator_lock_) {
+    const DexFile::TypeId& type_id =
+        dex_file_->GetTypeId(dex_file_->GetClassDef(class_def_index_).class_idx_);
+    const char* class_descriptor = dex_file_->GetTypeDescriptor(type_id);
+    // Skip methods that are not in the image.
+    if (!writer_->GetCompilerDriver()->IsImageClass(class_descriptor)) {
+      return true;
+    }
+
     OatClass* oat_class = &writer_->oat_classes_[oat_class_index_];
     CompiledMethod* compiled_method = oat_class->GetCompiledMethod(class_def_method_index);
 
@@ -958,7 +966,9 @@
     if (writer_->HasBootImage()) {
       auto* element = writer_->image_writer_->GetDexCacheArrayElementImageAddress<const uint8_t*>(
               patch.TargetDexCacheDexFile(), patch.TargetDexCacheElementOffset());
-      const uint8_t* oat_data = writer_->image_writer_->GetOatFileBegin() + file_offset_;
+      const char* oat_filename = writer_->image_writer_->GetOatFilenameForDexCache(dex_cache_);
+      const uint8_t* oat_data =
+          writer_->image_writer_->GetOatFileBegin(oat_filename) + file_offset_;
       return element - oat_data;
     } else {
       size_t start = writer_->dex_cache_arrays_offsets_.Get(patch.TargetDexCacheDexFile());
@@ -994,9 +1004,15 @@
       // NOTE: We're using linker patches for app->boot references when the image can
       // be relocated and therefore we need to emit .oat_patches. We're not using this
       // for app->app references, so check that the method is an image method.
-      gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-      size_t method_offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin();
-      CHECK(image_space->GetImageHeader().GetMethodsSection().Contains(method_offset));
+      std::vector<gc::space::ImageSpace*> image_spaces =
+          Runtime::Current()->GetHeap()->GetBootImageSpaces();
+      bool contains_method = false;
+      for (gc::space::ImageSpace* image_space : image_spaces) {
+        size_t method_offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin();
+        contains_method |=
+            image_space->GetImageHeader().GetMethodsSection().Contains(method_offset);
+      }
+      CHECK(contains_method);
     }
     // Note: We only patch targeting ArtMethods in image which is in the low 4gb.
     uint32_t address = PointerToLowMemUInt32(method);
@@ -1012,7 +1028,8 @@
       SHARED_REQUIRES(Locks::mutator_lock_) {
     uint32_t address = target_offset;
     if (writer_->HasBootImage()) {
-      address = PointerToLowMemUInt32(writer_->image_writer_->GetOatFileBegin() +
+      const char* oat_filename = writer_->image_writer_->GetOatFilenameForDexCache(dex_cache_);
+      address = PointerToLowMemUInt32(writer_->image_writer_->GetOatFileBegin(oat_filename) +
                                       writer_->oat_data_offset_ + target_offset);
     }
     DCHECK_LE(offset + 4, code->size());
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index a4dcb3a..9842137 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -372,6 +372,18 @@
 
 bool HInliner::TryInline(HInvoke* invoke_instruction, ArtMethod* method, bool do_rtp) {
   const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile();
+
+  // Check whether we're allowed to inline. The outermost compilation unit is the relevant
+  // dex file here (though the transitivity of an inline chain would allow checking the calller).
+  if (!compiler_driver_->MayInline(method->GetDexFile(),
+                                   outer_compilation_unit_.GetDexFile())) {
+    VLOG(compiler) << "Won't inline " << PrettyMethod(method) << " in "
+                   << outer_compilation_unit_.GetDexFile()->GetLocation() << " ("
+                   << caller_compilation_unit_.GetDexFile()->GetLocation() << ") from "
+                   << method->GetDexFile()->GetLocation();
+    return false;
+  }
+
   uint32_t method_index = FindMethodIndexIn(
       method, caller_dex_file, invoke_instruction->GetDexMethodIndex());
   if (method_index == DexFile::kDexNoIndex) {
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 831b626..dd1d193 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -880,7 +880,11 @@
 
 bool IsCompilingWithCoreImage() {
   const std::string& image = Runtime::Current()->GetImageLocation();
-  return EndsWith(image, "core.art") || EndsWith(image, "core-optimizing.art");
+  // TODO: This is under-approximating...
+  if (EndsWith(image, "core.art") || EndsWith(image, "core-optimizing.art")) {
+    return true;
+  }
+  return false;
 }
 
 bool OptimizingCompiler::JitCompile(Thread* self,
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 21ce73c..bb4224b 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -193,7 +193,7 @@
   UsageError("      corresponding to the file descriptor specified by --zip-fd.");
   UsageError("      Example: --zip-location=/system/app/Calculator.apk");
   UsageError("");
-  UsageError("  --oat-file=<file.oat>: specifies the oat output destination via a filename.");
+  UsageError("  --oat-file=<file.oat>: specifies an oat output destination via a filename.");
   UsageError("      Example: --oat-file=/system/framework/boot.oat");
   UsageError("");
   UsageError("  --oat-fd=<number>: specifies the oat output destination via a file descriptor.");
@@ -203,10 +203,10 @@
   UsageError("      to the file descriptor specified by --oat-fd.");
   UsageError("      Example: --oat-location=/data/dalvik-cache/system@app@Calculator.apk.oat");
   UsageError("");
-  UsageError("  --oat-symbols=<file.oat>: specifies the oat output destination with full symbols.");
+  UsageError("  --oat-symbols=<file.oat>: specifies an oat output destination with full symbols.");
   UsageError("      Example: --oat-symbols=/symbols/system/framework/boot.oat");
   UsageError("");
-  UsageError("  --image=<file.art>: specifies the output image filename.");
+  UsageError("  --image=<file.art>: specifies an output image filename.");
   UsageError("      Example: --image=/system/framework/boot.art");
   UsageError("");
   UsageError("  --image-format=(uncompressed|lz4):");
@@ -355,6 +355,9 @@
   UsageError("  --app-image-file=<file-name>: specify a file name for app image.");
   UsageError("      Example: --app-image-file=/data/dalvik-cache/system@app@Calculator.apk.art");
   UsageError("");
+  UsageError("  --multi-image: specify that separate oat and image files be generated for each "
+             "input dex file.");
+  UsageError("");
   std::cerr << "See log for usage error information\n";
   exit(EXIT_FAILURE);
 }
@@ -536,7 +539,9 @@
       for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files_) {
         dex_file.release();
       }
-      oat_file_.release();
+      for (std::unique_ptr<File>& oat_file : oat_files_) {
+        oat_file.release();
+      }
       runtime_.release();
       verification_results_.release();
       key_value_store_.release();
@@ -544,7 +549,7 @@
   }
 
   struct ParserOptions {
-    std::string oat_symbols;
+    std::vector<const char*> oat_symbols;
     std::string boot_image_filename;
     bool watch_dog_enabled = true;
     bool requested_specific_compiler = false;
@@ -644,8 +649,8 @@
     }
   }
 
-  void ProcessOptions(ParserOptions* parser_options) {
-    boot_image_ = !image_filename_.empty();
+  void ProcessOptions(ParserOptions* parser_options, bool multi_image) {
+    boot_image_ = !image_filenames_.empty();
     app_image_ = app_image_fd_ != -1 || !app_image_file_name_.empty();
 
     if (IsAppImage() && IsBootImage()) {
@@ -657,11 +662,11 @@
       compiler_options_->debuggable_ = true;
     }
 
-    if (oat_filename_.empty() && oat_fd_ == -1) {
+    if (oat_filenames_.empty() && oat_fd_ == -1) {
       Usage("Output must be supplied with either --oat-file or --oat-fd");
     }
 
-    if (!oat_filename_.empty() && oat_fd_ != -1) {
+    if (!oat_filenames_.empty() && oat_fd_ != -1) {
       Usage("--oat-file should not be used with --oat-fd");
     }
 
@@ -673,10 +678,19 @@
       Usage("--oat-symbols should not be used with --host");
     }
 
-    if (oat_fd_ != -1 && !image_filename_.empty()) {
+    if (oat_fd_ != -1 && !image_filenames_.empty()) {
       Usage("--oat-fd should not be used with --image");
     }
 
+    if (!parser_options->oat_symbols.empty() &&
+        parser_options->oat_symbols.size() != oat_filenames_.size()) {
+      Usage("--oat-file arguments do not match --oat-symbols arguments");
+    }
+
+    if (!image_filenames_.empty() && image_filenames_.size() != oat_filenames_.size()) {
+      Usage("--oat-file arguments do not match --image arguments");
+    }
+
     if (android_root_.empty()) {
       const char* android_root_env_var = getenv("ANDROID_ROOT");
       if (android_root_env_var == nullptr) {
@@ -737,6 +751,12 @@
       Usage("--dex-location arguments do not match --dex-file arguments");
     }
 
+    if (!dex_filenames_.empty() && !oat_filenames_.empty()) {
+      if (oat_filenames_.size() != 1 && oat_filenames_.size() != dex_filenames_.size()) {
+        Usage("--oat-file arguments must be singular or match --dex-file arguments");
+      }
+    }
+
     if (zip_fd_ != -1 && zip_location_.empty()) {
       Usage("--zip-location should be supplied with --zip-fd");
     }
@@ -747,11 +767,8 @@
       }
     }
 
-    oat_stripped_ = oat_filename_;
     if (!parser_options->oat_symbols.empty()) {
-      oat_unstripped_ = parser_options->oat_symbols;
-    } else {
-      oat_unstripped_ = oat_filename_;
+      oat_unstripped_ = std::move(parser_options->oat_symbols);
     }
 
     // If no instruction set feature was given, use the default one for the target
@@ -816,6 +833,68 @@
 
     compiler_options_->verbose_methods_ = verbose_methods_.empty() ? nullptr : &verbose_methods_;
 
+    if (!IsBootImage() && multi_image) {
+      Usage("--multi-image can only be used when creating boot images");
+    }
+    if (IsBootImage() && multi_image && image_filenames_.size() > 1) {
+      Usage("--multi-image cannot be used with multiple image names");
+    }
+
+    // For now, if we're on the host and compile the boot image, *always* use multiple image files.
+    if (!kIsTargetBuild && IsBootImage()) {
+      if (image_filenames_.size() == 1) {
+        multi_image = true;
+      }
+    }
+
+    if (IsBootImage() && multi_image) {
+      // Expand the oat and image filenames.
+      std::string base_oat = oat_filenames_[0];
+      size_t last_oat_slash = base_oat.rfind('/');
+      if (last_oat_slash == std::string::npos) {
+        Usage("--multi-image used with unusable oat filename %s", base_oat.c_str());
+      }
+      base_oat = base_oat.substr(0, last_oat_slash + 1);
+
+      std::string base_img = image_filenames_[0];
+      size_t last_img_slash = base_img.rfind('/');
+      if (last_img_slash == std::string::npos) {
+        Usage("--multi-image used with unusable image filename %s", base_img.c_str());
+      }
+      base_img = base_img.substr(0, last_img_slash + 1);
+
+      // Now create the other names.
+      // Note: we have some special case here for our testing. We have to inject the differentiating
+      //       parts for the different core images.
+      std::string infix;  // Empty infix by default.
+      {
+        // Check the first name.
+        std::string dex_file = oat_filenames_[0];
+        size_t last_dex_slash = dex_file.rfind('/');
+        if (last_dex_slash != std::string::npos) {
+          dex_file = dex_file.substr(last_dex_slash + 1);
+        }
+        size_t last_dex_dot = dex_file.rfind('.');
+        if (last_dex_dot != std::string::npos) {
+          dex_file = dex_file.substr(0, last_dex_dot);
+        }
+        if (StartsWith(dex_file, "core-")) {
+          infix = dex_file.substr(strlen("core"));
+        }
+      }
+      // Use a counted loop to skip the first one.
+      for (size_t i = 1; i < dex_locations_.size(); ++i) {
+        // TODO: Make everything properly std::string.
+        std::string image_name = CreateMultiImageName(dex_locations_[i], infix, ".art");
+        char_backing_storage_.push_back(base_img + image_name);
+        image_filenames_.push_back((char_backing_storage_.end() - 1)->c_str());
+
+        std::string oat_name = CreateMultiImageName(dex_locations_[i], infix, ".oat");
+        char_backing_storage_.push_back(base_oat + oat_name);
+        oat_filenames_.push_back((char_backing_storage_.end() - 1)->c_str());
+      }
+    }
+
     // Done with usage checks, enable watchdog if requested
     if (parser_options->watch_dog_enabled) {
       watchdog_.reset(new WatchDog(true));
@@ -825,6 +904,26 @@
     key_value_store_.reset(new SafeMap<std::string, std::string>());
   }
 
+  static std::string CreateMultiImageName(std::string in,
+                                          const std::string& infix,
+                                          const char* suffix) {
+    size_t last_dex_slash = in.rfind('/');
+    if (last_dex_slash != std::string::npos) {
+      in = in.substr(last_dex_slash + 1);
+    }
+    if (!infix.empty()) {
+      // Inject infix.
+      size_t last_dot = in.rfind('.');
+      if (last_dot != std::string::npos) {
+        in.insert(last_dot, infix);
+      }
+    }
+    if (EndsWith(in, ".jar")) {
+      in = in.substr(0, in.length() - strlen(".jar")) + (suffix != nullptr ? suffix : "");
+    }
+    return in;
+  }
+
   void InsertCompileOptions(int argc, char** argv) {
     std::ostringstream oss;
     for (int i = 0; i < argc; ++i) {
@@ -865,6 +964,8 @@
     std::unique_ptr<ParserOptions> parser_options(new ParserOptions());
     compiler_options_.reset(new CompilerOptions());
 
+    bool multi_image = false;
+
     for (int i = 0; i < argc; i++) {
       const StringPiece option(argv[i]);
       const bool log_options = false;
@@ -880,9 +981,9 @@
       } else if (option.starts_with("--zip-location=")) {
         zip_location_ = option.substr(strlen("--zip-location=")).data();
       } else if (option.starts_with("--oat-file=")) {
-        oat_filename_ = option.substr(strlen("--oat-file=")).data();
+        oat_filenames_.push_back(option.substr(strlen("--oat-file=")).data());
       } else if (option.starts_with("--oat-symbols=")) {
-        parser_options->oat_symbols = option.substr(strlen("--oat-symbols=")).data();
+        parser_options->oat_symbols.push_back(option.substr(strlen("--oat-symbols=")).data());
       } else if (option.starts_with("--oat-fd=")) {
         ParseOatFd(option);
       } else if (option == "--watch-dog") {
@@ -894,7 +995,7 @@
       } else if (option.starts_with("--oat-location=")) {
         oat_location_ = option.substr(strlen("--oat-location=")).data();
       } else if (option.starts_with("--image=")) {
-        image_filename_ = option.substr(strlen("--image=")).data();
+        image_filenames_.push_back(option.substr(strlen("--image=")).data());
       } else if (option.starts_with("--image-classes=")) {
         image_classes_filename_ = option.substr(strlen("--image-classes=")).data();
       } else if (option.starts_with("--image-classes-zip=")) {
@@ -961,41 +1062,56 @@
         //       conditional on having verbost methods.
         gLogVerbosity.compiler = false;
         Split(option.substr(strlen("--verbose-methods=")).ToString(), ',', &verbose_methods_);
+      } else if (option == "--multi-image") {
+        multi_image = true;
+      } else if (option.starts_with("--no-inline-from=")) {
+        no_inline_from_string_ = option.substr(strlen("--no-inline-from=")).data();
       } else if (!compiler_options_->ParseCompilerOption(option, Usage)) {
         Usage("Unknown argument %s", option.data());
       }
     }
 
-    ProcessOptions(parser_options.get());
+    ProcessOptions(parser_options.get(), multi_image);
 
     // Insert some compiler things.
     InsertCompileOptions(argc, argv);
   }
 
-  // Check whether the oat output file is writable, and open it for later. Also open a swap file,
-  // if a name is given.
+  // Check whether the oat output files are writable, and open them for later. Also open a swap
+  // file, if a name is given.
   bool OpenFile() {
-    bool create_file = !oat_unstripped_.empty();  // as opposed to using open file descriptor
+    bool create_file = oat_fd_ == -1;  // as opposed to using open file descriptor
     if (create_file) {
-      oat_file_.reset(OS::CreateEmptyFile(oat_unstripped_.c_str()));
-      if (oat_location_.empty()) {
-        oat_location_ = oat_filename_;
+      for (const char* oat_filename : oat_filenames_) {
+        std::unique_ptr<File> oat_file(OS::CreateEmptyFile(oat_filename));
+        if (oat_file.get() == nullptr) {
+          PLOG(ERROR) << "Failed to create oat file: " << oat_filename;
+          return false;
+        }
+        if (create_file && fchmod(oat_file->Fd(), 0644) != 0) {
+          PLOG(ERROR) << "Failed to make oat file world readable: " << oat_filename;
+          oat_file->Erase();
+          return false;
+        }
+        oat_files_.push_back(std::move(oat_file));
       }
     } else {
-      oat_file_.reset(new File(oat_fd_, oat_location_, true));
-      oat_file_->DisableAutoClose();
-      if (oat_file_->SetLength(0) != 0) {
+      std::unique_ptr<File> oat_file(new File(oat_fd_, oat_location_, true));
+      oat_file->DisableAutoClose();
+      if (oat_file->SetLength(0) != 0) {
         PLOG(WARNING) << "Truncating oat file " << oat_location_ << " failed.";
       }
-    }
-    if (oat_file_.get() == nullptr) {
-      PLOG(ERROR) << "Failed to create oat file: " << oat_location_;
-      return false;
-    }
-    if (create_file && fchmod(oat_file_->Fd(), 0644) != 0) {
-      PLOG(ERROR) << "Failed to make oat file world readable: " << oat_location_;
-      oat_file_->Erase();
-      return false;
+      if (oat_file.get() == nullptr) {
+        PLOG(ERROR) << "Failed to create oat file: " << oat_location_;
+        return false;
+      }
+      if (create_file && fchmod(oat_file->Fd(), 0644) != 0) {
+        PLOG(ERROR) << "Failed to make oat file world readable: " << oat_location_;
+        oat_file->Erase();
+        return false;
+      }
+      oat_filenames_.push_back(oat_location_.c_str());
+      oat_files_.push_back(std::move(oat_file));
     }
 
     // Swap file handling.
@@ -1020,10 +1136,12 @@
     return true;
   }
 
-  void EraseOatFile() {
-    DCHECK(oat_file_.get() != nullptr);
-    oat_file_->Erase();
-    oat_file_.reset();
+  void EraseOatFiles() {
+    for (size_t i = 0; i < oat_files_.size(); ++i) {
+      DCHECK(oat_files_[i].get() != nullptr);
+      oat_files_[i]->Erase();
+      oat_files_[i].reset();
+    }
   }
 
   void Shutdown() {
@@ -1158,9 +1276,35 @@
       }
     }
 
+    // Organize inputs, handling multi-dex and multiple oat file outputs.
+    CreateDexOatMappings();
+
     return true;
   }
 
+  void CreateDexOatMappings() {
+    if (oat_files_.size() > 1) {
+      size_t index = 0;
+      for (size_t i = 0; i < oat_files_.size(); ++i) {
+        std::vector<const DexFile*> dex_files = { 1, dex_files_[index] };
+        dex_file_oat_filename_map_.emplace(dex_files_[index], oat_filenames_[i]);
+        index++;
+        while (index < dex_files_.size() &&
+            (dex_files_[index]->GetBaseLocation() == dex_files_[index - 1]->GetBaseLocation())) {
+          dex_file_oat_filename_map_.emplace(dex_files_[index], oat_filenames_[i]);
+          dex_files.push_back(dex_files_[index]);
+          index++;
+        }
+        dex_files_per_oat_file_.push_back(std::move(dex_files));
+      }
+    } else {
+      dex_files_per_oat_file_.push_back(dex_files_);
+      for (const DexFile* dex_file : dex_files_) {
+        dex_file_oat_filename_map_.emplace(dex_file, oat_filenames_[0]);
+      }
+    }
+  }
+
   // Create and invoke the compiler driver. This will compile all the dex files.
   void Compile() {
     TimingLogger::ScopedTiming t("dex2oat Compile", timings_);
@@ -1191,6 +1335,85 @@
       class_loader = class_linker->CreatePathClassLoader(self, dex_files_, class_path_class_loader);
     }
 
+    // Find the dex file we should not inline from.
+
+    // For now, on the host always have core-oj removed.
+    if (!kIsTargetBuild && no_inline_from_string_.empty()) {
+      no_inline_from_string_ = "core-oj";
+    }
+
+    if (!no_inline_from_string_.empty()) {
+      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+      std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_);
+      std::vector<const std::vector<const DexFile*>*> dex_file_vectors = {
+          &class_linker->GetBootClassPath(),
+          &class_path_files,
+          &dex_files_
+      };
+      for (const std::vector<const DexFile*>* dex_file_vector : dex_file_vectors) {
+        if (dex_file_vector == nullptr) {
+          continue;
+        }
+
+        bool found = false;
+
+        for (const DexFile* dex_file : *dex_file_vector) {
+          // Try the complete location first.
+          found = no_inline_from_string_ == dex_file->GetLocation();
+          // The try just the name.
+          if (!found) {
+            size_t last_slash = dex_file->GetLocation().rfind('/');
+            if (last_slash != std::string::npos) {
+              found = StartsWith(dex_file->GetLocation().substr(last_slash + 1),
+                                 no_inline_from_string_.c_str());
+            }
+          }
+
+          if (found) {
+            VLOG(compiler) << "Disabling inlining from " << dex_file->GetLocation();
+            compiler_options_->no_inline_from_ = dex_file;
+            break;
+          }
+        }
+
+        if (found) {
+          break;
+        }
+      }
+    }
+
+    if (IsBootImage() && image_filenames_.size() > 1) {
+      // If we're compiling the boot image, store the boot classpath into the Key-Value store. If
+      // the image filename was adapted (e.g., for our tests), we need to change this here, too. We
+      // need this for the multi-image case.
+      std::ostringstream bootcp_oss;
+      bool first_bootcp = true;
+      for (size_t i = 0; i < dex_locations_.size(); ++i) {
+        if (!first_bootcp) {
+          bootcp_oss << ":";
+        }
+
+        std::string dex_loc = dex_locations_[i];
+        std::string image_filename = image_filenames_[i];
+
+        // Use the dex_loc path, but the image_filename name.
+        size_t dex_last_slash = dex_loc.rfind('/');
+        size_t image_last_slash = image_filename.rfind('/');
+        if (dex_last_slash == std::string::npos) {
+          dex_loc = image_filename.substr(image_last_slash + 1);
+        } else {
+          dex_loc = dex_loc.substr(0, dex_last_slash + 1) +
+              image_filename.substr(image_last_slash + 1);
+        }
+
+        // Image filenames already end with .art, no need to replace.
+
+        bootcp_oss << dex_loc;
+        first_bootcp = false;
+      }
+      key_value_store_->Put(OatHeader::kBootClassPath, bootcp_oss.str());
+    }
+
     driver_.reset(new CompilerDriver(compiler_options_.get(),
                                      verification_results_.get(),
                                      &method_inliner_map_,
@@ -1208,12 +1431,14 @@
                                      dump_cfg_append_,
                                      compiler_phases_timings_.get(),
                                      swap_fd_,
-                                     profile_file_));
+                                     profile_file_,
+                                     &dex_file_oat_filename_map_));
 
     driver_->SetDexFilesForOatFile(dex_files_);
     driver_->CompileAll(class_loader, dex_files_, timings_);
   }
 
+  // TODO: Update comments about how this works for multi image. b/26317072
   // Notes on the interleaving of creating the image and oat file to
   // ensure the references between the two are correct.
   //
@@ -1275,17 +1500,16 @@
   // Steps 1.-3. are done by the CreateOatFile() above, steps 4.-5.
   // are done by the CreateImageFile() below.
 
-
   // Write out the generated code part. Calls the OatWriter and ElfBuilder. Also prepares the
   // ImageWriter, if necessary.
   // Note: Flushing (and closing) the file is the caller's responsibility, except for the failure
   //       case (when the file will be explicitly erased).
-  bool CreateOatFile() {
+  bool CreateOatFiles() {
     CHECK(key_value_store_.get() != nullptr);
 
     TimingLogger::ScopedTiming t("dex2oat Oat", timings_);
 
-    std::unique_ptr<OatWriter> oat_writer;
+    std::vector<std::unique_ptr<OatWriter>> oat_writers;
     {
       TimingLogger::ScopedTiming t2("dex2oat OatWriter", timings_);
       std::string image_file_location;
@@ -1294,10 +1518,14 @@
       int32_t image_patch_delta = 0;
 
       if (app_image_ && image_base_ == 0) {
-        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-        image_base_ = RoundUp(
-            reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()),
-            kPageSize);
+        std::vector<gc::space::ImageSpace*> image_spaces =
+            Runtime::Current()->GetHeap()->GetBootImageSpaces();
+        for (gc::space::ImageSpace* image_space : image_spaces) {
+          // TODO: IS THIS IN ORDER? JUST TAKE THE LAST ONE?
+          image_base_ = std::max(image_base_, RoundUp(
+              reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()),
+              kPageSize));
+        }
         VLOG(compiler) << "App image base=" << reinterpret_cast<void*>(image_base_);
       }
 
@@ -1307,27 +1535,36 @@
 
       if (!IsBootImage()) {
         TimingLogger::ScopedTiming t3("Loading image checksum", timings_);
-        gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-        image_file_location_oat_checksum = image_space->GetImageHeader().GetOatChecksum();
+        std::vector<gc::space::ImageSpace*> image_spaces =
+            Runtime::Current()->GetHeap()->GetBootImageSpaces();
+        image_file_location_oat_checksum = image_spaces[0]->GetImageHeader().GetOatChecksum();
         image_file_location_oat_data_begin =
-            reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatDataBegin());
-        image_file_location = image_space->GetImageFilename();
-        image_patch_delta = image_space->GetImageHeader().GetPatchDelta();
+            reinterpret_cast<uintptr_t>(image_spaces[0]->GetImageHeader().GetOatDataBegin());
+        image_patch_delta = image_spaces[0]->GetImageHeader().GetPatchDelta();
+        std::vector<std::string> image_filenames;
+        for (const gc::space::ImageSpace* image_space : image_spaces) {
+          image_filenames.push_back(image_space->GetImageFilename());
+        }
+        image_file_location = Join(image_filenames, ':');
       }
 
       if (!image_file_location.empty()) {
         key_value_store_->Put(OatHeader::kImageLocationKey, image_file_location);
       }
 
-      oat_writer.reset(new OatWriter(dex_files_,
-                                     image_file_location_oat_checksum,
-                                     image_file_location_oat_data_begin,
-                                     image_patch_delta,
-                                     driver_.get(),
-                                     image_writer_.get(),
-                                     IsBootImage(),
-                                     timings_,
-                                     key_value_store_.get()));
+      for (size_t i = 0; i < oat_files_.size(); ++i) {
+        std::vector<const DexFile*>& dex_files = dex_files_per_oat_file_[i];
+        std::unique_ptr<OatWriter> oat_writer(new OatWriter(dex_files,
+                                                            image_file_location_oat_checksum,
+                                                            image_file_location_oat_data_begin,
+                                                            image_patch_delta,
+                                                            driver_.get(),
+                                                            image_writer_.get(),
+                                                            IsBootImage(),
+                                                            timings_,
+                                                            key_value_store_.get()));
+        oat_writers.push_back(std::move(oat_writer));
+      }
     }
 
     if (IsImage()) {
@@ -1342,37 +1579,56 @@
 
     {
       TimingLogger::ScopedTiming t2("dex2oat Write ELF", timings_);
-      std::unique_ptr<ElfWriter> elf_writer =
-          CreateElfWriterQuick(instruction_set_, compiler_options_.get(), oat_file_.get());
+      for (size_t i = 0; i < oat_files_.size(); ++i) {
+        std::unique_ptr<File>& oat_file = oat_files_[i];
+        std::unique_ptr<OatWriter>& oat_writer = oat_writers[i];
+        std::unique_ptr<ElfWriter> elf_writer =
+            CreateElfWriterQuick(instruction_set_, compiler_options_.get(), oat_file.get());
 
-      elf_writer->Start();
+        elf_writer->Start();
 
-      OutputStream* rodata = elf_writer->StartRoData();
-      if (!oat_writer->WriteRodata(rodata)) {
-        LOG(ERROR) << "Failed to write .rodata section to the ELF file " << oat_file_->GetPath();
-        return false;
-      }
-      elf_writer->EndRoData(rodata);
+        OutputStream* rodata = elf_writer->StartRoData();
+        if (!oat_writer->WriteRodata(rodata)) {
+          LOG(ERROR) << "Failed to write .rodata section to the ELF file " << oat_file->GetPath();
+          return false;
+        }
+        elf_writer->EndRoData(rodata);
 
-      OutputStream* text = elf_writer->StartText();
-      if (!oat_writer->WriteCode(text)) {
-        LOG(ERROR) << "Failed to write .text section to the ELF file " << oat_file_->GetPath();
-        return false;
-      }
-      elf_writer->EndText(text);
+        OutputStream* text = elf_writer->StartText();
+        if (!oat_writer->WriteCode(text)) {
+          LOG(ERROR) << "Failed to write .text section to the ELF file " << oat_file->GetPath();
+          return false;
+        }
+        elf_writer->EndText(text);
 
-      elf_writer->SetBssSize(oat_writer->GetBssSize());
-      elf_writer->WriteDynamicSection();
-      elf_writer->WriteDebugInfo(oat_writer->GetMethodDebugInfo());
-      elf_writer->WritePatchLocations(oat_writer->GetAbsolutePatchLocations());
+        elf_writer->SetBssSize(oat_writer->GetBssSize());
+        elf_writer->WriteDynamicSection();
+        elf_writer->WriteDebugInfo(oat_writer->GetMethodDebugInfo());
+        elf_writer->WritePatchLocations(oat_writer->GetAbsolutePatchLocations());
 
-      if (!elf_writer->End()) {
-        LOG(ERROR) << "Failed to write ELF file " << oat_file_->GetPath();
-        return false;
+        if (!elf_writer->End()) {
+          LOG(ERROR) << "Failed to write ELF file " << oat_file->GetPath();
+          return false;
+        }
+
+        // Flush the oat file.
+        if (oat_files_[i] != nullptr) {
+          if (oat_files_[i]->Flush() != 0) {
+            PLOG(ERROR) << "Failed to flush oat file: " << oat_filenames_[i];
+            oat_files_[i]->Erase();
+            return false;
+          }
+        }
+
+        if (IsImage()) {
+          // Update oat estimates.
+          UpdateImageWriter(i);
+        }
+
+        VLOG(compiler) << "Oat file written successfully: " << oat_filenames_[i];
       }
     }
 
-    VLOG(compiler) << "Oat file written successfully (unstripped): " << oat_location_;
     return true;
   }
 
@@ -1383,70 +1639,80 @@
       if (!CreateImageFile()) {
         return false;
       }
-      VLOG(compiler) << "Image written successfully: " << image_filename_;
+      VLOG(compiler) << "Images written successfully";
     }
     return true;
   }
 
-  // Create a copy from unstripped to stripped.
-  bool CopyUnstrippedToStripped() {
-    // If we don't want to strip in place, copy from unstripped location to stripped location.
-    // We need to strip after image creation because FixupElf needs to use .strtab.
-    if (oat_unstripped_ != oat_stripped_) {
-      // If the oat file is still open, flush it.
-      if (oat_file_.get() != nullptr && oat_file_->IsOpened()) {
-        if (!FlushCloseOatFile()) {
+  // Create a copy from stripped to unstripped.
+  bool CopyStrippedToUnstripped() {
+    for (size_t i = 0; i < oat_unstripped_.size(); ++i) {
+      // If we don't want to strip in place, copy from stripped location to unstripped location.
+      // We need to strip after image creation because FixupElf needs to use .strtab.
+      if (strcmp(oat_unstripped_[i], oat_filenames_[i]) != 0) {
+        // If the oat file is still open, flush it.
+        if (oat_files_[i].get() != nullptr && oat_files_[i]->IsOpened()) {
+          if (!FlushCloseOatFile(i)) {
+            return false;
+          }
+        }
+
+        TimingLogger::ScopedTiming t("dex2oat OatFile copy", timings_);
+        std::unique_ptr<File> in(OS::OpenFileForReading(oat_filenames_[i]));
+        std::unique_ptr<File> out(OS::CreateEmptyFile(oat_unstripped_[i]));
+        size_t buffer_size = 8192;
+        std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
+        while (true) {
+          int bytes_read = TEMP_FAILURE_RETRY(read(in->Fd(), buffer.get(), buffer_size));
+          if (bytes_read <= 0) {
+            break;
+          }
+          bool write_ok = out->WriteFully(buffer.get(), bytes_read);
+          CHECK(write_ok);
+        }
+        if (out->FlushCloseOrErase() != 0) {
+          PLOG(ERROR) << "Failed to flush and close copied oat file: " << oat_unstripped_[i];
+          return false;
+        }
+        VLOG(compiler) << "Oat file copied successfully (unstripped): " << oat_unstripped_[i];
+      }
+    }
+    return true;
+  }
+
+  bool FlushOatFiles() {
+    TimingLogger::ScopedTiming t2("dex2oat Flush ELF", timings_);
+    for (size_t i = 0; i < oat_files_.size(); ++i) {
+      if (oat_files_[i].get() != nullptr) {
+        if (oat_files_[i]->Flush() != 0) {
+          PLOG(ERROR) << "Failed to flush oat file: " << oat_filenames_[i];
+          oat_files_[i]->Erase();
           return false;
         }
       }
-
-      TimingLogger::ScopedTiming t("dex2oat OatFile copy", timings_);
-      std::unique_ptr<File> in(OS::OpenFileForReading(oat_unstripped_.c_str()));
-      std::unique_ptr<File> out(OS::CreateEmptyFile(oat_stripped_.c_str()));
-      size_t buffer_size = 8192;
-      std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
-      while (true) {
-        int bytes_read = TEMP_FAILURE_RETRY(read(in->Fd(), buffer.get(), buffer_size));
-        if (bytes_read <= 0) {
-          break;
-        }
-        bool write_ok = out->WriteFully(buffer.get(), bytes_read);
-        CHECK(write_ok);
-      }
-      if (out->FlushCloseOrErase() != 0) {
-        PLOG(ERROR) << "Failed to flush and close copied oat file: " << oat_stripped_;
-        return false;
-      }
-      VLOG(compiler) << "Oat file copied successfully (stripped): " << oat_stripped_;
     }
     return true;
   }
 
-  bool FlushOatFile() {
-    if (oat_file_.get() != nullptr) {
-      TimingLogger::ScopedTiming t2("dex2oat Flush ELF", timings_);
-      if (oat_file_->Flush() != 0) {
-        PLOG(ERROR) << "Failed to flush oat file: " << oat_location_ << " / "
-            << oat_filename_;
-        oat_file_->Erase();
-        return false;
-      }
-    }
-    return true;
-  }
-
-  bool FlushCloseOatFile() {
-    if (oat_file_.get() != nullptr) {
-      std::unique_ptr<File> tmp(oat_file_.release());
+  bool FlushCloseOatFile(size_t i) {
+    if (oat_files_[i].get() != nullptr) {
+      std::unique_ptr<File> tmp(oat_files_[i].release());
       if (tmp->FlushCloseOrErase() != 0) {
-        PLOG(ERROR) << "Failed to flush and close oat file: " << oat_location_ << " / "
-            << oat_filename_;
+        PLOG(ERROR) << "Failed to flush and close oat file: " << oat_filenames_[i];
         return false;
       }
     }
     return true;
   }
 
+  bool FlushCloseOatFiles() {
+    bool result = true;
+    for (size_t i = 0; i < oat_files_.size(); ++i) {
+      result &= FlushCloseOatFile(i);
+    }
+    return result;
+  }
+
   void DumpTiming() {
     if (dump_timing_ || (dump_slow_timing_ && timings_->GetTotalNs() > MsToNs(1000))) {
       LOG(INFO) << Dumpable<TimingLogger>(*timings_);
@@ -1707,42 +1973,52 @@
                                         image_base,
                                         compiler_options_->GetCompilePic(),
                                         IsAppImage(),
-                                        image_storage_mode_));
+                                        image_storage_mode_,
+                                        oat_filenames_,
+                                        dex_file_oat_filename_map_));
   }
 
-  // Let the ImageWriter write the image file. If we do not compile PIC, also fix up the oat file.
+  // Let the ImageWriter write the image files. If we do not compile PIC, also fix up the oat files.
   bool CreateImageFile()
       REQUIRES(!Locks::mutator_lock_) {
     CHECK(image_writer_ != nullptr);
-    if (!image_writer_->Write(app_image_fd_,
-                              IsBootImage() ? image_filename_ : app_image_file_name_,
-                              oat_unstripped_,
-                              oat_location_)) {
-      LOG(ERROR) << "Failed to create image file " << image_filename_;
+    if (!IsBootImage()) {
+      image_filenames_.push_back(app_image_file_name_.c_str());
+    }
+    if (!image_writer_->Write(app_image_fd_, image_filenames_, oat_filenames_)) {
+      LOG(ERROR) << "Failure during image file creation";
       return false;
     }
-    uintptr_t oat_data_begin = image_writer_->GetOatDataBegin();
 
+    // We need the OatDataBegin entries.
+    std::map<const char*, uintptr_t> oat_data_begins;
+    for (const char* oat_filename : oat_filenames_) {
+      oat_data_begins.emplace(oat_filename, image_writer_->GetOatDataBegin(oat_filename));
+    }
     // Destroy ImageWriter before doing FixupElf.
     image_writer_.reset();
 
-    // Do not fix up the ELF file if we are --compile-pic or compiing the app image
-    if (!compiler_options_->GetCompilePic() && IsBootImage()) {
-      std::unique_ptr<File> oat_file(OS::OpenFileReadWrite(oat_unstripped_.c_str()));
-      if (oat_file.get() == nullptr) {
-        PLOG(ERROR) << "Failed to open ELF file: " << oat_unstripped_;
-        return false;
-      }
+    for (const char* oat_filename : oat_filenames_) {
+      // Do not fix up the ELF file if we are --compile-pic or compiling the app image
+      if (!compiler_options_->GetCompilePic() && IsBootImage()) {
+        std::unique_ptr<File> oat_file(OS::OpenFileReadWrite(oat_filename));
+        if (oat_file.get() == nullptr) {
+          PLOG(ERROR) << "Failed to open ELF file: " << oat_filename;
+          return false;
+        }
 
-      if (!ElfWriter::Fixup(oat_file.get(), oat_data_begin)) {
-        oat_file->Erase();
-        LOG(ERROR) << "Failed to fixup ELF file " << oat_file->GetPath();
-        return false;
-      }
+        uintptr_t oat_data_begin = oat_data_begins.find(oat_filename)->second;
 
-      if (oat_file->FlushCloseOrErase()) {
-        PLOG(ERROR) << "Failed to flush and close fixed ELF file " << oat_file->GetPath();
-        return false;
+        if (!ElfWriter::Fixup(oat_file.get(), oat_data_begin)) {
+          oat_file->Erase();
+          LOG(ERROR) << "Failed to fixup ELF file " << oat_file->GetPath();
+          return false;
+        }
+
+        if (oat_file->FlushCloseOrErase()) {
+          PLOG(ERROR) << "Failed to flush and close fixed ELF file " << oat_file->GetPath();
+          return false;
+        }
       }
     }
 
@@ -1845,6 +2121,33 @@
                   "");
   }
 
+  std::string StripIsaFrom(const char* image_filename, InstructionSet isa) {
+    std::string res(image_filename);
+    size_t last_slash = res.rfind('/');
+    if (last_slash == std::string::npos || last_slash == 0) {
+      return res;
+    }
+    size_t penultimate_slash = res.rfind('/', last_slash - 1);
+    if (penultimate_slash == std::string::npos) {
+      return res;
+    }
+    // Check that the string in-between is the expected one.
+    if (res.substr(penultimate_slash + 1, last_slash - penultimate_slash - 1) !=
+            GetInstructionSetString(isa)) {
+      LOG(WARNING) << "Unexpected string when trying to strip isa: " << res;
+      return res;
+    }
+    return res.substr(0, penultimate_slash) + res.substr(last_slash);
+  }
+
+  // Update the estimate for the oat file with the given index.
+  void UpdateImageWriter(size_t index) {
+    DCHECK(image_writer_ != nullptr);
+    DCHECK_LT(index, oat_filenames_.size());
+
+    image_writer_->UpdateOatFile(oat_filenames_[index]);
+  }
+
   std::unique_ptr<CompilerOptions> compiler_options_;
   Compiler::Kind compiler_kind_;
 
@@ -1866,11 +2169,10 @@
   size_t thread_count_;
   uint64_t start_ns_;
   std::unique_ptr<WatchDog> watchdog_;
-  std::unique_ptr<File> oat_file_;
-  std::string oat_stripped_;
-  std::string oat_unstripped_;
+  std::vector<std::unique_ptr<File>> oat_files_;
   std::string oat_location_;
-  std::string oat_filename_;
+  std::vector<const char*> oat_filenames_;
+  std::vector<const char*> oat_unstripped_;
   int oat_fd_;
   std::vector<const char*> dex_filenames_;
   std::vector<const char*> dex_locations_;
@@ -1878,7 +2180,7 @@
   std::string zip_location_;
   std::string boot_image_filename_;
   std::vector<const char*> runtime_args_;
-  std::string image_filename_;
+  std::vector<const char*> image_filenames_;
   uintptr_t image_base_;
   const char* image_classes_zip_filename_;
   const char* image_classes_filename_;
@@ -1895,6 +2197,7 @@
   bool is_host_;
   std::string android_root_;
   std::vector<const DexFile*> dex_files_;
+  std::string no_inline_from_string_;
   std::vector<jobject> dex_caches_;
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 
@@ -1915,6 +2218,11 @@
   std::string profile_file_;  // Profile file to use
   TimingLogger* timings_;
   std::unique_ptr<CumulativeLogger> compiler_phases_timings_;
+  std::vector<std::vector<const DexFile*>> dex_files_per_oat_file_;
+  std::unordered_map<const DexFile*, const char*> dex_file_oat_filename_map_;
+
+  // Backing storage.
+  std::vector<std::string> char_backing_storage_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat);
 };
@@ -1942,19 +2250,18 @@
 static int CompileImage(Dex2Oat& dex2oat) {
   dex2oat.Compile();
 
-  // Create the boot.oat.
-  if (!dex2oat.CreateOatFile()) {
-    dex2oat.EraseOatFile();
+  if (!dex2oat.CreateOatFiles()) {
+    dex2oat.EraseOatFiles();
     return EXIT_FAILURE;
   }
 
-  // Flush and close the boot.oat. We always expect the output file by name, and it will be
-  // re-opened from the unstripped name.
-  if (!dex2oat.FlushCloseOatFile()) {
+  // Close the image oat files. We always expect the output file by name, and it will be
+  // re-opened from the unstripped name. Note: it's easier to *flush* and close...
+  if (!dex2oat.FlushCloseOatFiles()) {
     return EXIT_FAILURE;
   }
 
-  // Creates the boot.art and patches the boot.oat.
+  // Creates the boot.art and patches the oat files.
   if (!dex2oat.HandleImage()) {
     return EXIT_FAILURE;
   }
@@ -1965,13 +2272,13 @@
     return EXIT_SUCCESS;
   }
 
-  // Copy unstripped to stripped location, if necessary.
-  if (!dex2oat.CopyUnstrippedToStripped()) {
+  // Copy stripped to unstripped location, if necessary.
+  if (!dex2oat.CopyStrippedToUnstripped()) {
     return EXIT_FAILURE;
   }
 
-  // FlushClose again, as stripping might have re-opened the oat file.
-  if (!dex2oat.FlushCloseOatFile()) {
+  // FlushClose again, as stripping might have re-opened the oat files.
+  if (!dex2oat.FlushCloseOatFiles()) {
     return EXIT_FAILURE;
   }
 
@@ -1982,21 +2289,17 @@
 static int CompileApp(Dex2Oat& dex2oat) {
   dex2oat.Compile();
 
-  // Create the app oat.
-  if (!dex2oat.CreateOatFile()) {
-    dex2oat.EraseOatFile();
+  if (!dex2oat.CreateOatFiles()) {
+    dex2oat.EraseOatFiles();
     return EXIT_FAILURE;
   }
 
-  // Do not close the oat file here. We might haven gotten the output file by file descriptor,
+  // Do not close the oat files here. We might have gotten the output file by file descriptor,
   // which we would lose.
-  if (!dex2oat.FlushOatFile()) {
-    return EXIT_FAILURE;
-  }
 
   // When given --host, finish early without stripping.
   if (dex2oat.IsHost()) {
-    if (!dex2oat.FlushCloseOatFile()) {
+    if (!dex2oat.FlushCloseOatFiles()) {
       return EXIT_FAILURE;
     }
 
@@ -2004,14 +2307,14 @@
     return EXIT_SUCCESS;
   }
 
-  // Copy unstripped to stripped location, if necessary. This will implicitly flush & close the
-  // unstripped version. If this is given, we expect to be able to open writable files by name.
-  if (!dex2oat.CopyUnstrippedToStripped()) {
+  // Copy stripped to unstripped location, if necessary. This will implicitly flush & close the
+  // stripped versions. If this is given, we expect to be able to open writable files by name.
+  if (!dex2oat.CopyStrippedToUnstripped()) {
     return EXIT_FAILURE;
   }
 
-  // Flush and close the file.
-  if (!dex2oat.FlushCloseOatFile()) {
+  // Flush and close the files.
+  if (!dex2oat.FlushCloseOatFiles()) {
     return EXIT_FAILURE;
   }
 
@@ -2047,7 +2350,7 @@
   }
 
   if (!dex2oat.Setup()) {
-    dex2oat.EraseOatFile();
+    dex2oat.EraseOatFiles();
     return EXIT_FAILURE;
   }
 
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 5e71053..b8a72af 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -814,9 +814,9 @@
 
   static const ImageHeader& GetBootImageHeader() {
     gc::Heap* heap = Runtime::Current()->GetHeap();
-    gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
-    CHECK(image_space != nullptr);
-    const ImageHeader& image_header = image_space->GetImageHeader();
+    std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
+    CHECK(!image_spaces.empty());
+    const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
     return image_header;
   }
 
@@ -834,22 +834,25 @@
   DISALLOW_COPY_AND_ASSIGN(ImgDiagDumper);
 };
 
-static int DumpImage(Runtime* runtime, const char* image_location,
-                     std::ostream* os, pid_t image_diff_pid) {
+static int DumpImage(Runtime* runtime, std::ostream* os, pid_t image_diff_pid) {
   ScopedObjectAccess soa(Thread::Current());
   gc::Heap* heap = runtime->GetHeap();
-  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
-  CHECK(image_space != nullptr);
-  const ImageHeader& image_header = image_space->GetImageHeader();
-  if (!image_header.IsValid()) {
-    fprintf(stderr, "Invalid image header %s\n", image_location);
-    return EXIT_FAILURE;
+  std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
+  CHECK(!image_spaces.empty());
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    const ImageHeader& image_header = image_space->GetImageHeader();
+    if (!image_header.IsValid()) {
+      fprintf(stderr, "Invalid image header %s\n", image_space->GetImageLocation().c_str());
+      return EXIT_FAILURE;
+    }
+
+    ImgDiagDumper img_diag_dumper(
+        os, image_header, image_space->GetImageLocation().c_str(), image_diff_pid);
+    if (!img_diag_dumper.Dump()) {
+      return EXIT_FAILURE;
+    }
   }
-
-  ImgDiagDumper img_diag_dumper(os, image_header, image_location, image_diff_pid);
-
-  bool success = img_diag_dumper.Dump();
-  return (success) ? EXIT_SUCCESS : EXIT_FAILURE;
+  return EXIT_SUCCESS;
 }
 
 struct ImgDiagArgs : public CmdlineArgs {
@@ -935,7 +938,6 @@
     CHECK(args_ != nullptr);
 
     return DumpImage(runtime,
-                     args_->boot_image_location_,
                      args_->os_,
                      args_->image_diff_pid_) == EXIT_SUCCESS;
   }
diff --git a/imgdiag/imgdiag_test.cc b/imgdiag/imgdiag_test.cc
index a926ca5..dc101e5 100644
--- a/imgdiag/imgdiag_test.cc
+++ b/imgdiag/imgdiag_test.cc
@@ -47,9 +47,10 @@
     CommonRuntimeTest::SetUp();
 
     // We loaded the runtime with an explicit image. Therefore the image space must exist.
-    gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-    ASSERT_TRUE(image_space != nullptr);
-    boot_image_location_ = image_space->GetImageLocation();
+    std::vector<gc::space::ImageSpace*> image_spaces =
+        Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    ASSERT_TRUE(!image_spaces.empty());
+    boot_image_location_ = image_spaces[0]->GetImageLocation();
   }
 
   virtual void SetUpRuntimeOptions(RuntimeOptions* options) OVERRIDE {
diff --git a/oatdump/Android.mk b/oatdump/Android.mk
index a3ef38d..5c75f20 100644
--- a/oatdump/Android.mk
+++ b/oatdump/Android.mk
@@ -74,14 +74,14 @@
 .PHONY: dump-oat-boot-$(TARGET_ARCH)
 ifeq ($(ART_BUILD_TARGET_NDEBUG),true)
 dump-oat-boot-$(TARGET_ARCH): $(DEFAULT_DEX_PREOPT_BUILT_IMAGE_FILENAME) $(OATDUMP)
-	$(OATDUMP) --image=$(DEFAULT_DEX_PREOPT_BUILT_IMAGE_LOCATION) \
+	$(OATDUMP) $(addprefix --image=,$(DEFAULT_DEX_PREOPT_BUILT_IMAGE_LOCATION)) \
 	  --output=$(ART_DUMP_OAT_PATH)/boot.$(TARGET_ARCH).oatdump.txt --instruction-set=$(TARGET_ARCH)
 	@echo Output in $(ART_DUMP_OAT_PATH)/boot.$(TARGET_ARCH).oatdump.txt
 endif
 
 ifdef TARGET_2ND_ARCH
 dump-oat-boot-$(TARGET_2ND_ARCH): $(2ND_DEFAULT_DEX_PREOPT_BUILT_IMAGE_FILENAME) $(OATDUMP)
-	$(OATDUMP) --image=$(2ND_DEFAULT_DEX_PREOPT_BUILT_IMAGE_LOCATION) \
+	$(OATDUMP) $(addprefix --image=,$(2ND_DEFAULT_DEX_PREOPT_BUILT_IMAGE_LOCATION)) \
 	  --output=$(ART_DUMP_OAT_PATH)/boot.$(TARGET_2ND_ARCH).oatdump.txt --instruction-set=$(TARGET_2ND_ARCH)
 	@echo Output in $(ART_DUMP_OAT_PATH)/boot.$(TARGET_2ND_ARCH).oatdump.txt
 endif
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index bad928e..52c6524 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1492,6 +1492,8 @@
 
     os << "MAGIC: " << image_header_.GetMagic() << "\n\n";
 
+    os << "IMAGE LOCATION: " << image_space_.GetImageLocation() << "\n\n";
+
     os << "IMAGE BEGIN: " << reinterpret_cast<void*>(image_header_.GetImageBegin()) << "\n\n";
 
     os << "IMAGE SIZE: " << image_header_.GetImageSize() << "\n\n";
@@ -1599,9 +1601,8 @@
 
     os << "OBJECTS:\n" << std::flush;
 
-    // Loop through all the image spaces and dump their objects.
+    // Loop through the image space and dump its objects.
     gc::Heap* heap = runtime->GetHeap();
-    const std::vector<gc::space::ContinuousSpace*>& spaces = heap->GetContinuousSpaces();
     Thread* self = Thread::Current();
     {
       {
@@ -1629,21 +1630,16 @@
         }
       }
       ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
-      for (const auto& space : spaces) {
-        if (space->IsImageSpace()) {
-          auto* image_space = space->AsImageSpace();
-          // Dump the normal objects before ArtMethods.
-          image_space->GetLiveBitmap()->Walk(ImageDumper::Callback, this);
-          indent_os << "\n";
-          // TODO: Dump fields.
-          // Dump methods after.
-          const auto& methods_section = image_header_.GetMethodsSection();
-          const size_t pointer_size =
-              InstructionSetPointerSize(oat_dumper_->GetOatInstructionSet());
-          DumpArtMethodVisitor visitor(this);
-          methods_section.VisitPackedArtMethods(&visitor, image_space->Begin(), pointer_size);
-        }
-      }
+      // Dump the normal objects before ArtMethods.
+      image_space_.GetLiveBitmap()->Walk(ImageDumper::Callback, this);
+      indent_os << "\n";
+      // TODO: Dump fields.
+      // Dump methods after.
+      const auto& methods_section = image_header_.GetMethodsSection();
+      const size_t pointer_size =
+          InstructionSetPointerSize(oat_dumper_->GetOatInstructionSet());
+      DumpArtMethodVisitor visitor(this);
+      methods_section.VisitPackedArtMethods(&visitor, image_space_.Begin(), pointer_size);
       // Dump the large objects separately.
       heap->GetLargeObjectsSpace()->GetLiveBitmap()->Walk(ImageDumper::Callback, this);
       indent_os << "\n";
@@ -2163,6 +2159,9 @@
       size_t sum_of_expansion = 0;
       size_t sum_of_expansion_squared = 0;
       size_t n = method_outlier_size.size();
+      if (n == 0) {
+        return;
+      }
       for (size_t i = 0; i < n; i++) {
         size_t cur_size = method_outlier_size[i];
         sum_of_sizes += cur_size;
@@ -2377,26 +2376,28 @@
   DISALLOW_COPY_AND_ASSIGN(ImageDumper);
 };
 
-static int DumpImage(Runtime* runtime, const char* image_location, OatDumperOptions* options,
-                     std::ostream* os) {
+static int DumpImage(Runtime* runtime, OatDumperOptions* options, std::ostream* os) {
   // Dumping the image, no explicit class loader.
   ScopedNullHandle<mirror::ClassLoader> null_class_loader;
   options->class_loader_ = &null_class_loader;
 
   ScopedObjectAccess soa(Thread::Current());
   gc::Heap* heap = runtime->GetHeap();
-  gc::space::ImageSpace* image_space = heap->GetBootImageSpace();
-  CHECK(image_space != nullptr);
-  const ImageHeader& image_header = image_space->GetImageHeader();
-  if (!image_header.IsValid()) {
-    fprintf(stderr, "Invalid image header %s\n", image_location);
-    return EXIT_FAILURE;
+  std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
+  CHECK(!image_spaces.empty());
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    const ImageHeader& image_header = image_space->GetImageHeader();
+    if (!image_header.IsValid()) {
+      fprintf(stderr, "Invalid image header %s\n", image_space->GetImageLocation().c_str());
+      return EXIT_FAILURE;
+    }
+
+    ImageDumper image_dumper(os, *image_space, image_header, options);
+    if (!image_dumper.Dump()) {
+      return EXIT_FAILURE;
+    }
   }
-
-  ImageDumper image_dumper(os, *image_space, image_header, options);
-
-  bool success = image_dumper.Dump();
-  return (success) ? EXIT_SUCCESS : EXIT_FAILURE;
+  return EXIT_SUCCESS;
 }
 
 static int DumpOatWithRuntime(Runtime* runtime, OatFile* oat_file, OatDumperOptions* options,
@@ -2689,8 +2690,7 @@
                      args_->os_) == EXIT_SUCCESS;
     }
 
-    return DumpImage(runtime, args_->image_location_, oat_dumper_options_.get(), args_->os_)
-      == EXIT_SUCCESS;
+    return DumpImage(runtime, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS;
   }
 
   std::unique_ptr<OatDumperOptions> oat_dumper_options_;
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 46ab34b..b403abd 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -118,6 +118,38 @@
   return true;
 }
 
+static File* CreateOrOpen(const char* name, bool* created) {
+  if (OS::FileExists(name)) {
+    *created = false;
+    return OS::OpenFileReadWrite(name);
+  } else {
+    *created = true;
+    std::unique_ptr<File> f(OS::CreateEmptyFile(name));
+    if (f.get() != nullptr) {
+      if (fchmod(f->Fd(), 0644) != 0) {
+        PLOG(ERROR) << "Unable to make " << name << " world readable";
+        TEMP_FAILURE_RETRY(unlink(name));
+        return nullptr;
+      }
+    }
+    return f.release();
+  }
+}
+
+// Either try to close the file (close=true), or erase it.
+static bool FinishFile(File* file, bool close) {
+  if (close) {
+    if (file->FlushCloseOrErase() != 0) {
+      PLOG(ERROR) << "Failed to flush and close file.";
+      return false;
+    }
+    return true;
+  } else {
+    file->Erase();
+    return false;
+  }
+}
+
 bool PatchOat::Patch(const std::string& image_location, off_t delta,
                      File* output_image, InstructionSet isa,
                      TimingLogger* timings) {
@@ -195,11 +227,12 @@
     LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg;
     return false;
   }
-  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace();
+  // TODO: Support multi-image when patchoat is only patching images. Ever used? b/26317072
+  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpaces()[0];
 
   PatchOat p(isa, image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), delta, timings);
   t.NewTiming("Patching files");
-  if (!p.PatchImage()) {
+  if (!p.PatchImage(true)) {
     LOG(ERROR) << "Failed to patch image file " << input_image->GetPath();
     return false;
   }
@@ -214,7 +247,7 @@
 bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t delta,
                      File* output_oat, File* output_image, InstructionSet isa,
                      TimingLogger* timings,
-                     bool output_oat_opened_from_fd,
+                     bool output_oat_opened_from_fd ATTRIBUTE_UNUSED,
                      bool new_oat_out) {
   CHECK(Runtime::Current() == nullptr);
   CHECK(output_image != nullptr);
@@ -236,31 +269,6 @@
     isa = GetInstructionSetFromELF(elf_hdr.e_machine, elf_hdr.e_flags);
   }
   const char* isa_name = GetInstructionSetString(isa);
-  std::string image_filename;
-  if (!LocationToFilename(image_location, isa, &image_filename)) {
-    LOG(ERROR) << "Unable to find image at location " << image_location;
-    return false;
-  }
-  std::unique_ptr<File> input_image(OS::OpenFileForReading(image_filename.c_str()));
-  if (input_image.get() == nullptr) {
-    LOG(ERROR) << "unable to open input image file at " << image_filename
-               << " for location " << image_location;
-    return false;
-  }
-  int64_t image_len = input_image->GetLength();
-  if (image_len < 0) {
-    LOG(ERROR) << "Error while getting image length";
-    return false;
-  }
-  ImageHeader image_header;
-  if (sizeof(image_header) != input_image->Read(reinterpret_cast<char*>(&image_header),
-                                              sizeof(image_header), 0)) {
-    LOG(ERROR) << "Unable to read image header from image file " << input_image->GetPath();
-  }
-
-  /*bool is_image_pic = */IsImagePic(image_header, input_image->GetPath());
-  // Nothing special to do right now since the image always needs to get patched.
-  // Perhaps in some far-off future we may have images with relative addresses that are true-PIC.
 
   // Set up the runtime
   RuntimeOptions options;
@@ -279,70 +287,169 @@
   Thread::Current()->TransitionFromRunnableToSuspended(kNative);
   ScopedObjectAccess soa(Thread::Current());
 
+  std::string output_directory =
+      output_image->GetPath().substr(0, output_image->GetPath().find_last_of("/"));
   t.NewTiming("Image and oat Patching setup");
-  // Create the map where we will write the image patches to.
-  std::string error_msg;
-  std::unique_ptr<MemMap> image(MemMap::MapFile(image_len,
-                                                PROT_READ | PROT_WRITE,
-                                                MAP_PRIVATE,
-                                                input_image->Fd(),
-                                                0,
-                                                /*low_4gb*/false,
-                                                input_image->GetPath().c_str(),
-                                                &error_msg));
-  if (image.get() == nullptr) {
-    LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg;
-    return false;
-  }
-  gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace();
+  std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  std::map<gc::space::ImageSpace*, std::unique_ptr<File>> space_to_file_map;
+  std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>> space_to_memmap_map;
+  std::map<gc::space::ImageSpace*, PatchOat> space_to_patchoat_map;
+  std::map<gc::space::ImageSpace*, bool> space_to_skip_patching_map;
 
-  std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat,
-                                             PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg));
-  if (elf.get() == nullptr) {
-    LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg;
-    return false;
-  }
-
-  bool skip_patching_oat = false;
-  MaybePic is_oat_pic = IsOatPic(elf.get());
-  if (is_oat_pic >= ERROR_FIRST) {
-    // Error logged by IsOatPic
-    return false;
-  } else if (is_oat_pic == PIC) {
-    // Do not need to do ELF-file patching. Create a symlink and skip the ELF patching.
-    if (!ReplaceOatFileWithSymlink(input_oat->GetPath(),
-                                   output_oat->GetPath(),
-                                   output_oat_opened_from_fd,
-                                   new_oat_out)) {
-      // Errors already logged by above call.
+  for (size_t i = 0; i < spaces.size(); ++i) {
+    gc::space::ImageSpace* space = spaces[i];
+    std::string input_image_filename = space->GetImageFilename();
+    std::unique_ptr<File> input_image(OS::OpenFileForReading(input_image_filename.c_str()));
+    if (input_image.get() == nullptr) {
+      LOG(ERROR) << "Unable to open input image file at " << input_image_filename;
       return false;
     }
-    // Don't patch the OAT, since we just symlinked it. Image still needs patching.
-    skip_patching_oat = true;
-  } else {
-    CHECK(is_oat_pic == NOT_PIC);
+
+    int64_t image_len = input_image->GetLength();
+    if (image_len < 0) {
+      LOG(ERROR) << "Error while getting image length";
+      return false;
+    }
+    ImageHeader image_header;
+    if (sizeof(image_header) != input_image->Read(reinterpret_cast<char*>(&image_header),
+                                                  sizeof(image_header), 0)) {
+      LOG(ERROR) << "Unable to read image header from image file " << input_image->GetPath();
+    }
+
+    /*bool is_image_pic = */IsImagePic(image_header, input_image->GetPath());
+    // Nothing special to do right now since the image always needs to get patched.
+    // Perhaps in some far-off future we may have images with relative addresses that are true-PIC.
+
+    // Create the map where we will write the image patches to.
+    std::string error_msg;
+    std::unique_ptr<MemMap> image(MemMap::MapFile(image_len,
+                                                  PROT_READ | PROT_WRITE,
+                                                  MAP_PRIVATE,
+                                                  input_image->Fd(),
+                                                  0,
+                                                  /*low_4gb*/false,
+                                                  input_image->GetPath().c_str(),
+                                                  &error_msg));
+    if (image.get() == nullptr) {
+      LOG(ERROR) << "Unable to map image file " << input_image->GetPath() << " : " << error_msg;
+      return false;
+    }
+    space_to_file_map.emplace(space, std::move(input_image));
+    space_to_memmap_map.emplace(space, std::move(image));
   }
 
-  PatchOat p(isa, elf.release(), image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(),
-             delta, timings);
-  t.NewTiming("Patching files");
-  if (!skip_patching_oat && !p.PatchElf()) {
-    LOG(ERROR) << "Failed to patch oat file " << input_oat->GetPath();
-    return false;
-  }
-  if (!p.PatchImage()) {
-    LOG(ERROR) << "Failed to patch image file " << input_image->GetPath();
-    return false;
+  for (size_t i = 0; i < spaces.size(); ++i) {
+    gc::space::ImageSpace* space = spaces[i];
+    std::string input_image_filename = space->GetImageFilename();
+    std::string input_oat_filename =
+        ImageHeader::GetOatLocationFromImageLocation(input_image_filename);
+    std::unique_ptr<File> input_oat_file(OS::OpenFileForReading(input_oat_filename.c_str()));
+    if (input_oat_file.get() == nullptr) {
+      LOG(ERROR) << "Unable to open input oat file at " << input_oat_filename;
+      return false;
+    }
+    std::string error_msg;
+    std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat_file.get(),
+                                               PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg));
+    if (elf.get() == nullptr) {
+      LOG(ERROR) << "Unable to open oat file " << input_oat_file->GetPath() << " : " << error_msg;
+      return false;
+    }
+
+    bool skip_patching_oat = false;
+    MaybePic is_oat_pic = IsOatPic(elf.get());
+    if (is_oat_pic >= ERROR_FIRST) {
+      // Error logged by IsOatPic
+      return false;
+    } else if (is_oat_pic == PIC) {
+      // Do not need to do ELF-file patching. Create a symlink and skip the ELF patching.
+
+      std::string converted_image_filename = space->GetImageLocation();
+      std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
+      std::string output_image_filename = output_directory +
+                                          (StartsWith(converted_image_filename, "/") ? "" : "/") +
+                                          converted_image_filename;
+      std::string output_oat_filename =
+          ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
+
+      if (!ReplaceOatFileWithSymlink(input_oat_file->GetPath(),
+                                     output_oat_filename,
+                                     false,
+                                     true)) {
+        // Errors already logged by above call.
+        return false;
+      }
+      // Don't patch the OAT, since we just symlinked it. Image still needs patching.
+      skip_patching_oat = true;
+    } else {
+      CHECK(is_oat_pic == NOT_PIC);
+    }
+
+    PatchOat& p = space_to_patchoat_map.emplace(space,
+                                                PatchOat(
+                                                    isa,
+                                                    elf.release(),
+                                                    space_to_memmap_map.find(space)->second.get(),
+                                                    space->GetLiveBitmap(),
+                                                    space->GetMemMap(),
+                                                    delta,
+                                                    &space_to_memmap_map,
+                                                    timings)).first->second;
+
+    t.NewTiming("Patching files");
+    if (!skip_patching_oat && !p.PatchElf()) {
+      LOG(ERROR) << "Failed to patch oat file " << input_oat_file->GetPath();
+      return false;
+    }
+    if (!p.PatchImage(i == 0)) {
+      LOG(ERROR) << "Failed to patch image file " << input_image_filename;
+      return false;
+    }
+
+    space_to_skip_patching_map.emplace(space, skip_patching_oat);
   }
 
-  t.NewTiming("Writing files");
-  if (!skip_patching_oat && !p.WriteElf(output_oat)) {
-    LOG(ERROR) << "Failed to write oat file " << input_oat->GetPath();
-    return false;
-  }
-  if (!p.WriteImage(output_image)) {
-    LOG(ERROR) << "Failed to write image file " << input_image->GetPath();
-    return false;
+  for (size_t i = 0; i < spaces.size(); ++i) {
+    gc::space::ImageSpace* space = spaces[i];
+    std::string input_image_filename = space->GetImageFilename();
+
+    t.NewTiming("Writing files");
+    std::string converted_image_filename = space->GetImageLocation();
+    std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
+    std::string output_image_filename = output_directory +
+                                        (StartsWith(converted_image_filename, "/") ? "" : "/") +
+                                        converted_image_filename;
+    std::unique_ptr<File>
+        output_image_file(CreateOrOpen(output_image_filename.c_str(), &new_oat_out));
+    if (output_image_file.get() == nullptr) {
+      LOG(ERROR) << "Failed to open output image file at " << output_image_filename;
+      return false;
+    }
+
+    PatchOat& p = space_to_patchoat_map.find(space)->second;
+
+    if (!p.WriteImage(output_image_file.get())) {
+      LOG(ERROR) << "Failed to write image file " << output_image_file->GetPath();
+      return false;
+    }
+    FinishFile(output_image_file.get(), true);
+
+    bool skip_patching_oat = space_to_skip_patching_map.find(space)->second;
+    if (!skip_patching_oat) {
+      std::string output_oat_filename =
+          ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
+      std::unique_ptr<File>
+          output_oat_file(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out));
+      if (output_oat_file.get() == nullptr) {
+        LOG(ERROR) << "Failed to open output oat file at " << output_oat_filename;
+        return false;
+      }
+      if (!p.WriteElf(output_oat_file.get())) {
+        LOG(ERROR) << "Failed to write oat file " << output_oat_file->GetPath();
+        return false;
+      }
+      FinishFile(output_oat_file.get(), true);
+    }
   }
   return true;
 }
@@ -616,7 +723,7 @@
   }
 }
 
-bool PatchOat::PatchImage() {
+bool PatchOat::PatchImage(bool primary_image) {
   ImageHeader* image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
   CHECK_GT(image_->Size(), sizeof(ImageHeader));
   // These are the roots from the original file.
@@ -630,9 +737,12 @@
   // Patch dex file int/long arrays which point to ArtFields.
   PatchDexFileArrays(img_roots);
 
-  VisitObject(img_roots);
+  if (primary_image) {
+    VisitObject(img_roots);
+  }
+
   if (!image_header->IsValid()) {
-    LOG(ERROR) << "reloction renders image header invalid";
+    LOG(ERROR) << "relocation renders image header invalid";
     return false;
   }
 
@@ -655,7 +765,8 @@
 void PatchOat::PatchVisitor::operator() (mirror::Object* obj, MemberOffset off,
                                          bool is_static_unused ATTRIBUTE_UNUSED) const {
   mirror::Object* referent = obj->GetFieldObject<mirror::Object, kVerifyNone>(off);
-  DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap.";
+  // TODO: Modify check for multi-image support? b/26317072
+  // DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap.";
   mirror::Object* moved_object = patcher_->RelocatedAddressOfPointer(referent);
   copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(off, moved_object);
 }
@@ -664,7 +775,8 @@
                                          mirror::Reference* ref) const {
   MemberOffset off = mirror::Reference::ReferentOffset();
   mirror::Object* referent = ref->GetReferent();
-  DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap.";
+  // TODO: Modify check for multi-image support? b/26317072
+  // DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap.";
   mirror::Object* moved_object = patcher_->RelocatedAddressOfPointer(referent);
   copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(off, moved_object);
 }
@@ -691,7 +803,7 @@
     klass->FixupNativePointers(copy_klass, pointer_size, native_visitor);
     auto* vtable = klass->GetVTable();
     if (vtable != nullptr) {
-      vtable->Fixup(RelocatedCopyOf(vtable), pointer_size, native_visitor);
+      vtable->Fixup(RelocatedCopyOfFollowImages(vtable), pointer_size, native_visitor);
     }
     auto* iftable = klass->GetIfTable();
     if (iftable != nullptr) {
@@ -699,7 +811,9 @@
         if (iftable->GetMethodArrayCount(i) > 0) {
           auto* method_array = iftable->GetMethodArray(i);
           CHECK(method_array != nullptr);
-          method_array->Fixup(RelocatedCopyOf(method_array), pointer_size, native_visitor);
+          method_array->Fixup(RelocatedCopyOfFollowImages(method_array),
+                              pointer_size,
+                              native_visitor);
         }
       }
     }
@@ -972,38 +1086,6 @@
   return true;
 }
 
-static File* CreateOrOpen(const char* name, bool* created) {
-  if (OS::FileExists(name)) {
-    *created = false;
-    return OS::OpenFileReadWrite(name);
-  } else {
-    *created = true;
-    std::unique_ptr<File> f(OS::CreateEmptyFile(name));
-    if (f.get() != nullptr) {
-      if (fchmod(f->Fd(), 0644) != 0) {
-        PLOG(ERROR) << "Unable to make " << name << " world readable";
-        TEMP_FAILURE_RETRY(unlink(name));
-        return nullptr;
-      }
-    }
-    return f.release();
-  }
-}
-
-// Either try to close the file (close=true), or erase it.
-static bool FinishFile(File* file, bool close) {
-  if (close) {
-    if (file->FlushCloseOrErase() != 0) {
-      PLOG(ERROR) << "Failed to flush and close file.";
-      return false;
-    }
-    return true;
-  } else {
-    file->Erase();
-    return false;
-  }
-}
-
 static int patchoat(int argc, char **argv) {
   InitLogging(argv);
   MemMap::Init();
diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h
index 38bd865..cb0d14b 100644
--- a/patchoat/patchoat.h
+++ b/patchoat/patchoat.h
@@ -23,8 +23,10 @@
 #include "elf_file.h"
 #include "elf_utils.h"
 #include "gc/accounting/space_bitmap.h"
+#include "gc/space/image_space.h"
 #include "gc/heap.h"
 #include "os.h"
+#include "runtime.h"
 
 namespace art {
 
@@ -57,21 +59,23 @@
                     bool output_oat_opened_from_fd,  // Was this using --oatput-oat-fd ?
                     bool new_oat_out);               // Output oat was a new file created by us?
 
+  ~PatchOat() {}
+  PatchOat(PatchOat&&) = default;
+
  private:
   // Takes ownership only of the ElfFile. All other pointers are only borrowed.
   PatchOat(ElfFile* oat_file, off_t delta, TimingLogger* timings)
       : oat_file_(oat_file), image_(nullptr), bitmap_(nullptr), heap_(nullptr), delta_(delta),
-        isa_(kNone), timings_(timings) {}
+        isa_(kNone), space_map_(nullptr), timings_(timings) {}
   PatchOat(InstructionSet isa, MemMap* image, gc::accounting::ContinuousSpaceBitmap* bitmap,
            MemMap* heap, off_t delta, TimingLogger* timings)
       : image_(image), bitmap_(bitmap), heap_(heap),
-        delta_(delta), isa_(isa), timings_(timings) {}
+        delta_(delta), isa_(isa), space_map_(nullptr), timings_(timings) {}
   PatchOat(InstructionSet isa, ElfFile* oat_file, MemMap* image,
            gc::accounting::ContinuousSpaceBitmap* bitmap, MemMap* heap, off_t delta,
-           TimingLogger* timings)
+           std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* map, TimingLogger* timings)
       : oat_file_(oat_file), image_(image), bitmap_(bitmap), heap_(heap),
-        delta_(delta), isa_(isa), timings_(timings) {}
-  ~PatchOat() {}
+        delta_(delta), isa_(isa), space_map_(map), timings_(timings) {}
 
   // Was the .art image at image_path made with --compile-pic ?
   static bool IsImagePic(const ImageHeader& image_header, const std::string& image_path);
@@ -111,7 +115,7 @@
   template <typename ElfFileImpl>
   bool PatchOatHeader(ElfFileImpl* oat_file);
 
-  bool PatchImage() SHARED_REQUIRES(Locks::mutator_lock_);
+  bool PatchImage(bool primary_image) SHARED_REQUIRES(Locks::mutator_lock_);
   void PatchArtFields(const ImageHeader* image_header) SHARED_REQUIRES(Locks::mutator_lock_);
   void PatchArtMethods(const ImageHeader* image_header) SHARED_REQUIRES(Locks::mutator_lock_);
   void PatchInternedStrings(const ImageHeader* image_header)
@@ -129,15 +133,34 @@
     if (obj == nullptr) {
       return nullptr;
     }
-    DCHECK_GT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->Begin()));
-    DCHECK_LT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->End()));
+    // TODO: Fix these checks for multi-image. Some may still be valid. b/26317072
+    // DCHECK_GT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->Begin()));
+    // DCHECK_LT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->End()));
     uintptr_t heap_off =
         reinterpret_cast<uintptr_t>(obj) - reinterpret_cast<uintptr_t>(heap_->Begin());
-    DCHECK_LT(heap_off, image_->Size());
+    // DCHECK_LT(heap_off, image_->Size());
     return reinterpret_cast<T*>(image_->Begin() + heap_off);
   }
 
   template <typename T>
+  T* RelocatedCopyOfFollowImages(T* obj) const {
+    if (obj == nullptr) {
+      return nullptr;
+    }
+    // Find ImageSpace this belongs to.
+    auto image_spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    for (gc::space::ImageSpace* image_space : image_spaces) {
+      if (image_space->Contains(obj)) {
+        uintptr_t heap_off = reinterpret_cast<uintptr_t>(obj) -
+                             reinterpret_cast<uintptr_t>(image_space->GetMemMap()->Begin());
+        return reinterpret_cast<T*>(space_map_->find(image_space)->second->Begin() + heap_off);
+      }
+    }
+    LOG(FATAL) << "Did not find object in boot image space " << obj;
+    UNREACHABLE();
+  }
+
+  template <typename T>
   T* RelocatedAddressOfPointer(T* obj) const {
     if (obj == nullptr) {
       return obj;
@@ -197,6 +220,8 @@
   // Active instruction set, used to know the entrypoint size.
   const InstructionSet isa_;
 
+  const std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* space_map_;
+
   TimingLogger* timings_;
 
   friend class FixupRootVisitor;
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 342e1d9..0518911 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -330,7 +330,7 @@
   Runtime* const runtime = Runtime::Current();
   gc::Heap* const heap = runtime->GetHeap();
 
-  CHECK(!heap->HasImageSpace()) << "Runtime has image. We should use it.";
+  CHECK(!heap->HasBootImageSpace()) << "Runtime has image. We should use it.";
   CHECK(!init_done_);
 
   // Use the pointer size from the runtime since we are probably creating the image.
@@ -736,7 +736,7 @@
 
 static void SanityCheckArtMethod(ArtMethod* m,
                                  mirror::Class* expected_class,
-                                 gc::space::ImageSpace* space)
+                                 std::vector<gc::space::ImageSpace*> spaces)
     SHARED_REQUIRES(Locks::mutator_lock_) {
   if (m->IsRuntimeMethod()) {
     CHECK(m->GetDeclaringClass() == nullptr) << PrettyMethod(m);
@@ -745,18 +745,22 @@
   } else if (expected_class != nullptr) {
     CHECK_EQ(m->GetDeclaringClassUnchecked(), expected_class) << PrettyMethod(m);
   }
-  if (space != nullptr) {
-    auto& header = space->GetImageHeader();
-    auto& methods = header.GetMethodsSection();
-    auto offset = reinterpret_cast<uint8_t*>(m) - space->Begin();
-    CHECK(methods.Contains(offset)) << m << " not in " << methods;
+  if (!spaces.empty()) {
+    bool contains = false;
+    for (gc::space::ImageSpace* space : spaces) {
+      auto& header = space->GetImageHeader();
+      auto& methods = header.GetMethodsSection();
+      auto offset = reinterpret_cast<uint8_t*>(m) - space->Begin();
+      contains |= methods.Contains(offset);
+    }
+    CHECK(contains) << m << " not found";
   }
 }
 
 static void SanityCheckArtMethodPointerArray(mirror::PointerArray* arr,
                                              mirror::Class* expected_class,
                                              size_t pointer_size,
-                                             gc::space::ImageSpace* space)
+                                             std::vector<gc::space::ImageSpace*> spaces)
     SHARED_REQUIRES(Locks::mutator_lock_) {
   CHECK(arr != nullptr);
   for (int32_t j = 0; j < arr->GetLength(); ++j) {
@@ -766,11 +770,12 @@
       CHECK(method != nullptr);
     }
     if (method != nullptr) {
-      SanityCheckArtMethod(method, expected_class, space);
+      SanityCheckArtMethod(method, expected_class, spaces);
     }
   }
 }
 
+/* TODO: Modify check to support multiple image spaces and reenable. b/26317072
 static void SanityCheckArtMethodPointerArray(
     ArtMethod** arr,
     size_t size,
@@ -790,6 +795,7 @@
     }
   }
 }
+*/
 
 static void SanityCheckObjectsCallback(mirror::Object* obj, void* arg ATTRIBUTE_UNUSED)
     SHARED_REQUIRES(Locks::mutator_lock_) {
@@ -805,29 +811,30 @@
       CHECK_EQ(field.GetDeclaringClass(), klass);
     }
     auto* runtime = Runtime::Current();
-    auto* image_space = runtime->GetHeap()->GetBootImageSpace();
+    auto image_spaces = runtime->GetHeap()->GetBootImageSpaces();
     auto pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
     for (auto& m : klass->GetMethods(pointer_size)) {
-      SanityCheckArtMethod(&m, klass, image_space);
+      SanityCheckArtMethod(&m, klass, image_spaces);
     }
     auto* vtable = klass->GetVTable();
     if (vtable != nullptr) {
-      SanityCheckArtMethodPointerArray(vtable, nullptr, pointer_size, image_space);
+      SanityCheckArtMethodPointerArray(vtable, nullptr, pointer_size, image_spaces);
     }
     if (klass->ShouldHaveEmbeddedImtAndVTable()) {
       for (size_t i = 0; i < mirror::Class::kImtSize; ++i) {
-        SanityCheckArtMethod(klass->GetEmbeddedImTableEntry(i, pointer_size), nullptr, image_space);
+        SanityCheckArtMethod(
+            klass->GetEmbeddedImTableEntry(i, pointer_size), nullptr, image_spaces);
       }
       for (int32_t i = 0; i < klass->GetEmbeddedVTableLength(); ++i) {
-        SanityCheckArtMethod(klass->GetEmbeddedVTableEntry(i, pointer_size), nullptr, image_space);
+        SanityCheckArtMethod(klass->GetEmbeddedVTableEntry(i, pointer_size), nullptr, image_spaces);
       }
     }
     auto* iftable = klass->GetIfTable();
     if (iftable != nullptr) {
       for (int32_t i = 0; i < klass->GetIfTableCount(); ++i) {
         if (iftable->GetMethodArrayCount(i) > 0) {
-          SanityCheckArtMethodPointerArray(iftable->GetMethodArray(i), nullptr, pointer_size,
-                                           image_space);
+          SanityCheckArtMethodPointerArray(
+              iftable->GetMethodArray(i), nullptr, pointer_size, image_spaces);
         }
       }
     }
@@ -856,6 +863,33 @@
   DISALLOW_COPY_AND_ASSIGN(SetInterpreterEntrypointArtMethodVisitor);
 };
 
+struct TrampolineCheckData {
+  const void* quick_resolution_trampoline;
+  const void* quick_imt_conflict_trampoline;
+  const void* quick_generic_jni_trampoline;
+  const void* quick_to_interpreter_bridge_trampoline;
+  size_t pointer_size;
+  ArtMethod* m;
+  bool error;
+};
+static void CheckTrampolines(mirror::Object* obj, void* arg) NO_THREAD_SAFETY_ANALYSIS {
+  if (obj->IsClass()) {
+    mirror::Class* klass = obj->AsClass();
+    TrampolineCheckData* d = reinterpret_cast<TrampolineCheckData*>(arg);
+    for (ArtMethod& m : klass->GetMethods(d->pointer_size)) {
+      const void* entrypoint = m.GetEntryPointFromQuickCompiledCodePtrSize(d->pointer_size);
+      if (entrypoint == d->quick_resolution_trampoline ||
+          entrypoint == d->quick_imt_conflict_trampoline ||
+          entrypoint == d->quick_generic_jni_trampoline ||
+          entrypoint == d->quick_to_interpreter_bridge_trampoline) {
+        d->m = &m;
+        d->error = true;
+        return;
+      }
+    }
+  }
+}
+
 bool ClassLinker::InitFromImage(std::string* error_msg) {
   VLOG(startup) << "ClassLinker::InitFromImage entering";
   CHECK(!init_done_);
@@ -863,28 +897,71 @@
   Runtime* const runtime = Runtime::Current();
   Thread* const self = Thread::Current();
   gc::Heap* const heap = runtime->GetHeap();
-  gc::space::ImageSpace* const space = heap->GetBootImageSpace();
-  CHECK(space != nullptr);
-  image_pointer_size_ = space->GetImageHeader().GetPointerSize();
+  std::vector<gc::space::ImageSpace*> spaces = heap->GetBootImageSpaces();
+  CHECK(!spaces.empty());
+  image_pointer_size_ = spaces[0]->GetImageHeader().GetPointerSize();
   dex_cache_boot_image_class_lookup_required_ = true;
-  const OatFile* oat_file = runtime->GetOatFileManager().RegisterImageOatFile(space);
-  DCHECK(oat_file != nullptr);
-  CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatChecksum(), 0U);
-  CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatDataBegin(), 0U);
-  const char* image_file_location = oat_file->GetOatHeader().
+  std::vector<const OatFile*> oat_files =
+      runtime->GetOatFileManager().RegisterImageOatFiles(spaces);
+  DCHECK(!oat_files.empty());
+  const OatHeader& default_oat_header = oat_files[0]->GetOatHeader();
+  CHECK_EQ(default_oat_header.GetImageFileLocationOatChecksum(), 0U);
+  CHECK_EQ(default_oat_header.GetImageFileLocationOatDataBegin(), 0U);
+  const char* image_file_location = oat_files[0]->GetOatHeader().
       GetStoreValueByKey(OatHeader::kImageLocationKey);
   CHECK(image_file_location == nullptr || *image_file_location == 0);
-  quick_resolution_trampoline_ = oat_file->GetOatHeader().GetQuickResolutionTrampoline();
-  quick_imt_conflict_trampoline_ = oat_file->GetOatHeader().GetQuickImtConflictTrampoline();
-  quick_generic_jni_trampoline_ = oat_file->GetOatHeader().GetQuickGenericJniTrampoline();
-  quick_to_interpreter_bridge_trampoline_ = oat_file->GetOatHeader().GetQuickToInterpreterBridge();
-  StackHandleScope<2> hs(self);
-  mirror::Object* dex_caches_object = space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(
-      hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
+  quick_resolution_trampoline_ = default_oat_header.GetQuickResolutionTrampoline();
+  quick_imt_conflict_trampoline_ = default_oat_header.GetQuickImtConflictTrampoline();
+  quick_generic_jni_trampoline_ = default_oat_header.GetQuickGenericJniTrampoline();
+  quick_to_interpreter_bridge_trampoline_ = default_oat_header.GetQuickToInterpreterBridge();
+  if (kIsDebugBuild) {
+    // Check that the other images use the same trampoline.
+    for (size_t i = 1; i < oat_files.size(); ++i) {
+      const OatHeader& ith_oat_header = oat_files[i]->GetOatHeader();
+      const void* ith_quick_resolution_trampoline =
+          ith_oat_header.GetQuickResolutionTrampoline();
+      const void* ith_quick_imt_conflict_trampoline =
+          ith_oat_header.GetQuickImtConflictTrampoline();
+      const void* ith_quick_generic_jni_trampoline =
+          ith_oat_header.GetQuickGenericJniTrampoline();
+      const void* ith_quick_to_interpreter_bridge_trampoline =
+          ith_oat_header.GetQuickToInterpreterBridge();
+      if (ith_quick_resolution_trampoline != quick_resolution_trampoline_ ||
+          ith_quick_imt_conflict_trampoline != quick_imt_conflict_trampoline_ ||
+          ith_quick_generic_jni_trampoline != quick_generic_jni_trampoline_ ||
+          ith_quick_to_interpreter_bridge_trampoline != quick_to_interpreter_bridge_trampoline_) {
+        // Make sure that all methods in this image do not contain those trampolines as
+        // entrypoints. Otherwise the class-linker won't be able to work with a single set.
+        TrampolineCheckData data;
+        data.error = false;
+        data.pointer_size = GetImagePointerSize();
+        data.quick_resolution_trampoline = ith_quick_resolution_trampoline;
+        data.quick_imt_conflict_trampoline = ith_quick_imt_conflict_trampoline;
+        data.quick_generic_jni_trampoline = ith_quick_generic_jni_trampoline;
+        data.quick_to_interpreter_bridge_trampoline = ith_quick_to_interpreter_bridge_trampoline;
+        ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
+        spaces[i]->GetLiveBitmap()->Walk(CheckTrampolines, &data);
+        if (data.error) {
+          ArtMethod* m = data.m;
+          LOG(ERROR) << "Found a broken ArtMethod: " << PrettyMethod(m);
+          *error_msg = "Found an ArtMethod with a bad entrypoint";
+          return false;
+        }
+      }
+    }
+  }
 
-  Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
-      space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
+  StackHandleScopeCollection handles(self);
+  std::vector<Handle<mirror::ObjectArray<mirror::DexCache>>> dex_caches_vector;
+  for (gc::space::ImageSpace* space : spaces) {
+    Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(handles.NewHandle(
+        space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches)->
+        AsObjectArray<mirror::DexCache>()));
+    dex_caches_vector.push_back(dex_caches);
+  }
+
+  Handle<mirror::ObjectArray<mirror::Class>> class_roots(handles.NewHandle(
+      spaces[0]->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
       AsObjectArray<mirror::Class>()));
   class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(class_roots.Get());
 
@@ -896,56 +973,70 @@
   java_lang_Object->SetObjectSize(sizeof(mirror::Object));
   // Allocate in non-movable so that it's possible to check if a JNI weak global ref has been
   // cleared without triggering the read barrier and unintentionally mark the sentinel alive.
-  runtime->SetSentinel(heap->AllocNonMovableObject<true>(self,
-                                                         java_lang_Object,
-                                                         java_lang_Object->GetObjectSize(),
-                                                         VoidFunctor()));
+  runtime->SetSentinel(heap->AllocNonMovableObject<true>(
+      self, java_lang_Object, java_lang_Object->GetObjectSize(), VoidFunctor()));
 
-  if (oat_file->GetOatHeader().GetDexFileCount() !=
-      static_cast<uint32_t>(dex_caches->GetLength())) {
+  uint32_t dex_file_count = 0;
+  for (const OatFile* oat_file : oat_files) {
+    dex_file_count += oat_file->GetOatHeader().GetDexFileCount();
+  }
+  uint32_t dex_caches_count = 0;
+  for (auto dex_caches : dex_caches_vector) {
+    dex_caches_count += dex_caches->GetLength();
+  }
+  if (dex_file_count != dex_caches_count) {
     *error_msg = "Dex cache count and dex file count mismatch while trying to initialize from "
                  "image";
     return false;
   }
-  for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-    StackHandleScope<1> hs2(self);
-    Handle<mirror::DexCache> dex_cache(hs2.NewHandle(dex_caches->Get(i)));
-    const std::string& dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8());
-    const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file_location.c_str(),
-                                                                      nullptr);
-    if (oat_dex_file == nullptr) {
-      *error_msg = StringPrintf("Failed finding oat dex file for %s %s",
-                                oat_file->GetLocation().c_str(),
-                                dex_file_location.c_str());
-      return false;
-    }
-    std::string inner_error_msg;
-    std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&inner_error_msg);
-    if (dex_file == nullptr) {
-      *error_msg = StringPrintf("Failed to open dex file %s from within oat file %s error '%s'",
-                                dex_file_location.c_str(),
-                                oat_file->GetLocation().c_str(),
-                                inner_error_msg.c_str());
-      return false;
-    }
+  for (auto dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+      StackHandleScope<1> hs2(self);
+      Handle<mirror::DexCache> dex_cache(hs2.NewHandle(dex_caches->Get(i)));
+      const std::string& dex_file_location(dex_cache->GetLocation()->ToModifiedUtf8());
+      const OatFile::OatDexFile* oat_dex_file = nullptr;
+      for (const OatFile* oat_file : oat_files) {
+        const OatFile::OatDexFile* oat_dex =
+            oat_file->GetOatDexFile(dex_file_location.c_str(), nullptr, false);
+        if (oat_dex != nullptr) {
+          DCHECK(oat_dex_file == nullptr);
+          oat_dex_file = oat_dex;
+        }
+      }
 
-    if (kSanityCheckObjects) {
-      SanityCheckArtMethodPointerArray(dex_cache->GetResolvedMethods(),
-                                       dex_cache->NumResolvedMethods(),
-                                       image_pointer_size_,
-                                       space);
-    }
+      if (oat_dex_file == nullptr) {
+        *error_msg = StringPrintf("Failed finding oat dex file for %s",
+                                  dex_file_location.c_str());
+        return false;
+      }
+      std::string inner_error_msg;
+      std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&inner_error_msg);
+      if (dex_file == nullptr) {
+        *error_msg = StringPrintf("Failed to open dex file %s error '%s'",
+                                  dex_file_location.c_str(),
+                                  inner_error_msg.c_str());
+        return false;
+      }
 
-    if (dex_file->GetLocationChecksum() != oat_dex_file->GetDexFileLocationChecksum()) {
-      *error_msg = StringPrintf("Checksums do not match for %s: %x vs %x",
-                                dex_file_location.c_str(),
-                                dex_file->GetLocationChecksum(),
-                                oat_dex_file->GetDexFileLocationChecksum());
-      return false;
-    }
+      // TODO: Modify check to support multiple image spaces and reenable.
+//      if (kSanityCheckObjects) {
+//        SanityCheckArtMethodPointerArray(dex_cache->GetResolvedMethods(),
+//                                         dex_cache->NumResolvedMethods(),
+//                                         image_pointer_size_,
+//                                         spaces);
+//      }
 
-    AppendToBootClassPath(*dex_file.get(), dex_cache);
-    opened_dex_files_.push_back(std::move(dex_file));
+      if (dex_file->GetLocationChecksum() != oat_dex_file->GetDexFileLocationChecksum()) {
+        *error_msg = StringPrintf("Checksums do not match for %s: %x vs %x",
+                                  dex_file_location.c_str(),
+                                  dex_file->GetLocationChecksum(),
+                                  oat_dex_file->GetDexFileLocationChecksum());
+        return false;
+      }
+
+      AppendToBootClassPath(*dex_file.get(), dex_cache);
+      opened_dex_files_.push_back(std::move(dex_file));
+    }
   }
 
   if (!ValidPointerSize(image_pointer_size_)) {
@@ -968,12 +1059,14 @@
   }
 
   if (kSanityCheckObjects) {
-    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-      auto* dex_cache = dex_caches->Get(i);
-      for (size_t j = 0; j < dex_cache->NumResolvedFields(); ++j) {
-        auto* field = dex_cache->GetResolvedField(j, image_pointer_size_);
-        if (field != nullptr) {
-          CHECK(field->GetDeclaringClass()->GetClass() != nullptr);
+    for (auto dex_caches : dex_caches_vector) {
+      for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+        auto* dex_cache = dex_caches->Get(i);
+        for (size_t j = 0; j < dex_cache->NumResolvedFields(); ++j) {
+          auto* field = dex_cache->GetResolvedField(j, image_pointer_size_);
+          if (field != nullptr) {
+            CHECK(field->GetDeclaringClass()->GetClass() != nullptr);
+          }
         }
       }
     }
@@ -982,10 +1075,12 @@
 
   // Set entry point to interpreter if in InterpretOnly mode.
   if (!runtime->IsAotCompiler() && runtime->GetInstrumentation()->InterpretOnly()) {
-    const ImageHeader& header = space->GetImageHeader();
-    const ImageSection& methods = header.GetMethodsSection();
-    SetInterpreterEntrypointArtMethodVisitor visitor(image_pointer_size_);
-    methods.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
+    for (gc::space::ImageSpace* space : spaces) {
+      const ImageHeader& header = space->GetImageHeader();
+      const ImageSection& methods = header.GetMethodsSection();
+      SetInterpreterEntrypointArtMethodVisitor visitor(image_pointer_size_);
+      methods.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
+    }
   }
 
   // reinit class_roots_
@@ -1014,13 +1109,15 @@
   mirror::Throwable::SetClass(GetClassRoot(kJavaLangThrowable));
   mirror::StackTraceElement::SetClass(GetClassRoot(kJavaLangStackTraceElement));
 
-  const ImageHeader& header = space->GetImageHeader();
-  const ImageSection& section = header.GetImageSection(ImageHeader::kSectionClassTable);
-  if (section.Size() > 0u) {
-    WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-    ClassTable* const class_table = InsertClassTableForClassLoader(nullptr);
-    class_table->ReadFromMemory(space->Begin() + section.Offset());
-    dex_cache_boot_image_class_lookup_required_ = false;
+  for (gc::space::ImageSpace* space : spaces) {
+    const ImageHeader& header = space->GetImageHeader();
+    const ImageSection& section = header.GetImageSection(ImageHeader::kSectionClassTable);
+    if (section.Size() > 0u) {
+      WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+      ClassTable* const class_table = InsertClassTableForClassLoader(nullptr);
+      class_table->ReadFromMemory(space->Begin() + section.Offset());
+      dex_cache_boot_image_class_lookup_required_ = false;
+    }
   }
 
   FinishInit(self);
@@ -1974,7 +2071,7 @@
   }
   Runtime* runtime = Runtime::Current();
   if (!runtime->IsStarted()) {
-    if (runtime->IsAotCompiler() || runtime->GetHeap()->HasImageSpace()) {
+    if (runtime->IsAotCompiler() || runtime->GetHeap()->HasBootImageSpace()) {
       return;  // OAT file unavailable.
     }
   }
@@ -2783,23 +2880,27 @@
   return result;
 }
 
-static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches(gc::space::ImageSpace* image_space)
-    SHARED_REQUIRES(Locks::mutator_lock_) {
-  CHECK(image_space != nullptr);
-  mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  DCHECK(root != nullptr);
-  return root->AsObjectArray<mirror::DexCache>();
+static std::vector<mirror::ObjectArray<mirror::DexCache>*> GetImageDexCaches(
+    std::vector<gc::space::ImageSpace*> image_spaces) SHARED_REQUIRES(Locks::mutator_lock_) {
+  CHECK(!image_spaces.empty());
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector;
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+    DCHECK(root != nullptr);
+    dex_caches_vector.push_back(root->AsObjectArray<mirror::DexCache>());
+  }
+  return dex_caches_vector;
 }
 
 void ClassLinker::AddBootImageClassesToClassTable() {
   if (dex_cache_boot_image_class_lookup_required_) {
-    AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpace(),
+    AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpaces(),
                                 /*class_loader*/nullptr);
     dex_cache_boot_image_class_lookup_required_ = false;
   }
 }
 
-void ClassLinker::AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+void ClassLinker::AddImageClassesToClassTable(std::vector<gc::space::ImageSpace*> image_spaces,
                                               mirror::ClassLoader* class_loader) {
   Thread* self = Thread::Current();
   WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -2807,25 +2908,28 @@
 
   ClassTable* const class_table = InsertClassTableForClassLoader(class_loader);
 
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(image_space);
   std::string temp;
-  for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
-    for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
-      mirror::Class* klass = types[j].Read();
-      if (klass != nullptr) {
-        DCHECK_EQ(klass->GetClassLoader(), class_loader);
-        const char* descriptor = klass->GetDescriptor(&temp);
-        size_t hash = ComputeModifiedUtf8Hash(descriptor);
-        mirror::Class* existing = class_table->Lookup(descriptor, hash);
-        if (existing != nullptr) {
-          CHECK_EQ(existing, klass) << PrettyClassAndClassLoader(existing) << " != "
-              << PrettyClassAndClassLoader(klass);
-        } else {
-          class_table->Insert(klass);
-          if (log_new_class_table_roots_) {
-            new_class_roots_.push_back(GcRoot<mirror::Class>(klass));
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector =
+      GetImageDexCaches(image_spaces);
+  for (mirror::ObjectArray<mirror::DexCache>* dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
+      for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
+        mirror::Class* klass = types[j].Read();
+        if (klass != nullptr) {
+          DCHECK_EQ(klass->GetClassLoader(), class_loader);
+          const char* descriptor = klass->GetDescriptor(&temp);
+          size_t hash = ComputeModifiedUtf8Hash(descriptor);
+          mirror::Class* existing = class_table->Lookup(descriptor, hash);
+          if (existing != nullptr) {
+            CHECK_EQ(existing, klass) << PrettyClassAndClassLoader(existing) << " != "
+                << PrettyClassAndClassLoader(klass);
+          } else {
+            class_table->Insert(klass);
+            if (log_new_class_table_roots_) {
+              new_class_roots_.push_back(GcRoot<mirror::Class>(klass));
+            }
           }
         }
       }
@@ -2856,18 +2960,20 @@
 
 mirror::Class* ClassLinker::LookupClassFromBootImage(const char* descriptor) {
   ScopedAssertNoThreadSuspension ants(Thread::Current(), "Image class lookup");
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(
-      Runtime::Current()->GetHeap()->GetBootImageSpace());
-  for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    const DexFile* dex_file = dex_cache->GetDexFile();
-    // Try binary searching the type index by descriptor.
-    const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor);
-    if (type_id != nullptr) {
-      uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id);
-      mirror::Class* klass = dex_cache->GetResolvedType(type_idx);
-      if (klass != nullptr) {
-        return klass;
+  std::vector<mirror::ObjectArray<mirror::DexCache>*> dex_caches_vector =
+      GetImageDexCaches(Runtime::Current()->GetHeap()->GetBootImageSpaces());
+  for (mirror::ObjectArray<mirror::DexCache>* dex_caches : dex_caches_vector) {
+    for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      // Try binary searching the type index by descriptor.
+      const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor);
+      if (type_id != nullptr) {
+        uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id);
+        mirror::Class* klass = dex_cache->GetResolvedType(type_idx);
+        if (klass != nullptr) {
+          return klass;
+        }
       }
     }
   }
@@ -3167,7 +3273,7 @@
   // the runtime isn't started. On the other hand, app classes can be re-verified even if they are
   // already pre-opted, as then the runtime is started.
   if (!Runtime::Current()->IsAotCompiler() &&
-      !Runtime::Current()->GetHeap()->HasImageSpace() &&
+      !Runtime::Current()->GetHeap()->HasBootImageSpace() &&
       klass->GetClassLoader() != nullptr) {
     return false;
   }
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index f16fe92..9d432c6 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -505,7 +505,7 @@
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Add image classes to the class table.
-  void AddImageClassesToClassTable(gc::space::ImageSpace* image_space,
+  void AddImageClassesToClassTable(std::vector<gc::space::ImageSpace*> image_spaces,
                                    mirror::ClassLoader* class_loader)
       REQUIRES(!Locks::classlinker_classes_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index 8f7bb94..d16afd9 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -487,7 +487,9 @@
 
 // Mark all references to the alloc space(s).
 void ModUnionTableCardCache::UpdateAndMarkReferences(MarkObjectVisitor* visitor) {
-  auto* image_space = heap_->GetBootImageSpace();
+  // TODO: Needs better support for multi-images? b/26317072
+  space::ImageSpace* image_space =
+      heap_->GetBootImageSpaces().empty() ? nullptr : heap_->GetBootImageSpaces()[0];
   // If we don't have an image space, just pass in space_ as the immune space. Pass in the same
   // space_ instead of image_space to avoid a null check in ModUnionUpdateObjectReferencesVisitor.
   CardBitVisitor bit_visitor(visitor, space_, image_space != nullptr ? image_space : space_,
diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h
index 3be7181..61c67f8 100644
--- a/runtime/gc/accounting/space_bitmap-inl.h
+++ b/runtime/gc/accounting/space_bitmap-inl.h
@@ -167,7 +167,10 @@
   uintptr_t* address = &bitmap_begin_[index];
   uintptr_t old_word = *address;
   if (kSetBit) {
-    *address = old_word | mask;
+    if ((old_word & mask) == 0) {
+      // Avoid dirtying the page if possible.
+      *address = old_word | mask;
+    }
   } else {
     *address = old_word & ~mask;
   }
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 6d72f31..3e432c7 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -233,8 +233,7 @@
       backtrace_lock_(nullptr),
       seen_backtrace_count_(0u),
       unique_backtrace_count_(0u),
-      gc_disabled_for_shutdown_(false),
-      boot_image_space_(nullptr) {
+      gc_disabled_for_shutdown_(false) {
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "Heap() entering";
   }
@@ -260,23 +259,107 @@
     CHECK_GE(300 * MB, non_moving_space_capacity);
     requested_alloc_space_begin = reinterpret_cast<uint8_t*>(300 * MB) - non_moving_space_capacity;
   }
+
+  // Load image space(s).
   if (!image_file_name.empty()) {
-    ATRACE_BEGIN("ImageSpace::Create");
-    std::string error_msg;
-    boot_image_space_ = space::ImageSpace::Create(image_file_name.c_str(),
-                                                  image_instruction_set,
-                                                  &error_msg);
-    ATRACE_END();
-    if (boot_image_space_ != nullptr) {
-      AddSpace(boot_image_space_);
-      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
-      // isn't going to get in the middle
-      uint8_t* oat_file_end_addr = boot_image_space_->GetImageHeader().GetOatFileEnd();
-      CHECK_GT(oat_file_end_addr, boot_image_space_->End());
-      requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
-    } else {
-      LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. "
-                   << "Attempting to fall back to imageless running. Error was: " << error_msg;
+    // For code reuse, handle this like a work queue.
+    std::vector<std::string> image_file_names;
+    image_file_names.push_back(image_file_name);
+
+    for (size_t index = 0; index < image_file_names.size(); ++index) {
+      std::string& image_name = image_file_names[index];
+      ATRACE_BEGIN("ImageSpace::Create");
+      std::string error_msg;
+      space::ImageSpace* boot_image_space = space::ImageSpace::Create(image_name.c_str(),
+                                                                      image_instruction_set,
+                                                                      index > 0,
+                                                                      &error_msg);
+      ATRACE_END();
+      if (boot_image_space != nullptr) {
+        AddSpace(boot_image_space);
+        // Oat files referenced by image files immediately follow them in memory, ensure alloc space
+        // isn't going to get in the middle
+        uint8_t* oat_file_end_addr = boot_image_space->GetImageHeader().GetOatFileEnd();
+        CHECK_GT(oat_file_end_addr, boot_image_space->End());
+        requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
+        boot_image_spaces_.push_back(boot_image_space);
+
+        if (index == 0) {
+          // If this was the first space, check whether there are more images to load.
+          const OatFile* boot_oat_file = boot_image_space->GetOatFile();
+          if (boot_oat_file == nullptr) {
+            continue;
+          }
+
+          const OatHeader& boot_oat_header = boot_oat_file->GetOatHeader();
+          const char* boot_classpath =
+              boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+          if (boot_classpath == nullptr) {
+            continue;
+          }
+
+          std::vector<std::string> images;
+          Split(boot_classpath, ':', &images);
+
+          // Add the rest into the list. We have to adjust locations, possibly:
+          //
+          // For example, image_file_name is /a/b/c/d/e.art
+          //              images[0] is          f/c/d/e.art
+          // ----------------------------------------------
+          //              images[1] is          g/h/i/j.art  -> /a/b/h/i/j.art
+
+          // Derive pattern.
+          std::vector<std::string> left;
+          Split(image_file_name, '/', &left);
+          std::vector<std::string> right;
+          Split(images[0], '/', &right);
+
+          size_t common = 1;
+          while (common < left.size() && common < right.size()) {
+            if (left[left.size() - common - 1] != right[right.size() - common - 1]) {
+              break;
+            }
+            common++;
+          }
+
+          std::vector<std::string> prefix_vector(left.begin(), left.end() - common);
+          std::string common_prefix = Join(prefix_vector, '/');
+          if (!common_prefix.empty() && common_prefix[0] != '/' && image_file_name[0] == '/') {
+            common_prefix = "/" + common_prefix;
+          }
+
+          // Apply pattern to images[1] .. images[n].
+          for (size_t i = 1; i < images.size(); ++i) {
+            std::string image = images[i];
+
+            size_t rslash = std::string::npos;
+            for (size_t j = 0; j < common; ++j) {
+              if (rslash != std::string::npos) {
+                rslash--;
+              }
+
+              rslash = image.rfind('/', rslash);
+              if (rslash == std::string::npos) {
+                rslash = 0;
+              }
+              if (rslash == 0) {
+                break;
+              }
+            }
+            std::string image_part = image.substr(rslash);
+
+            std::string new_image = common_prefix + (StartsWith(image_part, "/") ? "" : "/") +
+                image_part;
+            image_file_names.push_back(new_image);
+          }
+        }
+      } else {
+        LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. "
+            << "Attempting to fall back to imageless running. Error was: " << error_msg
+            << "\nAttempted image: " << image_name;
+        // TODO: Remove already loaded spaces.
+        break;
+      }
     }
   }
   /*
@@ -456,13 +539,15 @@
     rb_table_.reset(new accounting::ReadBarrierTable());
     DCHECK(rb_table_->IsAllCleared());
   }
-  if (GetBootImageSpace() != nullptr) {
+  if (HasBootImageSpace()) {
     // Don't add the image mod union table if we are running without an image, this can crash if
     // we use the CardCache implementation.
-    accounting::ModUnionTable* mod_union_table = new accounting::ModUnionTableToZygoteAllocspace(
-        "Image mod-union table", this, GetBootImageSpace());
-    CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
-    AddModUnionTable(mod_union_table);
+    for (space::ImageSpace* image_space : GetBootImageSpaces()) {
+      accounting::ModUnionTable* mod_union_table = new accounting::ModUnionTableToZygoteAllocspace(
+          "Image mod-union table", this, image_space);
+      CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
+      AddModUnionTable(mod_union_table);
+    }
   }
   if (collector::SemiSpace::kUseRememberedSet && non_moving_space_ != main_space_) {
     accounting::RememberedSet* non_moving_space_rem_set =
@@ -525,11 +610,12 @@
       garbage_collectors_.push_back(mark_compact_collector_);
     }
   }
-  if (GetBootImageSpace() != nullptr && non_moving_space_ != nullptr &&
+  if (!GetBootImageSpaces().empty() && non_moving_space_ != nullptr &&
       (is_zygote || separate_non_moving_space || foreground_collector_type_ == kCollectorTypeGSS)) {
     // Check that there's no gap between the image space and the non moving space so that the
     // immune region won't break (eg. due to a large object allocated in the gap). This is only
     // required when we're the zygote or using GSS.
+    /* TODO: Modify this check to support multi-images. b/26317072
     bool no_gap = MemMap::CheckNoGaps(GetBootImageSpace()->GetMemMap(),
                                       non_moving_space_->GetMemMap());
     if (!no_gap) {
@@ -537,6 +623,7 @@
       MemMap::DumpMaps(LOG(ERROR), true);
       LOG(FATAL) << "There's a gap between the image space and the non-moving space";
     }
+    */
   }
   instrumentation::Instrumentation* const instrumentation = runtime->GetInstrumentation();
   if (gc_stress_mode_) {
@@ -1202,8 +1289,8 @@
   return FindDiscontinuousSpaceFromObject(obj, fail_ok);
 }
 
-space::ImageSpace* Heap::GetBootImageSpace() const {
-  return boot_image_space_;
+std::vector<space::ImageSpace*> Heap::GetBootImageSpaces() const {
+  return boot_image_spaces_;
 }
 
 void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index e23b1a3..e7ea983 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -580,9 +580,8 @@
   // Unbind any bound bitmaps.
   void UnBindBitmaps() REQUIRES(Locks::heap_bitmap_lock_);
 
-  // Returns the boot image space. There may be multiple image spaces, but there is only one boot
-  // image space.
-  space::ImageSpace* GetBootImageSpace() const;
+  // Returns the boot image spaces. There may be multiple boot image spaces.
+  std::vector<space::ImageSpace*> GetBootImageSpaces() const;
 
   // Permenantly disable moving garbage collection.
   void DisableMovingGc() REQUIRES(!*gc_complete_lock_);
@@ -660,8 +659,8 @@
   void RemoveRememberedSet(space::Space* space);
 
   bool IsCompilingBoot() const;
-  bool HasImageSpace() const {
-    return boot_image_space_ != nullptr;
+  bool HasBootImageSpace() const {
+    return !boot_image_spaces_.empty();
   }
 
   ReferenceProcessor* GetReferenceProcessor() {
@@ -1322,8 +1321,8 @@
   // allocating.
   bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_);
 
-  // Boot image space.
-  space::ImageSpace* boot_image_space_;
+  // Boot image spaces.
+  std::vector<space::ImageSpace*> boot_image_spaces_;
 
   friend class CollectorTransitionTask;
   friend class collector::GarbageCollector;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 8f67c21..952759c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -43,12 +43,17 @@
 
 Atomic<uint32_t> ImageSpace::bitmap_index_(0);
 
-ImageSpace::ImageSpace(const std::string& image_filename, const char* image_location,
-                       MemMap* mem_map, accounting::ContinuousSpaceBitmap* live_bitmap,
-                       uint8_t* end)
+ImageSpace::ImageSpace(const std::string& image_filename,
+                       const char* image_location,
+                       MemMap* mem_map,
+                       accounting::ContinuousSpaceBitmap* live_bitmap,
+                       uint8_t* end,
+                       MemMap* shadow_map)
     : MemMapSpace(image_filename, mem_map, mem_map->Begin(), end, end,
                   kGcRetentionPolicyNeverCollect),
-      image_location_(image_location) {
+      oat_file_non_owned_(nullptr),
+      image_location_(image_location),
+      shadow_map_(shadow_map) {
   DCHECK(live_bitmap != nullptr);
   live_bitmap_.reset(live_bitmap);
 }
@@ -470,6 +475,7 @@
 
 ImageSpace* ImageSpace::Create(const char* image_location,
                                const InstructionSet image_isa,
+                               bool secondary_image,
                                std::string* error_msg) {
   std::string system_filename;
   bool has_system = false;
@@ -481,7 +487,7 @@
                                              &has_system, &cache_filename, &dalvik_cache_exists,
                                              &has_cache, &is_global_cache);
 
-  if (Runtime::Current()->IsZygote()) {
+  if (Runtime::Current()->IsZygote() && !secondary_image) {
     MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
   }
 
@@ -686,7 +692,7 @@
     return nullptr;
   }
 
-  if (kIsDebugBuild) {
+  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);
@@ -799,11 +805,52 @@
     return nullptr;
   }
 
+  // In case of multi-images, the images are spaced apart so that the bitmaps don't overlap. We
+  // need to reserve the slack, as otherwise the large object space might allocate in there.
+  // TODO: Reconsider the multi-image layout. b/26317072
+  std::unique_ptr<MemMap> shadow_map;
+  {
+    uintptr_t image_begin = reinterpret_cast<uintptr_t>(image_header.GetImageBegin());
+    uintptr_t image_end = RoundUp(image_begin + image_header.GetImageSize(), kPageSize);
+    uintptr_t oat_begin = reinterpret_cast<uintptr_t>(image_header.GetOatFileBegin());
+    if (image_end < oat_begin) {
+      // There's a gap. Could be multi-image, could be the oat file spaced apart. Go ahead and
+      // dummy-reserve the space covered by the bitmap (which will be a shadow that introduces
+      // a gap to the next image).
+      uintptr_t heap_size = bitmap->HeapSize();
+      uintptr_t bitmap_coverage_end = RoundUp(image_begin + heap_size, kPageSize);
+      if (bitmap_coverage_end > image_end) {
+        VLOG(startup) << "Reserving bitmap shadow ["
+                      << std::hex << image_end << ";"
+                      << std::hex << bitmap_coverage_end << ";] (oat file begins at "
+                      << std::hex << oat_begin;
+        // Note: we cannot use MemMap::Dummy here, as that won't reserve the space in 32-bit mode.
+        shadow_map.reset(MemMap::MapAnonymous("Image bitmap shadow",
+                                              reinterpret_cast<uint8_t*>(image_end),
+                                              bitmap_coverage_end - image_end,
+                                              PROT_NONE,
+                                              false,
+                                              false,
+                                              error_msg));
+        if (shadow_map == nullptr) {
+          return nullptr;
+        }
+        // madvise it away, we don't really want it, just reserve the address space.
+        // TODO: Should we use MadviseDontNeedAndZero? b/26317072
+        madvise(shadow_map->BaseBegin(), shadow_map->BaseSize(), MADV_DONTNEED);
+      }
+    }
+  }
+
   // We only want the mirror object, not the ArtFields and ArtMethods.
   uint8_t* const image_end =
       map->Begin() + image_header.GetImageSection(ImageHeader::kSectionObjects).End();
-  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, image_location,
-                                                   map.release(), bitmap.release(), image_end));
+  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
+                                                   image_location,
+                                                   map.release(),
+                                                   bitmap.release(),
+                                                   image_end,
+                                                   shadow_map.release()));
 
   // VerifyImageAllocations() will be called later in Runtime::Init()
   // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_
@@ -826,16 +873,18 @@
   Runtime* runtime = Runtime::Current();
   runtime->SetInstructionSet(space->oat_file_->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::kCalleeSaveMethod), Runtime::kSaveAll);
-  runtime->SetCalleeSaveMethod(
-      image_header.GetImageMethod(ImageHeader::kRefsOnlySaveMethod), Runtime::kRefsOnly);
-  runtime->SetCalleeSaveMethod(
-      image_header.GetImageMethod(ImageHeader::kRefsAndArgsSaveMethod), Runtime::kRefsAndArgs);
+  if (!runtime->HasResolutionMethod()) {
+    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::kCalleeSaveMethod), Runtime::kSaveAll);
+    runtime->SetCalleeSaveMethod(
+        image_header.GetImageMethod(ImageHeader::kRefsOnlySaveMethod), Runtime::kRefsOnly);
+    runtime->SetCalleeSaveMethod(
+        image_header.GetImageMethod(ImageHeader::kRefsAndArgsSaveMethod), Runtime::kRefsAndArgs);
+  }
 
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "ImageSpace::Init exiting (" << PrettyDuration(NanoTime() - start_time)
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index babd672..a54358a 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -43,7 +43,10 @@
   // 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* Create(const char* image, InstructionSet image_isa, std::string* error_msg)
+  static ImageSpace* Create(const char* image,
+                            InstructionSet image_isa,
+                            bool secondary_image,
+                            std::string* error_msg)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   // Reads the image header from the specified image location for the
@@ -158,8 +161,12 @@
 
   std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap_;
 
-  ImageSpace(const std::string& name, const char* image_location,
-             MemMap* mem_map, accounting::ContinuousSpaceBitmap* live_bitmap, uint8_t* end);
+  ImageSpace(const std::string& name,
+             const char* image_location,
+             MemMap* mem_map,
+             accounting::ContinuousSpaceBitmap* live_bitmap,
+             uint8_t* end,
+             MemMap* shadow_map = nullptr);
 
   // The OatFile associated with the image during early startup to
   // reserve space contiguous to the image. It is later released to
@@ -172,6 +179,10 @@
 
   const std::string image_location_;
 
+  // A MemMap reserving the space of the bitmap "shadow," so that we don't allocate into it. Only
+  // used in the multi-image case.
+  std::unique_ptr<MemMap> shadow_map_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ImageSpace);
 };
diff --git a/runtime/image.cc b/runtime/image.cc
index f8f930b..3856787 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -55,7 +55,6 @@
   CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize));
   CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize));
   CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize));
-  CHECK_LT(image_begin, image_roots);
   CHECK_LT(image_roots, oat_file_begin);
   CHECK_LE(oat_file_begin, oat_data_begin);
   CHECK_LT(oat_data_begin, oat_data_end);
@@ -100,9 +99,6 @@
   if (oat_file_begin_ >= oat_data_begin_) {
     return false;
   }
-  if (image_roots_ <= image_begin_ || oat_file_begin_ <= image_roots_) {
-    return false;
-  }
   if (!IsAligned<kPageSize>(patch_delta_)) {
     return false;
   }
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index e2e4782..d035f5d 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -187,24 +187,27 @@
   if (image_added_to_intern_table_) {
     return nullptr;
   }
-  gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetBootImageSpace();
-  if (image == nullptr) {
+  std::vector<gc::space::ImageSpace*> image_spaces =
+      Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  if (image_spaces.empty()) {
     return nullptr;  // No image present.
   }
-  mirror::Object* root = image->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>();
   const std::string utf8 = s->ToModifiedUtf8();
-  for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
-    mirror::DexCache* dex_cache = dex_caches->Get(i);
-    const DexFile* dex_file = dex_cache->GetDexFile();
-    // Binary search the dex file for the string index.
-    const DexFile::StringId* string_id = dex_file->FindStringId(utf8.c_str());
-    if (string_id != nullptr) {
-      uint32_t string_idx = dex_file->GetIndexForStringId(*string_id);
-      // GetResolvedString() contains a RB.
-      mirror::String* image_string = dex_cache->GetResolvedString(string_idx);
-      if (image_string != nullptr) {
-        return image_string;
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
+    mirror::ObjectArray<mirror::DexCache>* dex_caches = root->AsObjectArray<mirror::DexCache>();
+    for (int32_t i = 0; i < dex_caches->GetLength(); ++i) {
+      mirror::DexCache* dex_cache = dex_caches->Get(i);
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      // Binary search the dex file for the string index.
+      const DexFile::StringId* string_id = dex_file->FindStringId(utf8.c_str());
+      if (string_id != nullptr) {
+        uint32_t string_idx = dex_file->GetIndexForStringId(*string_id);
+        // GetResolvedString() contains a RB.
+        mirror::String* image_string = dex_cache->GetResolvedString(string_idx);
+        if (image_string != nullptr) {
+          return image_string;
+        }
       }
     }
   }
diff --git a/runtime/oat.h b/runtime/oat.h
index 5ed1977..13fd6a4 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -39,6 +39,7 @@
   static constexpr const char* kPicKey = "pic";
   static constexpr const char* kDebuggableKey = "debuggable";
   static constexpr const char* kClassPathKey = "classpath";
+  static constexpr const char* kBootClassPath = "bootclasspath";
 
   static constexpr const char kTrueValue[] = "true";
   static constexpr const char kFalseValue[] = "false";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 83e594b..e3de14b 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -983,6 +983,7 @@
     LOG(WARNING) << "Failed to find OatDexFile for DexFile " << dex_location
                  << " ( canonical path " << dex_canonical_location << ")"
                  << " with checksum " << checksum << " in OatFile " << GetLocation();
+    /* TODO: Modify for multi-image support and reenable. b/26317072
     if (kIsDebugBuild) {
       for (const OatDexFile* odf : oat_dex_files_storage_) {
         LOG(WARNING) << "OatFile " << GetLocation()
@@ -991,6 +992,7 @@
                      << " with checksum 0x" << std::hex << odf->GetDexFileLocationChecksum();
       }
     }
+    */
   }
 
   return nullptr;
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 0f3a013..cb80cc9 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -846,11 +846,7 @@
 
 std::string OatFileAssistant::ImageLocation() {
   Runtime* runtime = Runtime::Current();
-  const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-  if (image_space == nullptr) {
-    return "";
-  }
-  return image_space->GetImageLocation();
+  return runtime->GetHeap()->GetBootImageSpaces()[0]->GetImageLocation();
 }
 
 const uint32_t* OatFileAssistant::GetRequiredDexChecksum() {
@@ -949,12 +945,13 @@
     image_info_load_attempted_ = true;
 
     Runtime* runtime = Runtime::Current();
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-    if (image_space != nullptr) {
-      cached_image_info_.location = image_space->GetImageLocation();
+    std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces();
+    if (!image_spaces.empty()) {
+      // TODO: Better support multi-images? b/26317072
+      cached_image_info_.location = image_spaces[0]->GetImageLocation();
 
       if (isa_ == kRuntimeISA) {
-        const ImageHeader& image_header = image_space->GetImageHeader();
+        const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
         cached_image_info_.oat_checksum = image_header.GetOatChecksum();
         cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
             image_header.GetOatDataBegin());
@@ -969,7 +966,7 @@
         cached_image_info_.patch_delta = image_header->GetPatchDelta();
       }
     }
-    image_info_load_succeeded_ = (image_space != nullptr);
+    image_info_load_succeeded_ = (!image_spaces.empty());
   }
   return image_info_load_succeeded_ ? &cached_image_info_ : nullptr;
 }
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 40cd50b..f994f0c 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -223,9 +223,10 @@
         false, dex_location.c_str(), &error_msg));
     ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
 
-    const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace();
-    ASSERT_TRUE(image_space != nullptr);
-    const ImageHeader& image_header = image_space->GetImageHeader();
+    const std::vector<gc::space::ImageSpace*> image_spaces =
+        runtime->GetHeap()->GetBootImageSpaces();
+    ASSERT_TRUE(!image_spaces.empty() && image_spaces[0] != nullptr);
+    const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
     const OatHeader& oat_header = odex_file->GetOatHeader();
     EXPECT_FALSE(odex_file->IsPic());
     EXPECT_EQ(image_header.GetOatChecksum(), oat_header.GetImageFileLocationOatChecksum());
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index ea6d3ff..36a967f 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -78,17 +78,23 @@
   return nullptr;
 }
 
-const OatFile* OatFileManager::GetBootOatFile() const {
-  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-  return (image_space == nullptr) ? nullptr : image_space->GetOatFile();
+std::vector<const OatFile*> OatFileManager::GetBootOatFiles() const {
+  std::vector<const OatFile*> oat_files;
+  std::vector<gc::space::ImageSpace*> image_spaces =
+      Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  for (gc::space::ImageSpace* image_space : image_spaces) {
+    oat_files.push_back(image_space->GetOatFile());
+  }
+  return oat_files;
 }
 
 const OatFile* OatFileManager::GetPrimaryOatFile() const {
   ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
-  const OatFile* boot_oat_file = GetBootOatFile();
-  if (boot_oat_file != nullptr) {
+  std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
+  if (!boot_oat_files.empty()) {
     for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
-      if (oat_file.get() != boot_oat_file) {
+      if (std::find(boot_oat_files.begin(), boot_oat_files.end(), oat_file.get()) ==
+          boot_oat_files.end()) {
         return oat_file.get();
       }
     }
@@ -102,8 +108,13 @@
   oat_files_.clear();
 }
 
-const OatFile* OatFileManager::RegisterImageOatFile(gc::space::ImageSpace* space) {
-  return RegisterOatFile(space->ReleaseOatFile());
+std::vector<const OatFile*> OatFileManager::RegisterImageOatFiles(
+    std::vector<gc::space::ImageSpace*> spaces) {
+  std::vector<const OatFile*> oat_files;
+  for (gc::space::ImageSpace* space : spaces) {
+    oat_files.push_back(RegisterOatFile(space->ReleaseOatFile()));
+  }
+  return oat_files;
 }
 
 class DexFileAndClassPair : ValueObject {
@@ -213,7 +224,7 @@
   std::priority_queue<DexFileAndClassPair> queue;
 
   // Add dex files from already loaded oat files, but skip boot.
-  const OatFile* boot_oat = GetBootOatFile();
+  std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
   // The same OatFile can be loaded multiple times at different addresses. In this case, we don't
   // need to check both against each other since they would have resolved the same way at compile
   // time.
@@ -221,8 +232,8 @@
   for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) {
     DCHECK_NE(loaded_oat_file.get(), oat_file);
     const std::string& location = loaded_oat_file->GetLocation();
-    if (loaded_oat_file.get() != boot_oat &&
-        location != oat_file->GetLocation() &&
+    if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) ==
+        boot_oat_files.end() && location != oat_file->GetLocation() &&
         unique_locations.find(location) == unique_locations.end()) {
       unique_locations.insert(location);
       AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue);
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index af7efb4..4690e45 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -73,15 +73,15 @@
     return have_non_pic_oat_file_;
   }
 
-  // Returns the boot image oat file.
-  const OatFile* GetBootOatFile() const;
+  // Returns the boot image oat files.
+  std::vector<const OatFile*> GetBootOatFiles() const;
 
   // Returns the first non-image oat file in the class path.
   const OatFile* GetPrimaryOatFile() const REQUIRES(!Locks::oat_file_manager_lock_);
 
-  // Return the oat file for an image, registers the oat file. Takes ownership of the imagespace's
-  // underlying oat file.
-  const OatFile* RegisterImageOatFile(gc::space::ImageSpace* space)
+  // Returns the oat files for the images, registers the oat files.
+  // Takes ownership of the imagespace's underlying oat files.
+  std::vector<const OatFile*> RegisterImageOatFiles(std::vector<gc::space::ImageSpace*> spaces)
       REQUIRES(!Locks::oat_file_manager_lock_);
 
   // Finds or creates the oat file holding dex_location. Then loads and returns
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index 03cad08..5643739 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -44,7 +44,8 @@
     uintptr_t code = reinterpret_cast<uintptr_t>(code_ptr);
     uintptr_t header = code - OFFSETOF_MEMBER(OatQuickMethodHeader, code_);
     DCHECK(IsAlignedParam(code, GetInstructionSetAlignment(kRuntimeISA)) ||
-           IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA)));
+           IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA)))
+        << std::hex << code << " " << std::hex << header;
     return reinterpret_cast<OatQuickMethodHeader*>(header);
   }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index eeaadd4..be64bff 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -547,15 +547,15 @@
   // Use !IsAotCompiler so that we get test coverage, tests are never the zygote.
   if (!IsAotCompiler()) {
     ScopedObjectAccess soa(self);
-    gc::space::ImageSpace* image_space = heap_->GetBootImageSpace();
-    if (image_space != nullptr) {
+    std::vector<gc::space::ImageSpace*> image_spaces = heap_->GetBootImageSpaces();
+    for (gc::space::ImageSpace* image_space : image_spaces) {
       ATRACE_BEGIN("AddImageStringsToTable");
       GetInternTable()->AddImageStringsToTable(image_space);
       ATRACE_END();
-      ATRACE_BEGIN("MoveImageClassesToClassTable");
-      GetClassLinker()->AddBootImageClassesToClassTable();
-      ATRACE_END();
     }
+    ATRACE_BEGIN("MoveImageClassesToClassTable");
+    GetClassLinker()->AddBootImageClassesToClassTable();
+    ATRACE_END();
   }
 
   // If we are the zygote then we need to wait until after forking to create the code cache
@@ -564,7 +564,7 @@
     CreateJit();
   }
 
-  if (!IsImageDex2OatEnabled() || !GetHeap()->HasImageSpace()) {
+  if (!IsImageDex2OatEnabled() || !GetHeap()->HasBootImageSpace()) {
     ScopedObjectAccess soa(self);
     StackHandleScope<1> hs(soa.Self());
     auto klass(hs.NewHandle<mirror::Class>(mirror::Class::GetJavaLangClass()));
@@ -754,61 +754,96 @@
   VLOG(startup) << "Runtime::StartDaemonThreads exiting";
 }
 
+// Attempts to open dex files from image(s). Given the image location, try to find the oat file
+// and open it to get the stored dex file. If the image is the first for a multi-image boot
+// classpath, go on and also open the other images.
 static bool OpenDexFilesFromImage(const std::string& image_location,
                                   std::vector<std::unique_ptr<const DexFile>>* dex_files,
                                   size_t* failures) {
   DCHECK(dex_files != nullptr) << "OpenDexFilesFromImage: out-param is nullptr";
-  std::string system_filename;
-  bool has_system = false;
-  std::string cache_filename_unused;
-  bool dalvik_cache_exists_unused;
-  bool has_cache_unused;
-  bool is_global_cache_unused;
-  bool found_image = gc::space::ImageSpace::FindImageFilename(image_location.c_str(),
-                                                              kRuntimeISA,
-                                                              &system_filename,
-                                                              &has_system,
-                                                              &cache_filename_unused,
-                                                              &dalvik_cache_exists_unused,
-                                                              &has_cache_unused,
-                                                              &is_global_cache_unused);
-  *failures = 0;
-  if (!found_image || !has_system) {
-    return false;
-  }
-  std::string error_msg;
-  // We are falling back to non-executable use of the oat file because patching failed, presumably
-  // due to lack of space.
-  std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(system_filename.c_str());
-  std::string oat_location = ImageHeader::GetOatLocationFromImageLocation(image_location.c_str());
-  std::unique_ptr<File> file(OS::OpenFileForReading(oat_filename.c_str()));
-  if (file.get() == nullptr) {
-    return false;
-  }
-  std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.release(), false, false, &error_msg));
-  if (elf_file.get() == nullptr) {
-    return false;
-  }
-  std::unique_ptr<const OatFile> oat_file(
-      OatFile::OpenWithElfFile(elf_file.release(), oat_location, nullptr, &error_msg));
-  if (oat_file == nullptr) {
-    LOG(WARNING) << "Unable to use '" << oat_filename << "' because " << error_msg;
-    return false;
-  }
 
-  for (const OatFile::OatDexFile* oat_dex_file : oat_file->GetOatDexFiles()) {
-    if (oat_dex_file == nullptr) {
-      *failures += 1;
-      continue;
+  // Use a work-list approach, so that we can easily reuse the opening code.
+  std::vector<std::string> image_locations;
+  image_locations.push_back(image_location);
+
+  for (size_t index = 0; index < image_locations.size(); ++index) {
+    std::string system_filename;
+    bool has_system = false;
+    std::string cache_filename_unused;
+    bool dalvik_cache_exists_unused;
+    bool has_cache_unused;
+    bool is_global_cache_unused;
+    bool found_image = gc::space::ImageSpace::FindImageFilename(image_locations[index].c_str(),
+                                                                kRuntimeISA,
+                                                                &system_filename,
+                                                                &has_system,
+                                                                &cache_filename_unused,
+                                                                &dalvik_cache_exists_unused,
+                                                                &has_cache_unused,
+                                                                &is_global_cache_unused);
+
+    if (!found_image || !has_system) {
+      return false;
     }
-    std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
-    if (dex_file.get() == nullptr) {
-      *failures += 1;
-    } else {
-      dex_files->push_back(std::move(dex_file));
+
+    // We are falling back to non-executable use of the oat file because patching failed, presumably
+    // due to lack of space.
+    std::string oat_filename =
+        ImageHeader::GetOatLocationFromImageLocation(system_filename.c_str());
+    std::string oat_location =
+        ImageHeader::GetOatLocationFromImageLocation(image_locations[index].c_str());
+    // Note: in the multi-image case, the image location may end in ".jar," and not ".art." Handle
+    //       that here.
+    if (EndsWith(oat_location, ".jar")) {
+      oat_location.replace(oat_location.length() - 3, 3, "oat");
     }
+
+    std::unique_ptr<File> file(OS::OpenFileForReading(oat_filename.c_str()));
+    if (file.get() == nullptr) {
+      return false;
+    }
+    std::string error_msg;
+    std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.release(), false, false, &error_msg));
+    if (elf_file.get() == nullptr) {
+      return false;
+    }
+    std::unique_ptr<const OatFile> oat_file(
+        OatFile::OpenWithElfFile(elf_file.release(), oat_location, nullptr, &error_msg));
+    if (oat_file == nullptr) {
+      LOG(WARNING) << "Unable to use '" << oat_filename << "' because " << error_msg;
+      return false;
+    }
+
+    for (const OatFile::OatDexFile* oat_dex_file : oat_file->GetOatDexFiles()) {
+      if (oat_dex_file == nullptr) {
+        *failures += 1;
+        continue;
+      }
+      std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
+      if (dex_file.get() == nullptr) {
+        *failures += 1;
+      } else {
+        dex_files->push_back(std::move(dex_file));
+      }
+    }
+
+    if (index == 0) {
+      // First file. See if this is a multi-image environment, and if so, enqueue the other images.
+      const OatHeader& boot_oat_header = oat_file->GetOatHeader();
+      const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+      if (boot_cp != nullptr) {
+        std::vector<std::string> cp;
+        Split(boot_cp, ':', &cp);
+
+        if (cp.size() > 1) {
+          // More images, enqueue (skipping the first).
+          image_locations.insert(image_locations.end(), cp.begin() + 1, cp.end());
+        }
+      }
+    }
+
+    Runtime::Current()->GetOatFileManager().RegisterOatFile(std::move(oat_file));
   }
-  Runtime::Current()->GetOatFileManager().RegisterOatFile(std::move(oat_file));
   return true;
 }
 
@@ -946,7 +981,7 @@
                        runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));
   ATRACE_END();
 
-  if (heap_->GetBootImageSpace() == nullptr && !allow_dex_file_fallback_) {
+  if (!heap_->HasBootImageSpace() && !allow_dex_file_fallback_) {
     LOG(ERROR) << "Dex file fallback disabled, cannot continue without image.";
     ATRACE_END();
     return false;
@@ -1054,7 +1089,7 @@
 
   CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U);
   class_linker_ = new ClassLinker(intern_table_);
-  if (GetHeap()->HasImageSpace()) {
+  if (GetHeap()->HasBootImageSpace()) {
     ATRACE_BEGIN("InitFromImage");
     std::string error_msg;
     bool result = class_linker_->InitFromImage(&error_msg);
@@ -1063,9 +1098,13 @@
       LOG(ERROR) << "Could not initialize from image: " << error_msg;
       return false;
     }
+    /* TODO: Modify check to support multiple image spaces and reenable. b/26317072
     if (kIsDebugBuild) {
-      GetHeap()->GetBootImageSpace()->VerifyImageAllocations();
+      for (auto image_space : GetHeap()->GetBootImageSpaces()) {
+        image_space->VerifyImageAllocations();
+      }
     }
+    */
     if (boot_class_path_string_.empty()) {
       // The bootclasspath is not explicitly specified: construct it from the loaded dex files.
       const std::vector<const DexFile*>& boot_class_path = GetClassLinker()->GetBootClassPath();
diff --git a/test/117-nopatchoat/nopatchoat.cc b/test/117-nopatchoat/nopatchoat.cc
index b6b1c43..1337442 100644
--- a/test/117-nopatchoat/nopatchoat.cc
+++ b/test/117-nopatchoat/nopatchoat.cc
@@ -35,8 +35,9 @@
   }
 
   static bool isRelocationDeltaZero() {
-    gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-    return space != nullptr && space->GetImageHeader().GetPatchDelta() == 0;
+    std::vector<gc::space::ImageSpace*> spaces =
+        Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    return !spaces.empty() && spaces[0]->GetImageHeader().GetPatchDelta() == 0;
   }
 
   static bool hasExecutableOat(jclass cls) {
diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc
index 7762b2d..9bfe429 100644
--- a/test/137-cfi/cfi.cc
+++ b/test/137-cfi/cfi.cc
@@ -92,9 +92,10 @@
 // detecting this.
 #if __linux__
 static bool IsPicImage() {
-  gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace();
-  CHECK(image_space != nullptr);  // We should be running with an image.
-  const OatFile* oat_file = image_space->GetOatFile();
+  std::vector<gc::space::ImageSpace*> image_spaces =
+      Runtime::Current()->GetHeap()->GetBootImageSpaces();
+  CHECK(!image_spaces.empty());  // We should be running with an image.
+  const OatFile* oat_file = image_spaces[0]->GetOatFile();
   CHECK(oat_file != nullptr);     // We should have an oat file to go with the image.
   return oat_file->IsPic();
 }
diff --git a/test/464-checker-inline-sharpen-calls/src/Main.java b/test/464-checker-inline-sharpen-calls/src/Main.java
index 5080f142..2222e0f 100644
--- a/test/464-checker-inline-sharpen-calls/src/Main.java
+++ b/test/464-checker-inline-sharpen-calls/src/Main.java
@@ -16,6 +16,14 @@
 
 public final class Main {
 
+  public final static class Helper {
+    private int foo = 3;
+
+    public int getFoo() {
+        return foo;
+    }
+  }
+
   public void invokeVirtual() {
   }
 
@@ -31,25 +39,25 @@
     m.invokeVirtual();
   }
 
-  /// CHECK-START: int Main.inlineSharpenStringInvoke() ssa_builder (after)
-  /// CHECK-DAG:     <<Invoke:i\d+>>  InvokeVirtual
+  /// CHECK-START: int Main.inlineSharpenHelperInvoke() ssa_builder (after)
+  /// CHECK-DAG:     <<Invoke:i\d+>>  InvokeVirtual {{.*\.getFoo.*}}
   /// CHECK-DAG:                      Return [<<Invoke>>]
 
-  /// CHECK-START: int Main.inlineSharpenStringInvoke() inliner (after)
-  /// CHECK-NOT:                      InvokeStaticOrDirect
-  /// CHECK-NOT:                      InvokeVirtual
+  /// CHECK-START: int Main.inlineSharpenHelperInvoke() inliner (after)
+  /// CHECK-NOT:                      InvokeStaticOrDirect {{.*\.getFoo.*}}
+  /// CHECK-NOT:                      InvokeVirtual {{.*\.getFoo.*}}
 
-  /// CHECK-START: int Main.inlineSharpenStringInvoke() inliner (after)
+  /// CHECK-START: int Main.inlineSharpenHelperInvoke() inliner (after)
   /// CHECK-DAG:     <<Field:i\d+>>   InstanceFieldGet
   /// CHECK-DAG:                      Return [<<Field>>]
 
-  public static int inlineSharpenStringInvoke() {
-    return "Foo".length();
+  public static int inlineSharpenHelperInvoke() {
+    return new Helper().getFoo();
   }
 
   public static void main(String[] args) {
     inlineSharpenInvokeVirtual(new Main());
-    if (inlineSharpenStringInvoke() != 3) {
+    if (inlineSharpenHelperInvoke() != 3) {
       throw new Error("Expected 3");
     }
   }
diff --git a/test/490-checker-inline/src/Main.java b/test/490-checker-inline/src/Main.java
index 21a0189..2e2deea 100644
--- a/test/490-checker-inline/src/Main.java
+++ b/test/490-checker-inline/src/Main.java
@@ -39,7 +39,7 @@
   /// CHECK-DAG:     InvokeInterface
 
   /// CHECK-START: void Main.testMethod() inliner (after)
-  /// CHECK-NOT:     Invoke{{.*}}
+  /// CHECK-NOT:     Invoke{{.*Object\.<init>.*}}
 
   public static void testMethod() {
     createMain().invokeVirtual();
diff --git a/test/492-checker-inline-invoke-interface/src/Main.java b/test/492-checker-inline-invoke-interface/src/Main.java
index a8b6307..3106ce4 100644
--- a/test/492-checker-inline-invoke-interface/src/Main.java
+++ b/test/492-checker-inline-invoke-interface/src/Main.java
@@ -32,14 +32,14 @@
   }
 
   /// CHECK-START: void Main.main(java.lang.String[]) ssa_builder (after)
-  /// CHECK:           InvokeStaticOrDirect
+  /// CHECK:           InvokeStaticOrDirect {{.*Main.<init>.*}}
   /// CHECK:           InvokeInterface
 
   /// CHECK-START: void Main.main(java.lang.String[]) inliner (before)
   /// CHECK-NOT:       ClinitCheck
 
   /// CHECK-START: void Main.main(java.lang.String[]) inliner (after)
-  /// CHECK-NOT:       InvokeStaticOrDirect
+  /// CHECK-NOT:       InvokeStaticOrDirect {{.*Main.<init>.*}}
   /// CHECK-NOT:       InvokeVirtual
   /// CHECK-NOT:       InvokeInterface
 
diff --git a/test/493-checker-inline-invoke-interface/src/Main.java b/test/493-checker-inline-invoke-interface/src/Main.java
index 44b727f..171405c 100644
--- a/test/493-checker-inline-invoke-interface/src/Main.java
+++ b/test/493-checker-inline-invoke-interface/src/Main.java
@@ -36,7 +36,7 @@
   /// CHECK:           InvokeInterface
 
   /// CHECK-START: void Main.main(java.lang.String[]) inliner (after)
-  /// CHECK-NOT:       Invoke{{.*}}
+  /// CHECK-NOT:       Invoke{{.*Object\.<init>.*}}
   public static void main(String[] args) {
     Itf itf = bar();
     itf.foo();
diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java
index 3f65d5a..be666e9 100644
--- a/test/536-checker-intrinsic-optimization/src/Main.java
+++ b/test/536-checker-intrinsic-optimization/src/Main.java
@@ -47,7 +47,7 @@
   }
 
   /// CHECK-START-X86: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
-  /// CHECK:          InvokeVirtual
+  /// CHECK:          InvokeVirtual {{.*\.equals.*}}
   /// CHECK-NOT:      test
   public static boolean stringArgumentNotNull(Object obj) {
     obj.getClass();
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index afd833e..81cfb70 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -155,8 +155,14 @@
 ifeq ($(ART_TEST_RUN_TEST_NO_IMAGE),true)
   IMAGE_TYPES += no-image
 endif
+ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
+  IMAGE_TYPES := multiimage
+endif
 ifeq ($(ART_TEST_PIC_IMAGE),true)
   IMAGE_TYPES += picimage
+  ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
+    IMAGE_TYPES := multipicimage
+  endif
 endif
 PICTEST_TYPES := npictest
 ifeq ($(ART_TEST_PIC_TEST),true)
@@ -581,6 +587,19 @@
 TEST_ART_BROKEN_DEFAULT_HEAP_POISONING_RUN_TESTS :=
 TEST_ART_BROKEN_OPTIMIZING_HEAP_POISONING_RUN_TESTS :=
 
+
+# Tests broken by multi-image. b/26317072
+TEST_ART_BROKEN_MULTI_IMAGE_RUN_TESTS := \
+  476-checker-ctor-memory-barrier \
+  530-checker-lse
+
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+    $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
+    $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), \
+    $(TEST_ART_BROKEN_MULTI_IMAGE_RUN_TESTS),  $(ALL_ADDRESS_SIZES))
+
+TEST_ART_BROKEN_MULTI_IMAGE_RUN_TESTS :=
+
 # Clear variables ahead of appending to them when defining tests.
 $(foreach target, $(TARGET_TYPES), $(eval ART_RUN_TEST_$(call name-to-var,$(target))_RULES :=))
 $(foreach target, $(TARGET_TYPES), \
@@ -839,7 +858,27 @@
           prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
         endif
       else
-        $$(error found $(9) expected $(IMAGE_TYPES))
+        ifeq ($(9),multiimage)
+          test_groups += ART_RUN_TEST_$$(uc_host_or_target)_IMAGE_RULES
+          run_test_options += --multi-image
+      		ifeq ($(1),host)
+        		prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
+      		else
+        		prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
+      		endif
+        else
+          ifeq ($(9),multipicimage)
+            test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
+        		run_test_options += --pic-image --multi-image
+        		ifeq ($(1),host)
+          		prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
+        		else
+          		prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
+        		endif
+          else
+            $$(error found $(9) expected $(IMAGE_TYPES))
+          endif
+        endif
       endif
     endif
   endif
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 082c9b3..fd41fd2 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -56,7 +56,7 @@
 
 extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasImage(JNIEnv* env ATTRIBUTE_UNUSED,
                                                          jclass cls ATTRIBUTE_UNUSED) {
-  return Runtime::Current()->GetHeap()->HasImageSpace();
+  return Runtime::Current()->GetHeap()->HasBootImageSpace();
 }
 
 // public static native boolean isImageDex2OatEnabled();
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 3efa6ff..6ff356f 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -361,13 +361,17 @@
 dex2oat_cmdline="true"
 mkdir_cmdline="mkdir -p ${DEX_LOCATION}/dalvik-cache/$ISA"
 
+# TODO: allow app-image to work with multi-image. b/26317072
+app_image=""
+# app_image="--app-image-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.art | cut -d/ -f 2- | sed "s:/:@:g")"
+
 if [ "$PREBUILD" = "y" ]; then
   dex2oat_cmdline="$INVOKE_WITH $ANDROID_ROOT/bin/dex2oatd \
                       $COMPILE_FLAGS \
                       --boot-image=${BOOT_IMAGE} \
                       --dex-file=$DEX_LOCATION/$TEST_NAME.jar \
                       --oat-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.dex | cut -d/ -f 2- | sed "s:/:@:g") \
-                      --app-image-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.art | cut -d/ -f 2- | sed "s:/:@:g") \
+                      ${app_image} \
                       --instruction-set=$ISA"
   if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
     dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
diff --git a/test/run-test b/test/run-test
index ac2b52c..d076687 100755
--- a/test/run-test
+++ b/test/run-test
@@ -135,6 +135,7 @@
 have_image="yes"
 image_suffix=""
 pic_image_suffix=""
+multi_image_suffix=""
 android_root="/system"
 
 while true; do
@@ -184,6 +185,9 @@
     elif [ "x$1" = "x--pic-image" ]; then
         pic_image_suffix="-pic"
         shift
+    elif [ "x$1" = "x--multi-image" ]; then
+        multi_image_suffix="-multi"
+        shift
     elif [ "x$1" = "x--pic-test" ]; then
         run_args="${run_args} --pic-test"
         shift
@@ -470,12 +474,12 @@
             export ANDROID_HOST_OUT=${OUT_DIR:-$ANDROID_BUILD_TOP/out/}host/linux-x86
         fi
         guess_host_arch_name
-        run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${pic_image_suffix}.art"
+        run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
         run_args="${run_args} --runtime-option -Djava.library.path=${ANDROID_HOST_OUT}/lib${suffix64}"
     else
         guess_target_arch_name
         run_args="${run_args} --runtime-option -Djava.library.path=/data/art-test/${target_arch_name}"
-        run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}.art"
+        run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
     fi
     if [ "$relocate" = "yes" ]; then
       run_args="${run_args} --relocate"
@@ -612,6 +616,8 @@
         echo "                          Set instruction-set-features for compilation."
         echo "    --pic-image           Use an image compiled with position independent code for the"
         echo "                          boot class path."
+        echo "    --multi-image         Use a set of images compiled with dex2oat multi-image for"
+        echo "                          the boot class path."
         echo "    --pic-test            Compile the test code position independent."
         echo "    --quiet               Don't print anything except failure messages"
     ) 1>&2  # Direct to stderr so usage is not printed if --quiet is set.