Clean up initialization checks for entrypoints.

Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing --jit
Test: aosp_taimen-userdebug boots.
Test: run-gtests.sh
Test: testrunner.py --target --optimizing --jit
Bug: 18161648
Change-Id: Ia3c2fdb616a5bb289e5afeccd4e6fe3eaf7ed697
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index e1db1f6..6a13d59 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -37,6 +37,7 @@
 #include "debugger_interface.h"
 #include "dex/dex_file_loader.h"
 #include "dex/method_reference.h"
+#include "entrypoints/entrypoint_utils-inl.h"
 #include "entrypoints/runtime_asm_entrypoints.h"
 #include "gc/accounting/bitmap-inl.h"
 #include "gc/allocator/dlmalloc.h"
@@ -126,9 +127,29 @@
     for (ArtMethod* m : GetMethods()) {
       // Because `m` might be in the process of being deleted:
       // - Call the dedicated method instead of the more generic UpdateMethodsCode
-      // - Check the class status without a read barrier.
-      // TODO: Use ReadBarrier::IsMarked() if not null to check the class status.
-      if (!m->NeedsInitializationCheck<kWithoutReadBarrier>()) {
+      // - Check the class status without a full read barrier; use ReadBarrier::IsMarked().
+      bool can_set_entrypoint = true;
+      if (NeedsClinitCheckBeforeCall(m)) {
+        // To avoid resurrecting an unreachable object, we must not use a full read
+        // barrier but we do not want to miss updating an entrypoint under common
+        // circumstances, i.e. during a GC the class becomes visibly initialized,
+        // the method becomes hot, we compile the thunk and want to update the
+        // entrypoint while the method's declaring class field still points to the
+        // from-space class object with the old status. Therefore we read the
+        // declaring class without a read barrier and check if it's already marked.
+        // If yes, we check the status of the to-space class object as intended.
+        // Otherwise, there is no to-space object and the from-space class object
+        // contains the most recent value of the status field; even if this races
+        // with another thread doing a read barrier and updating the status, that's
+        // no different from a race with a thread that just updates the status.
+        // Such race can happen only for the zygote method pre-compilation, as we
+        // otherwise compile only thunks for methods of visibly initialized classes.
+        ObjPtr<mirror::Class> klass = m->GetDeclaringClass<kWithoutReadBarrier>();
+        ObjPtr<mirror::Class> marked = ReadBarrier::IsMarked(klass.Ptr());
+        ObjPtr<mirror::Class> checked_klass = (marked != nullptr) ? marked : klass;
+        can_set_entrypoint = checked_klass->IsVisiblyInitialized();
+      }
+      if (can_set_entrypoint) {
         instrum->UpdateNativeMethodsCodeToJitCode(m, entrypoint);
       }
     }
@@ -730,7 +751,8 @@
       if (osr) {
         number_of_osr_compilations_++;
         osr_code_map_.Put(method, code_ptr);
-      } else if (method->NeedsInitializationCheck()) {
+      } else if (NeedsClinitCheckBeforeCall(method) &&
+                 !method->GetDeclaringClass()->IsVisiblyInitialized()) {
         // This situation currently only occurs in the jit-zygote mode.
         DCHECK(Runtime::Current()->IsUsingApexBootImageLocation());
         DCHECK(!garbage_collect_code_);
@@ -1559,21 +1581,27 @@
     return false;
   }
 
-  if (method->NeedsInitializationCheck() && !prejit) {
-    // Unless we're pre-jitting, we currently don't save the JIT compiled code if we cannot
-    // update the entrypoint due to needing an initialization check.
-    if (method->GetDeclaringClass()->IsInitialized()) {
-      // Request visible initialization but do not block to allow compiling other methods.
-      // Hopefully, this will complete by the time the method becomes hot again.
-      Runtime::Current()->GetClassLinker()->MakeInitializedClassesVisiblyInitialized(
-          self, /*wait=*/ false);
+  if (NeedsClinitCheckBeforeCall(method) && !prejit) {
+    // We do not need a synchronization barrier for checking the visibly initialized status
+    // or checking the initialized status just for requesting visible initialization.
+    ClassStatus status = method->GetDeclaringClass()
+        ->GetStatus<kDefaultVerifyFlags, /*kWithSynchronizationBarrier=*/ false>();
+    if (status != ClassStatus::kVisiblyInitialized) {
+      // Unless we're pre-jitting, we currently don't save the JIT compiled code if we cannot
+      // update the entrypoint due to needing an initialization check.
+      if (status == ClassStatus::kInitialized) {
+        // Request visible initialization but do not block to allow compiling other methods.
+        // Hopefully, this will complete by the time the method becomes hot again.
+        Runtime::Current()->GetClassLinker()->MakeInitializedClassesVisiblyInitialized(
+            self, /*wait=*/ false);
+      }
+      VLOG(jit) << "Not compiling "
+                << method->PrettyMethod()
+                << " because it has the resolution stub";
+      // Give it a new chance to be hot.
+      ClearMethodCounter(method, /*was_warm=*/ false);
+      return false;
     }
-    VLOG(jit) << "Not compiling "
-              << method->PrettyMethod()
-              << " because it has the resolution stub";
-    // Give it a new chance to be hot.
-    ClearMethodCounter(method, /*was_warm=*/ false);
-    return false;
   }
 
   MutexLock mu(self, *Locks::jit_lock_);