Support running without a boot image.

Bug: 17000769

(cherry picked from commit 64ad14dbe2225441fb7734bf6d89358d96692eea)

Change-Id: I6404d5050c8a2f4ee6e70d58532eb25ee9de248e
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 7494615..f75dea3 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -198,10 +198,13 @@
 
 // To set a value for generic JNI. May be necessary in compiler tests.
 extern "C" void art_quick_generic_jni_trampoline(mirror::ArtMethod*);
+extern "C" void art_quick_resolution_trampoline(mirror::ArtMethod*);
+extern "C" void art_quick_imt_conflict_trampoline(mirror::ArtMethod*);
+extern "C" void art_quick_to_interpreter_bridge(mirror::ArtMethod*);
 
-void ClassLinker::InitFromCompiler(const std::vector<const DexFile*>& boot_class_path) {
+void ClassLinker::InitWithoutImage(const std::vector<const DexFile*>& boot_class_path) {
   VLOG(startup) << "ClassLinker::Init";
-  CHECK(Runtime::Current()->IsCompiler());
+  CHECK(!Runtime::Current()->GetHeap()->HasImageSpace()) << "Runtime has image. We should use it.";
 
   CHECK(!init_done_);
 
@@ -373,6 +376,12 @@
   // Set up GenericJNI entrypoint. That is mainly a hack for common_compiler_test.h so that
   // we do not need friend classes or a publicly exposed setter.
   quick_generic_jni_trampoline_ = reinterpret_cast<void*>(art_quick_generic_jni_trampoline);
+  if (!runtime->IsCompiler()) {
+    // We need to set up the generic trampolines since we don't have an image.
+    quick_resolution_trampoline_ = reinterpret_cast<void*>(art_quick_resolution_trampoline);
+    quick_imt_conflict_trampoline_ = reinterpret_cast<void*>(art_quick_imt_conflict_trampoline);
+    quick_to_interpreter_bridge_trampoline_ = reinterpret_cast<void*>(art_quick_to_interpreter_bridge);
+  }
 
   // Object, String and DexCache need to be rerun through FindSystemClass to finish init
   java_lang_Object->SetStatus(mirror::Class::kStatusNotReady, self);
@@ -592,6 +601,13 @@
 
   gc::Heap* heap = Runtime::Current()->GetHeap();
   std::string boot_image_option("--boot-image=");
+  if (heap->GetImageSpace() == nullptr) {
+    // TODO If we get a dex2dex compiler working we could maybe use that, OTOH since we are likely
+    // out of space anyway it might not matter.
+    *error_msg = StringPrintf("Cannot create oat file for '%s' because we are running "
+                              "without an image.", dex_filename);
+    return false;
+  }
   boot_image_option += heap->GetImageSpace()->GetImageLocation();
 
   std::string dex_file_option("--dex-file=");
@@ -882,16 +898,17 @@
     oat_location = cache_location.c_str();
   }
 
+  bool has_flock = true;
   // Definitely need to lock now.
   if (!scoped_flock.HasFile()) {
     std::string error_msg;
     if (!scoped_flock.Init(oat_location, &error_msg)) {
       error_msgs->push_back(error_msg);
-      return false;
+      has_flock = false;
     }
   }
 
-  if (Runtime::Current()->IsDex2OatEnabled()) {
+  if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
     // Create the oat file.
     open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
                                                     oat_location, error_msgs));
@@ -1006,6 +1023,9 @@
                                          const InstructionSet instruction_set) {
   Runtime* runtime = Runtime::Current();
   const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+  if (image_space == nullptr) {
+    return false;
+  }
   uint32_t image_oat_checksum = 0;
   if (instruction_set == kRuntimeISA) {
     const ImageHeader& image_header = image_space->GetImageHeader();
@@ -1023,6 +1043,10 @@
                                      std::string* error_msg) {
   Runtime* runtime = Runtime::Current();
   const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+  if (image_space == nullptr) {
+    *error_msg = "No image space for verification against";
+    return false;
+  }
 
   // If the requested instruction set is the same as the current runtime,
   // we can use the checksums directly. If it isn't, we'll have to read the
@@ -1273,13 +1297,15 @@
   std::string error_msg;
   if (runtime->CanRelocate()) {
     // Run relocation
-    const std::string& image_location =
-        Runtime::Current()->GetHeap()->GetImageSpace()->GetImageLocation();
-    if (odex_checksum_verified && should_patch_system) {
-      ret = PatchAndRetrieveOat(odex_filename, cache_filename, image_location, isa, &error_msg);
-    } else if (cache_checksum_verified && should_patch_cache) {
-      CHECK(have_dalvik_cache);
-      ret = PatchAndRetrieveOat(cache_filename, cache_filename, image_location, isa, &error_msg);
+    gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetImageSpace();
+    if (space != nullptr) {
+      const std::string& image_location = space->GetImageLocation();
+      if (odex_checksum_verified && should_patch_system) {
+        ret = PatchAndRetrieveOat(odex_filename, cache_filename, image_location, isa, &error_msg);
+      } else if (cache_checksum_verified && should_patch_cache) {
+        CHECK(have_dalvik_cache);
+        ret = PatchAndRetrieveOat(cache_filename, cache_filename, image_location, isa, &error_msg);
+      }
     }
   }
   if (ret == nullptr && have_dalvik_cache && OS::FileExists(cache_filename.c_str())) {
@@ -1343,6 +1369,12 @@
                                                 const std::string& image_location,
                                                 InstructionSet isa,
                                                 std::string* error_msg) {
+  if (!Runtime::Current()->GetHeap()->HasImageSpace()) {
+    // We don't have an image space so there is no point in trying to patchoat.
+    LOG(WARNING) << "Patching of oat file '" << input_oat << "' not attempted because we are "
+                 << "running without an image. Attempting to use oat file for interpretation.";
+    return GetInterpretedOnlyOat(input_oat, isa, error_msg);
+  }
   if (!Runtime::Current()->IsDex2OatEnabled()) {
     // We don't have dex2oat so we can assume we don't have patchoat either. We should just use the
     // input_oat but make sure we only do interpretation on it's dex files.
@@ -1405,6 +1437,7 @@
   Runtime* runtime = Runtime::Current();
   int32_t real_patch_delta;
   const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+  CHECK(image_space != nullptr);
   if (isa == Runtime::Current()->GetInstructionSet()) {
     const ImageHeader& image_header = image_space->GetImageHeader();
     real_patch_delta = image_header.GetPatchDelta();
@@ -1426,6 +1459,10 @@
   void* real_image_oat_offset;
   int32_t real_patch_delta;
   const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace();
+  if (image_space == nullptr) {
+    *error_msg = "No image space present";
+    return false;
+  }
   if (isa == Runtime::Current()->GetInstructionSet()) {
     const ImageHeader& image_header = image_space->GetImageHeader();
     real_image_checksum = image_header.GetOatChecksum();
@@ -2259,8 +2296,11 @@
   }
   Runtime* runtime = Runtime::Current();
   if (!runtime->IsStarted() || runtime->UseCompileTimeClassPath()) {
-    return;  // OAT file unavailable.
+    if (runtime->IsCompiler() || runtime->GetHeap()->HasImageSpace()) {
+      return;  // OAT file unavailable.
+    }
   }
+
   const DexFile& dex_file = klass->GetDexFile();
   const DexFile::ClassDef* dex_class_def = klass->GetClassDef();
   CHECK(dex_class_def != nullptr);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index bd4a173..3adb9d4 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -60,8 +60,8 @@
   explicit ClassLinker(InternTable* intern_table);
   ~ClassLinker();
 
-  // Initialize class linker by bootstraping from dex files
-  void InitFromCompiler(const std::vector<const DexFile*>& boot_class_path)
+  // Initialize class linker by bootstraping from dex files.
+  void InitWithoutImage(const std::vector<const DexFile*>& boot_class_path)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   // Initialize class linker from one or more images.
diff --git a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc b/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
index 9a22c15..49df62d 100644
--- a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
@@ -38,7 +38,8 @@
   } else {
     result = instrumentation->GetQuickCodeFor(method);
   }
-  DCHECK(result != Runtime::Current()->GetClassLinker()->GetQuickToInterpreterBridgeTrampoline());
+  DCHECK((result != Runtime::Current()->GetClassLinker()->GetQuickToInterpreterBridgeTrampoline())
+         || !Runtime::Current()->GetHeap()->HasImageSpace());
   bool interpreter_entry = (result == GetQuickToInterpreterBridge());
   instrumentation->PushInstrumentationStackFrame(self, method->IsStatic() ? nullptr : this_object,
                                                  method, lr, interpreter_entry);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 47e1709..73907b2 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -203,15 +203,21 @@
   // Requested begin for the alloc space, to follow the mapped image and oat files
   byte* requested_alloc_space_begin = nullptr;
   if (!image_file_name.empty()) {
+    std::string error_msg;
     space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
-                                                               image_instruction_set);
-    CHECK(image_space != nullptr) << "Failed to create space for " << image_file_name;
-    AddSpace(image_space);
-    // Oat files referenced by image files immediately follow them in memory, ensure alloc space
-    // isn't going to get in the middle
-    byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
-    CHECK_GT(oat_file_end_addr, image_space->End());
-    requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
+                                                               image_instruction_set,
+                                                               &error_msg);
+    if (image_space != nullptr) {
+      AddSpace(image_space);
+      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
+      // isn't going to get in the middle
+      byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
+      CHECK_GT(oat_file_end_addr, image_space->End());
+      requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
+    } else {
+      LOG(WARNING) << "Could not create image space with image file '" << image_file_name << "'. "
+                   << "Attempting to fall back to imageless running. Error was: " << error_msg;
+    }
   }
   /*
   requested_alloc_space_begin ->     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
@@ -599,6 +605,9 @@
 }
 
 bool Heap::IsCompilingBoot() const {
+  if (!Runtime::Current()->IsCompiler()) {
+    return false;
+  }
   for (const auto& space : continuous_spaces_) {
     if (space->IsImageSpace() || space->IsZygoteSpace()) {
       return false;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 1d10af2..0139ecc 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -283,8 +283,8 @@
 }
 
 ImageSpace* ImageSpace::Create(const char* image_location,
-                               const InstructionSet image_isa) {
-  std::string error_msg;
+                               const InstructionSet image_isa,
+                               std::string* error_msg) {
   std::string system_filename;
   bool has_system = false;
   std::string cache_filename;
@@ -296,14 +296,18 @@
 
   ImageSpace* space;
   bool relocate = Runtime::Current()->ShouldRelocate();
+  bool can_compile = Runtime::Current()->IsImageDex2OatEnabled();
   if (found_image) {
     const std::string* image_filename;
     bool is_system = false;
     bool relocated_version_used = false;
     if (relocate) {
-      CHECK(dalvik_cache_exists) << "Requiring relocation for image " << image_location << " "
-                                 << "at " << system_filename << " but we do not have any "
-                                 << "dalvik_cache to find/place it in.";
+      if (!dalvik_cache_exists) {
+        *error_msg = StringPrintf("Requiring relocation for image '%s' at '%s' but we do not have "
+                                  "any dalvik_cache to find/place it in.",
+                                  image_location, system_filename.c_str());
+        return nullptr;
+      }
       if (has_system) {
         if (has_cache && ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) {
           // We already have a relocated version
@@ -311,14 +315,20 @@
           relocated_version_used = true;
         } else {
           // We cannot have a relocated version, Relocate the system one and use it.
-          if (RelocateImage(image_location, cache_filename.c_str(), image_isa,
-                            &error_msg)) {
+          if (can_compile && RelocateImage(image_location, cache_filename.c_str(), image_isa,
+                                           error_msg)) {
             relocated_version_used = true;
             image_filename = &cache_filename;
           } else {
-            LOG(FATAL) << "Unable to relocate image " << image_location << " "
-                       << "from " << system_filename << " to " << cache_filename << ": "
-                       << error_msg;
+            std::string reason;
+            if (can_compile) {
+              reason = StringPrintf(": %s", error_msg->c_str());
+            } else {
+              reason = " because image dex2oat is disabled.";
+            }
+            *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s'%s",
+                                      image_location, system_filename.c_str(),
+                                      cache_filename.c_str(), reason.c_str());
             return nullptr;
           }
         }
@@ -351,7 +361,7 @@
       // descriptor (and the associated exclusive lock) to be released when
       // we leave Create.
       ScopedFlock image_lock;
-      image_lock.Init(image_filename->c_str(), &error_msg);
+      image_lock.Init(image_filename->c_str(), error_msg);
       LOG(INFO) << "Using image file " << image_filename->c_str() << " for image location "
                 << image_location;
       // If we are in /system we can assume the image is good. We can also
@@ -359,7 +369,7 @@
       // matches) since this is only different by the offset. We need this to
       // make sure that host tests continue to work.
       space = ImageSpace::Init(image_filename->c_str(), image_location,
-                               !(is_system || relocated_version_used), &error_msg);
+                               !(is_system || relocated_version_used), error_msg);
     }
     if (space != nullptr) {
       return space;
@@ -374,29 +384,38 @@
                  << "but image failed to load: " << error_msg;
       return nullptr;
     } else if (is_system) {
-      LOG(FATAL) << "Failed to load /system image '" << *image_filename << "': " << error_msg;
+      *error_msg = StringPrintf("Failed to load /system image '%s': %s",
+                                image_filename->c_str(), error_msg->c_str());
       return nullptr;
     } else {
-      LOG(WARNING) << error_msg;
+      LOG(WARNING) << *error_msg;
     }
   }
 
-  CHECK(dalvik_cache_exists) << "No place to put generated image.";
-  CHECK(GenerateImage(cache_filename, &error_msg))
-      << "Failed to generate image '" << cache_filename << "': " << error_msg;
-  {
+  if (!can_compile) {
+    *error_msg = "Not attempting to compile image because -Xnoimage-dex2oat";
+    return nullptr;
+  } else if (!dalvik_cache_exists) {
+    *error_msg = StringPrintf("No place to put generated image.");
+    return nullptr;
+  } else if (!GenerateImage(cache_filename, error_msg)) {
+    *error_msg = StringPrintf("Failed to generate image '%s': %s",
+                              cache_filename.c_str(), error_msg->c_str());
+    return nullptr;
+  } else {
     // Note that we must not use the file descriptor associated with
     // ScopedFlock::GetFile to Init the image file. We want the file
     // descriptor (and the associated exclusive lock) to be released when
     // we leave Create.
     ScopedFlock image_lock;
-    image_lock.Init(cache_filename.c_str(), &error_msg);
-    space = ImageSpace::Init(cache_filename.c_str(), image_location, true, &error_msg);
+    image_lock.Init(cache_filename.c_str(), error_msg);
+    space = ImageSpace::Init(cache_filename.c_str(), image_location, true, error_msg);
+    if (space == nullptr) {
+      *error_msg = StringPrintf("Failed to load generated image '%s': %s",
+                                cache_filename.c_str(), error_msg->c_str());
+    }
+    return space;
   }
-  if (space == nullptr) {
-    LOG(FATAL) << "Failed to load generated image '" << cache_filename << "': " << error_msg;
-  }
-  return space;
 }
 
 void ImageSpace::VerifyImageAllocations() {
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 6be3b8f..28ebca6 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -43,7 +43,7 @@
   // 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)
+  static ImageSpace* Create(const char* image, InstructionSet image_isa, std::string* error_msg)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   // Reads the image header from the specified image location for the
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 9818385..bb2ad44 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -230,6 +230,7 @@
   is_zygote_ = false;
   must_relocate_ = kDefaultMustRelocate;
   dex2oat_enabled_ = true;
+  image_dex2oat_enabled_ = true;
   if (kPoisonHeapReferences) {
     // kPoisonHeapReferences currently works only with the interpreter only.
     // TODO: make it work with the compiler.
@@ -442,6 +443,10 @@
       dex2oat_enabled_ = false;
     } else if (option == "-Xdex2oat") {
       dex2oat_enabled_ = true;
+    } else if (option == "-Xnoimage-dex2oat") {
+      image_dex2oat_enabled_ = false;
+    } else if (option == "-Ximage-dex2oat") {
+      image_dex2oat_enabled_ = true;
     } else if (option == "-Xint") {
       interpreter_only_ = true;
     } else if (StartsWith(option, "-Xgc:")) {
@@ -806,6 +811,7 @@
   UsageMessage(stream, "  -Xpatchoat:filename\n");
   UsageMessage(stream, "  -X[no]relocate\n");
   UsageMessage(stream, "  -X[no]dex2oat (Whether to invoke dex2oat on the application)\n");
+  UsageMessage(stream, "  -X[no]image-dex2oat (Whether to create and use a boot image)\n");
   UsageMessage(stream, "\n");
 
   UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n");
diff --git a/runtime/parsed_options.h b/runtime/parsed_options.h
index f484e78..86056c5 100644
--- a/runtime/parsed_options.h
+++ b/runtime/parsed_options.h
@@ -50,6 +50,7 @@
   bool is_zygote_;
   bool must_relocate_;
   bool dex2oat_enabled_;
+  bool image_dex2oat_enabled_;
   std::string patchoat_executable_;
   bool interpreter_only_;
   bool is_explicit_gc_disabled_;
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 0f419b1..1744218 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -102,6 +102,7 @@
       is_concurrent_gc_enabled_(true),
       is_explicit_gc_disabled_(false),
       dex2oat_enabled_(true),
+      image_dex2oat_enabled_(true),
       default_stack_size_(0),
       heap_(nullptr),
       max_spins_before_thin_lock_inflation_(Monitor::kDefaultMaxSpinsBeforeThinLockInflation),
@@ -401,10 +402,18 @@
 
   // Restore main thread state to kNative as expected by native code.
   Thread* self = Thread::Current();
+
   self->TransitionFromRunnableToSuspended(kNative);
 
   started_ = true;
 
+  if (!IsImageDex2OatEnabled() || !Runtime::Current()->GetHeap()->HasImageSpace()) {
+    ScopedObjectAccess soa(Thread::Current());
+    StackHandleScope<1> hs(soa.Self());
+    auto klass(hs.NewHandle<mirror::Class>(mirror::Class::GetJavaLangClass()));
+    class_linker_->EnsureInitialized(klass, true, true);
+  }
+
   // InitNativeMethods needs to be after started_ so that the classes
   // it touches will have methods linked to the oat file if necessary.
   InitNativeMethods();
@@ -540,6 +549,24 @@
   VLOG(startup) << "Runtime::StartDaemonThreads exiting";
 }
 
+static size_t OpenDexFiles(const std::vector<std::string>& dex_filenames,
+                           std::vector<const DexFile*>& dex_files) {
+  size_t failure_count = 0;
+  for (size_t i = 0; i < dex_filenames.size(); i++) {
+    const char* dex_filename = dex_filenames[i].c_str();
+    std::string error_msg;
+    if (!OS::FileExists(dex_filename)) {
+      LOG(WARNING) << "Skipping non-existent dex file '" << dex_filename << "'";
+      continue;
+    }
+    if (!DexFile::Open(dex_filename, dex_filename, &error_msg, &dex_files)) {
+      LOG(WARNING) << "Failed to open .dex from file '" << dex_filename << "': " << error_msg;
+      ++failure_count;
+    }
+  }
+  return failure_count;
+}
+
 bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
   CHECK_EQ(sysconf(_SC_PAGE_SIZE), kPageSize);
 
@@ -564,6 +591,7 @@
   is_zygote_ = options->is_zygote_;
   is_explicit_gc_disabled_ = options->is_explicit_gc_disabled_;
   dex2oat_enabled_ = options->dex2oat_enabled_;
+  image_dex2oat_enabled_ = options->image_dex2oat_enabled_;
 
   vfprintf_ = options->hook_vfprintf_;
   exit_ = options->hook_exit_;
@@ -683,10 +711,24 @@
     if (kIsDebugBuild) {
       GetHeap()->GetImageSpace()->VerifyImageAllocations();
     }
+  } else if (!IsCompiler() || !image_dex2oat_enabled_) {
+    std::vector<std::string> dex_filenames;
+    Split(boot_class_path_string_, ':', dex_filenames);
+    std::vector<const DexFile*> boot_class_path;
+    OpenDexFiles(dex_filenames, boot_class_path);
+    class_linker_->InitWithoutImage(boot_class_path);
+    // TODO: Should we move the following to InitWithoutImage?
+    SetInstructionSet(kRuntimeISA);
+    for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) {
+      Runtime::CalleeSaveType type = Runtime::CalleeSaveType(i);
+      if (!HasCalleeSaveMethod(type)) {
+        SetCalleeSaveMethod(CreateCalleeSaveMethod(type), type);
+      }
+    }
   } else {
     CHECK(options->boot_class_path_ != NULL);
     CHECK_NE(options->boot_class_path_->size(), 0U);
-    class_linker_->InitFromCompiler(*options->boot_class_path_);
+    class_linker_->InitWithoutImage(*options->boot_class_path_);
   }
   CHECK(class_linker_ != NULL);
   verifier::MethodVerifier::Init();
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 259691a..b0a88d5 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -107,7 +107,11 @@
   }
 
   bool IsDex2OatEnabled() const {
-    return dex2oat_enabled_;
+    return dex2oat_enabled_ && IsImageDex2OatEnabled();
+  }
+
+  bool IsImageDex2OatEnabled() const {
+    return image_dex2oat_enabled_;
   }
 
   CompilerCallbacks* GetCompilerCallbacks() {
@@ -509,6 +513,7 @@
   bool is_concurrent_gc_enabled_;
   bool is_explicit_gc_disabled_;
   bool dex2oat_enabled_;
+  bool image_dex2oat_enabled_;
 
   std::string compiler_executable_;
   std::string patchoat_executable_;
diff --git a/test/118-noimage-dex2oat/expected.txt b/test/118-noimage-dex2oat/expected.txt
new file mode 100644
index 0000000..472a5f2
--- /dev/null
+++ b/test/118-noimage-dex2oat/expected.txt
@@ -0,0 +1,6 @@
+Run -Xnoimage-dex2oat
+Has image is false, is image dex2oat enabled is false.
+Run -Ximage-dex2oat
+Has image is true, is image dex2oat enabled is true.
+Run default
+Has image is true, is image dex2oat enabled is true.
diff --git a/test/118-noimage-dex2oat/info.txt b/test/118-noimage-dex2oat/info.txt
new file mode 100644
index 0000000..fc102ac
--- /dev/null
+++ b/test/118-noimage-dex2oat/info.txt
@@ -0,0 +1 @@
+Test that disables dex2oat'ing the image.
diff --git a/test/118-noimage-dex2oat/noimage-dex2oat.cc b/test/118-noimage-dex2oat/noimage-dex2oat.cc
new file mode 100644
index 0000000..4a3d33c
--- /dev/null
+++ b/test/118-noimage-dex2oat/noimage-dex2oat.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "class_linker.h"
+#include "dex_file-inl.h"
+#include "mirror/class-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread.h"
+
+namespace art {
+
+class NoDex2OatTest {
+ public:
+  static bool hasOat(jclass cls) {
+    ScopedObjectAccess soa(Thread::Current());
+    mirror::Class* klass = soa.Decode<mirror::Class*>(cls);
+    const DexFile& dex_file = klass->GetDexFile();
+    const OatFile* oat_file =
+        Runtime::Current()->GetClassLinker()->FindOpenedOatFileForDexFile(dex_file);
+    return oat_file != nullptr;
+  }
+};
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasImage(JNIEnv*, jclass cls) {
+  return Runtime::Current()->GetHeap()->HasImageSpace();
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isImageDex2OatEnabled(JNIEnv*, jclass cls) {
+  return Runtime::Current()->IsImageDex2OatEnabled();
+}
+
+}  // namespace art
diff --git a/test/118-noimage-dex2oat/run b/test/118-noimage-dex2oat/run
new file mode 100644
index 0000000..911abdf
--- /dev/null
+++ b/test/118-noimage-dex2oat/run
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Force relocation otherwise we will just use the already created core.oat/art pair.
+flags="${@/--no-relocate/--relocate}"
+
+# Use the non-prebuild script.
+RUN="${RUN/push-and-run-prebuilt-test-jar/push-and-run-test-jar}"
+
+if [ $(basename $RUN) == 'host-run-test-jar' ]; then
+  BPATH="--runtime-option -Xbootclasspath:$ANDROID_HOST_OUT/../common/obj/JAVA_LIBRARIES/core-libart-hostdex_intermediates/javalib.jar"
+  # Remove prebuild from the flags, this test is for testing not having oat files.
+  flags="${flags/--prebuild/}"
+else
+  BPATH="--runtime-option -Xbootclasspath:/system/framework/core-libart.jar"
+fi
+
+# Make sure we can run without an oat file,
+echo "Run -Xnoimage-dex2oat"
+${RUN} ${flags} ${BPATH} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat
+
+# Make sure we can run with the oat file.
+echo "Run -Ximage-dex2oat"
+${RUN} ${flags} ${BPATH} --runtime-option -Ximage-dex2oat
+
+# Make sure we can run with the default settings.
+echo "Run default"
+${RUN} ${flags} ${BPATH}
diff --git a/test/118-noimage-dex2oat/src/Main.java b/test/118-noimage-dex2oat/src/Main.java
new file mode 100644
index 0000000..11c736a
--- /dev/null
+++ b/test/118-noimage-dex2oat/src/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) {
+    boolean hasImage = hasImage();
+    System.out.println(
+        "Has image is " + hasImage + ", is image dex2oat enabled is "
+        + isImageDex2OatEnabled() + ".");
+
+    if (hasImage && !isImageDex2OatEnabled()) {
+      throw new Error("Image with dex2oat disabled runs with an oat file");
+    } else if (!hasImage && isImageDex2OatEnabled()) {
+      throw new Error("Image with dex2oat enabled runs without an oat file");
+    }
+  }
+
+  static {
+    System.loadLibrary("arttest");
+  }
+
+  private native static boolean hasImage();
+
+  private native static boolean isImageDex2OatEnabled();
+}
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index caaf649..2d139a6 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -25,7 +25,8 @@
   004-StackWalk/stack_walk_jni.cc \
   004-UnsafeTest/unsafe_test.cc \
   116-nodex2oat/nodex2oat.cc \
-  117-nopatchoat/nopatchoat.cc
+  117-nopatchoat/nopatchoat.cc \
+  118-noimage-dex2oat/noimage-dex2oat.cc
 
 ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so
 ifdef TARGET_2ND_ARCH