Make --debuggable rely on JIT code.

Removes -Xfully-deoptable in the process, which was added as a
temporary workaround until this CL.
Partial revert of https://android-review.googlesource.com/#/c/302232/

Makes things consistent with existing infrastructure:

- Parse the --debuggable from the compiler options, just like
  --compiler-filter.

- Add DEBUG_JAVA_DEBUGGABLE, passed by the zygote, for debuggable apps.

- Java debuggable now solely relies on JIT for simplicity.

- Debugging under userdebug for non-java-debuggable apps is still
  best effort.

Test: test-art-host, jdwp

bug: 28769520
Change-Id: Id0593aacd85b9780da97f20914a50943957c858f
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 148ce4f..eaac0b4 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -102,7 +102,7 @@
       /* no_inline_from */ nullptr,
       /* include_patch_information */ false,
       CompilerOptions::kDefaultTopKProfileThreshold,
-      Runtime::Current()->IsDebuggable(),
+      Runtime::Current()->IsJavaDebuggable(),
       CompilerOptions::kDefaultGenerateDebugInfo,
       /* implicit_null_checks */ true,
       /* implicit_so_checks */ true,
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index e86e560..864ca76 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -125,7 +125,7 @@
       class_path = OatFile::kSpecialSharedLibrary;
     }
     argv.push_back(class_path);
-    if (runtime->IsDebuggable()) {
+    if (runtime->IsJavaDebuggable()) {
       argv.push_back("--debuggable");
     }
     runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 02b26c6..edd6e3b 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2862,9 +2862,12 @@
     return true;
   }
 
-  if (runtime->IsFullyDeoptable()) {
-    // We need to be able to deoptimize at any time so we should always just ignore precompiled
-    // code and go to the interpreter assuming we don't already have jitted code.
+  if (runtime->IsJavaDebuggable()) {
+    // For simplicity, we ignore precompiled code and go to the interpreter
+    // assuming we don't already have jitted code.
+    // We could look at the oat file where `quick_code` is being defined,
+    // and check whether it's been compiled debuggable, but we decided to
+    // only rely on the JIT for debuggable apps.
     jit::Jit* jit = Runtime::Current()->GetJit();
     return (jit == nullptr) || !jit->GetCodeCache()->ContainsPc(quick_code);
   }
@@ -2872,18 +2875,13 @@
   if (runtime->IsNativeDebuggable()) {
     DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse());
     // If we are doing native debugging, ignore application's AOT code,
-    // since we want to JIT it with extra stackmaps for native debugging.
-    // On the other hand, keep all AOT code from the boot image, since the
-    // blocking JIT would results in non-negligible performance impact.
+    // since we want to JIT it (at first use) with extra stackmaps for native
+    // debugging. We keep however all AOT code from the boot image,
+    // since the JIT-at-first-use is blocking and would result in non-negligible
+    // startup performance impact.
     return !runtime->GetHeap()->IsInBootImageOatFile(quick_code);
   }
 
-  if (Dbg::IsDebuggerActive()) {
-    // Boot image classes may be AOT-compiled as non-debuggable.
-    // This is not suitable for the Java debugger, so ignore the AOT code.
-    return runtime->GetHeap()->IsInBootImageOatFile(quick_code);
-  }
-
   return false;
 }
 
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 22a3163..1a0cec0 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -588,29 +588,6 @@
   return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly();
 }
 
-// Used to patch boot image method entry point to interpreter bridge.
-class UpdateEntryPointsClassVisitor : public ClassVisitor {
- public:
-  explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation)
-      : instrumentation_(instrumentation) {}
-
-  bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) {
-    auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
-    for (auto& m : klass->GetMethods(pointer_size)) {
-      const void* code = m.GetEntryPointFromQuickCompiledCode();
-      if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
-          !m.IsNative() &&
-          !m.IsProxyMethod()) {
-        instrumentation_->UpdateMethodsCodeFromDebugger(&m, GetQuickToInterpreterBridge());
-      }
-    }
-    return true;
-  }
-
- private:
-  instrumentation::Instrumentation* const instrumentation_;
-};
-
 void Dbg::GoActive() {
   // Enable all debugging features, including scans for breakpoints.
   // This is a no-op if we're already active.
@@ -639,14 +616,16 @@
   }
 
   Runtime* runtime = Runtime::Current();
-  // Since boot image code may be AOT compiled as not debuggable, we need to patch
-  // entry points of methods in boot image to interpreter bridge.
-  // However, the performance cost of this is non-negligible during native-debugging due to the
+  // Best effort deoptimization if the runtime is non-Java debuggable. This happens when
+  // ro.debuggable is set, but the application is not debuggable, or when a standalone
+  // dalvikvm invocation is not passed the debuggable option (-Xcompiler-option --debuggable).
+  //
+  // The performance cost of this is non-negligible during native-debugging due to the
   // forced JIT, so we keep the AOT code in that case in exchange for limited native debugging.
-  if (!runtime->GetInstrumentation()->IsForcedInterpretOnly() && !runtime->IsNativeDebuggable()) {
-    ScopedObjectAccess soa(self);
-    UpdateEntryPointsClassVisitor visitor(runtime->GetInstrumentation());
-    runtime->GetClassLinker()->VisitClasses(&visitor);
+  if (!runtime->IsJavaDebuggable() &&
+      !runtime->GetInstrumentation()->IsForcedInterpretOnly() &&
+      !runtime->IsNativeDebuggable()) {
+    runtime->DeoptimizeBootImage();
   }
 
   ScopedSuspendAll ssa(__FUNCTION__);
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index eb76fb6..bde9009 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -781,15 +781,19 @@
   // If caller_pc is the instrumentation exit stub, the stub will check to see if deoptimization
   // should be done and it knows the real return pc.
   if (UNLIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) &&
-               Dbg::IsForcedInterpreterNeededForUpcall(self, caller) &&
-               Runtime::Current()->IsDeoptimizeable(caller_pc))) {
-    // Push the context of the deoptimization stack so we can restore the return value and the
-    // exception before executing the deoptimized frames.
-    self->PushDeoptimizationContext(
-        result, shorty[0] == 'L', /* from_code */ false, self->GetException());
+               Dbg::IsForcedInterpreterNeededForUpcall(self, caller))) {
+    if (!Runtime::Current()->IsAsyncDeoptimizeable(caller_pc)) {
+      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+                   << caller->PrettyMethod();
+    } else {
+      // Push the context of the deoptimization stack so we can restore the return value and the
+      // exception before executing the deoptimized frames.
+      self->PushDeoptimizationContext(
+          result, shorty[0] == 'L', /* from_code */ false, self->GetException());
 
-    // Set special exception to cause deoptimization.
-    self->SetException(Thread::GetDeoptimizationException());
+      // Set special exception to cause deoptimization.
+      self->SetException(Thread::GetDeoptimizationException());
+    }
   }
 
   // No need to restore the args since the method has already been run by the interpreter.
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index bbd6d35..3005c2a 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -105,10 +105,9 @@
   method->SetEntryPointFromQuickCompiledCode(quick_code);
 }
 
-bool Instrumentation::NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_) {
   return Dbg::IsDebuggerActive() &&
-         Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
+         Runtime::Current()->IsJavaDebuggable() &&
          !method->IsNative() &&
          !method->IsProxyMethod();
 }
@@ -132,9 +131,10 @@
     if ((forced_interpret_only_ || IsDeoptimized(method)) && !method->IsNative()) {
       new_quick_code = GetQuickToInterpreterBridge();
     } else if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) {
-      new_quick_code = class_linker->GetQuickOatCodeFor(method);
-      if (NeedDebugVersionForBootImageCode(method, new_quick_code)) {
+      if (NeedDebugVersionFor(method)) {
         new_quick_code = GetQuickToInterpreterBridge();
+      } else {
+        new_quick_code = class_linker->GetQuickOatCodeFor(method);
       }
     } else {
       new_quick_code = GetQuickResolutionStub();
@@ -148,13 +148,14 @@
       // class, all its static methods code will be set to the instrumentation entry point.
       // For more details, see ClassLinker::FixupStaticTrampolines.
       if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) {
-        new_quick_code = class_linker->GetQuickOatCodeFor(method);
-        if (NeedDebugVersionForBootImageCode(method, new_quick_code)) {
+        if (NeedDebugVersionFor(method)) {
           // Oat code should not be used. Don't install instrumentation stub and
           // use interpreter for instrumentation.
           new_quick_code = GetQuickToInterpreterBridge();
         } else if (entry_exit_stubs_installed_) {
           new_quick_code = GetQuickInstrumentationEntryPoint();
+        } else {
+          new_quick_code = class_linker->GetQuickOatCodeFor(method);
         }
       } else {
         new_quick_code = GetQuickResolutionStub();
@@ -731,10 +732,12 @@
   UpdateMethodsCodeImpl(method, quick_code);
 }
 
-void Instrumentation::UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code) {
-  // When debugger attaches, we may update the entry points of all methods of a class
-  // to the interpreter bridge. A method's declaring class might not be in resolved
-  // state yet in that case.
+void Instrumentation::UpdateMethodsCodeForJavaDebuggable(ArtMethod* method,
+                                                         const void* quick_code) {
+  // When the runtime is set to Java debuggable, we may update the entry points of
+  // all methods of a class to the interpreter bridge. A method's declaring class
+  // might not be in resolved state yet in that case, so we bypass the DCHECK in
+  // UpdateMethodsCode.
   UpdateMethodsCodeImpl(method, quick_code);
 }
 
@@ -819,10 +822,9 @@
         !method->GetDeclaringClass()->IsInitialized()) {
       UpdateEntrypoints(method, GetQuickResolutionStub());
     } else {
-      const void* quick_code = class_linker->GetQuickOatCodeFor(method);
-      if (NeedDebugVersionForBootImageCode(method, quick_code)) {
-        quick_code = GetQuickToInterpreterBridge();
-      }
+      const void* quick_code = NeedDebugVersionFor(method)
+          ? GetQuickToInterpreterBridge()
+          : class_linker->GetQuickOatCodeFor(method);
       UpdateEntrypoints(method, quick_code);
     }
 
@@ -1114,7 +1116,7 @@
   bool deoptimize = (visitor.caller != nullptr) &&
                     (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) ||
                     Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller));
-  if (deoptimize && Runtime::Current()->IsDeoptimizeable(*return_pc)) {
+  if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) {
     if (kVerboseInstrumentation) {
       LOG(INFO) << "Deoptimizing "
                 << visitor.caller->PrettyMethod()
@@ -1132,6 +1134,10 @@
     return GetTwoWordSuccessValue(*return_pc,
                                   reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
   } else {
+    if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) {
+      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod()
+                   << " at PC " << reinterpret_cast<void*>(*return_pc);
+    }
     if (kVerboseInstrumentation) {
       LOG(INFO) << "Returning from " << method->PrettyMethod()
                 << " to PC " << reinterpret_cast<void*>(*return_pc);
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 05c0aaa..39606f7 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -239,7 +239,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
 
   // Update the code of a method respecting any installed stubs from debugger.
-  void UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code)
+  void UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, const void* quick_code)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
 
   // Get the quick code for the given method. More efficient than asking the class linker as it
@@ -264,7 +264,7 @@
 
   // Code is in boot image oat file which isn't compiled as debuggable.
   // Need debug version (interpreter or jitted) if that's the case.
-  bool NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const
+  bool NeedDebugVersionFor(ArtMethod* method) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool AreExitStubsInstalled() const {
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 6336cdd..45611a9 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -594,7 +594,7 @@
       VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions.";
       return nullptr;
     }
-    DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable())
+    DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsJavaDebuggable())
         << "Should not be using cha on debuggable apps/runs!";
 
     for (ArtMethod* single_impl : cha_single_implementation_list) {
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 10fc90b..fd22d9e 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -71,7 +71,7 @@
 static void EnableDebugFeatures(uint32_t debug_flags) {
   // Must match values in com.android.internal.os.Zygote.
   enum {
-    DEBUG_ENABLE_DEBUGGER           = 1,
+    DEBUG_ENABLE_JDWP               = 1,
     DEBUG_ENABLE_CHECKJNI           = 1 << 1,
     DEBUG_ENABLE_ASSERT             = 1 << 2,
     DEBUG_ENABLE_SAFEMODE           = 1 << 3,
@@ -79,6 +79,7 @@
     DEBUG_GENERATE_DEBUG_INFO       = 1 << 5,
     DEBUG_ALWAYS_JIT                = 1 << 6,
     DEBUG_NATIVE_DEBUGGABLE         = 1 << 7,
+    DEBUG_JAVA_DEBUGGABLE           = 1 << 8,
   };
 
   Runtime* const runtime = Runtime::Current();
@@ -100,11 +101,11 @@
     debug_flags &= ~DEBUG_ENABLE_JNI_LOGGING;
   }
 
-  Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0);
-  if ((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0) {
+  Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_JDWP) != 0);
+  if ((debug_flags & DEBUG_ENABLE_JDWP) != 0) {
     EnableDebugger();
   }
-  debug_flags &= ~DEBUG_ENABLE_DEBUGGER;
+  debug_flags &= ~DEBUG_ENABLE_JDWP;
 
   const bool safe_mode = (debug_flags & DEBUG_ENABLE_SAFEMODE) != 0;
   if (safe_mode) {
@@ -130,6 +131,14 @@
     debug_flags &= ~DEBUG_ALWAYS_JIT;
   }
 
+  if ((debug_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {
+    runtime->AddCompilerOption("--debuggable");
+    runtime->SetJavaDebuggable(true);
+    // Deoptimize the boot image as it may be non-debuggable.
+    runtime->DeoptimizeBootImage();
+    debug_flags &= ~DEBUG_JAVA_DEBUGGABLE;
+  }
+
   if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) {
     runtime->AddCompilerOption("--debuggable");
     runtime->AddCompilerOption("--generate-debug-info");
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 8554fa2..b19ace5 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -530,7 +530,7 @@
     class_path = OatFile::kSpecialSharedLibrary;
   }
   argv.push_back(class_path);
-  if (runtime->IsDebuggable()) {
+  if (runtime->IsJavaDebuggable()) {
     argv.push_back("--debuggable");
   }
   runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index ccc5f7a..9113f83 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -300,8 +300,6 @@
       .Define("-Xplugin:_")
           .WithType<std::vector<Plugin>>().AppendValues()
           .IntoKey(M::Plugins)
-      .Define("-Xfully-deoptable")
-          .IntoKey(M::FullyDeoptable)
       .Define("-XX:ThreadSuspendTimeout=_")  // in ms
           .WithType<MillisecondsToNanoseconds>()  // store as ns
           .IntoKey(M::ThreadSuspendTimeout)
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index b809c3e..110c74b 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -347,9 +347,11 @@
       callee_method_ = method;
       return true;
     } else if (!single_frame_deopt_ &&
-               !Runtime::Current()->IsDeoptimizeable(GetCurrentQuickFramePc())) {
+               !Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) {
       // We hit some code that's not deoptimizeable. However, Single-frame deoptimization triggered
       // from compiled code is always allowed since HDeoptimize always saves the full environment.
+      LOG(WARNING) << "Got request to deoptimize un-deoptimizable method "
+                   << method->PrettyMethod();
       FinishStackWalk();
       return false;  // End stack walk.
     } else {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 5e008a8..0bf3018 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -244,7 +244,7 @@
       force_native_bridge_(false),
       is_native_bridge_loaded_(false),
       is_native_debuggable_(false),
-      is_fully_deoptable_(false),
+      is_java_debuggable_(false),
       zygote_max_failed_boots_(0),
       experimental_flags_(ExperimentalFlags::kNone),
       oat_file_manager_(nullptr),
@@ -825,14 +825,6 @@
   return IsShuttingDownLocked();
 }
 
-bool Runtime::IsDebuggable() const {
-  if (IsFullyDeoptable()) {
-    return true;
-  }
-  const OatFile* oat_file = GetOatFileManager().GetPrimaryOatFile();
-  return oat_file != nullptr && oat_file->IsDebuggable();
-}
-
 void Runtime::StartDaemonThreads() {
   ScopedTrace trace(__FUNCTION__);
   VLOG(startup) << "Runtime::StartDaemonThreads entering";
@@ -1038,6 +1030,12 @@
 
   compiler_executable_ = runtime_options.ReleaseOrDefault(Opt::Compiler);
   compiler_options_ = runtime_options.ReleaseOrDefault(Opt::CompilerOptions);
+  for (StringPiece option : Runtime::Current()->GetCompilerOptions()) {
+    if (option.starts_with("--debuggable")) {
+      SetJavaDebuggable(true);
+      break;
+    }
+  }
   image_compiler_options_ = runtime_options.ReleaseOrDefault(Opt::ImageCompilerOptions);
   image_location_ = runtime_options.GetOrDefault(Opt::Image);
 
@@ -1052,8 +1050,6 @@
   verify_ = runtime_options.GetOrDefault(Opt::Verify);
   allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback);
 
-  is_fully_deoptable_ = runtime_options.Exists(Opt::FullyDeoptable);
-
   no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain);
   force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge);
 
@@ -1258,6 +1254,11 @@
       ScopedTrace trace2("AddImageStringsToTable");
       GetInternTable()->AddImagesStringsToTable(heap_->GetBootImageSpaces());
     }
+    if (IsJavaDebuggable()) {
+      // Now that we have loaded the boot image, deoptimize its methods if we are running
+      // debuggable, as the code may have been compiled non-debuggable.
+      DeoptimizeBootImage();
+    }
   } else {
     std::vector<std::string> dex_filenames;
     Split(boot_class_path_string_, ':', &dex_filenames);
@@ -1404,7 +1405,7 @@
   }
 
   // Is the process debuggable? Otherwise, do not attempt to load the plugin.
-  if (!runtime->IsDebuggable()) {
+  if (!runtime->IsJavaDebuggable()) {
     *error_msg = "Process is not debuggable.";
     return false;
   }
@@ -2204,9 +2205,15 @@
   return verify_ == verifier::VerifyMode::kSoftFail;
 }
 
-bool Runtime::IsDeoptimizeable(uintptr_t code) const
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  return !heap_->IsInBootImageOatFile(reinterpret_cast<void *>(code));
+bool Runtime::IsAsyncDeoptimizeable(uintptr_t code) const {
+  // We only support async deopt (ie the compiled code is not explicitly asking for
+  // deopt, but something else like the debugger) in debuggable JIT code.
+  // We could look at the oat file where `code` is being defined,
+  // and check whether it's been compiled debuggable, but we decided to
+  // only rely on the JIT for debuggable apps.
+  return IsJavaDebuggable() &&
+      GetJit() != nullptr &&
+      GetJit()->GetCodeCache()->ContainsPc(reinterpret_cast<const void*>(code));
 }
 
 LinearAlloc* Runtime::CreateLinearAlloc() {
@@ -2290,4 +2297,43 @@
   return callbacks_.get();
 }
 
+// Used to patch boot image method entry point to interpreter bridge.
+class UpdateEntryPointsClassVisitor : public ClassVisitor {
+ public:
+  explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation)
+      : instrumentation_(instrumentation) {}
+
+  bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+    for (auto& m : klass->GetMethods(pointer_size)) {
+      const void* code = m.GetEntryPointFromQuickCompiledCode();
+      if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
+          !m.IsNative() &&
+          !m.IsProxyMethod()) {
+        instrumentation_->UpdateMethodsCodeForJavaDebuggable(&m, GetQuickToInterpreterBridge());
+      }
+    }
+    return true;
+  }
+
+ private:
+  instrumentation::Instrumentation* const instrumentation_;
+};
+
+void Runtime::SetJavaDebuggable(bool value) {
+  is_java_debuggable_ = value;
+  // Do not call DeoptimizeBootImage just yet, the runtime may still be starting up.
+}
+
+void Runtime::DeoptimizeBootImage() {
+  // If we've already started and we are setting this runtime to debuggable,
+  // we patch entry points of methods in boot image to interpreter bridge, as
+  // boot image code may be AOT compiled as not debuggable.
+  if (!GetInstrumentation()->IsForcedInterpretOnly()) {
+    ScopedObjectAccess soa(Thread::Current());
+    UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
+    GetClassLinker()->VisitClasses(&visitor);
+  }
+}
+
 }  // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index f7d6810..30b1756 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -434,7 +434,7 @@
     kInitialize
   };
 
-  jit::Jit* GetJit() {
+  jit::Jit* GetJit() const {
     return jit_.get();
   }
 
@@ -569,15 +569,14 @@
     return jit_options_.get();
   }
 
-  bool IsDebuggable() const;
-
-  bool IsFullyDeoptable() const {
-    return is_fully_deoptable_;
+  bool IsJavaDebuggable() const {
+    return is_java_debuggable_;
   }
 
-  void SetFullyDeoptable(bool value) {
-    is_fully_deoptable_ = value;
-  }
+  void SetJavaDebuggable(bool value);
+
+  // Deoptimize the boot image, called for Java debuggable apps.
+  void DeoptimizeBootImage();
 
   bool IsNativeDebuggable() const {
     return is_native_debuggable_;
@@ -639,9 +638,9 @@
     return zygote_no_threads_;
   }
 
-  // Returns if the code can be deoptimized. Code may be compiled with some
+  // Returns if the code can be deoptimized asynchronously. Code may be compiled with some
   // optimization that makes it impossible to deoptimize.
-  bool IsDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsAsyncDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns a saved copy of the environment (getenv/setenv values).
   // Used by Fork to protect against overwriting LD_LIBRARY_PATH, etc.
@@ -863,8 +862,8 @@
   // Whether we are running under native debugger.
   bool is_native_debuggable_;
 
-  // Whether we are expected to be deoptable at all points.
-  bool is_fully_deoptable_;
+  // Whether Java code needs to be debuggable.
+  bool is_java_debuggable_;
 
   // The maximum number of failed boots we allow before pruning the dalvik cache
   // and trying again. This option is only inspected when we're running as a
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index ad748b0..e68a1b2 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -123,7 +123,6 @@
 RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>,         AgentLib)  // -agentlib:<libname>=<options>
 RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>,         AgentPath)  // -agentpath:<libname>=<options>
 RUNTIME_OPTIONS_KEY (std::vector<Plugin>,            Plugins)  // -Xplugin:<library>
-RUNTIME_OPTIONS_KEY (Unit,                           FullyDeoptable)  // -Xfully-deoptable
 
 // Not parse-able from command line, but can be provided explicitly.
 // (Do not add anything here that is defined in ParsedOptions::MakeParser)
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 66a03a6..151e91c 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2852,13 +2852,16 @@
   if (Dbg::IsForcedInterpreterNeededForException(this)) {
     NthCallerVisitor visitor(this, 0, false);
     visitor.WalkStack();
-    if (Runtime::Current()->IsDeoptimizeable(visitor.caller_pc)) {
+    if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
       // Save the exception into the deoptimization context so it can be restored
       // before entering the interpreter.
       PushDeoptimizationContext(
           JValue(), /*is_reference */ false, /* from_code */ false, exception);
       artDeoptimize(this);
       UNREACHABLE();
+    } else {
+      LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+                   << visitor.caller->PrettyMethod();
     }
   }
 
diff --git a/test/466-get-live-vreg/get_live_vreg_jni.cc b/test/466-get-live-vreg/get_live_vreg_jni.cc
index d3a033b..6cea673 100644
--- a/test/466-get-live-vreg/get_live_vreg_jni.cc
+++ b/test/466-get-live-vreg/get_live_vreg_jni.cc
@@ -47,7 +47,7 @@
       uint32_t value = 0;
       if (GetCurrentQuickFrame() != nullptr &&
           GetCurrentOatQuickMethodHeader()->IsOptimized() &&
-          !Runtime::Current()->IsDebuggable()) {
+          !Runtime::Current()->IsJavaDebuggable()) {
         CHECK_EQ(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value), false);
       } else {
         CHECK(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value));
diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run
index 0664592..4a2eb34 100755
--- a/test/909-attach-agent/run
+++ b/test/909-attach-agent/run
@@ -22,10 +22,12 @@
 fi
 
 ./default-run "$@" --android-runtime-option -Xplugin:${plugin} \
-                   --android-runtime-option -Xfully-deoptable \
+                   --android-runtime-option -Xcompiler-option \
+                   --android-runtime-option --debuggable \
                    --args agent:${agent}=909-attach-agent
 
-./default-run "$@" --android-runtime-option -Xfully-deoptable \
+./default-run "$@" --android-runtime-option -Xcompiler-option \
+                   --android-runtime-option --debuggable \
                    --args agent:${agent}=909-attach-agent
 
 ./default-run "$@" --args agent:${agent}=909-attach-agent
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index cb798f0..4c526a2 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -605,6 +605,7 @@
 TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS := \
   454-get-vreg \
   457-regs \
+  602-deoptimizeable
 
 ifneq (,$(filter $(OPTIMIZING_COMPILER_TYPES),$(COMPILER_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 28fa130..4214053 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -344,7 +344,7 @@
   else
     FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art"
     FLAGS="${FLAGS} -Xplugin:${plugin}"
-    FLAGS="${FLAGS} -Xfully-deoptable"
+    FLAGS="${FLAGS} -Xcompiler-option --debuggable"
     # Always make the compilation be debuggable.
     COMPILE_FLAGS="${COMPILE_FLAGS} --debuggable"
   fi