Support clinit for app image during compilation

Enabled compiler driver to start initialization process for classes with
static class initializers if debuggable flag is not setted because this
might change the behaviour of interpreter (although invisible to user),
e.g. the memory heap at startup will contains initialization results.

Updated two testcases to reflect the change of heap memory before
classes are initialized.

Enabled testcase 660-clinit to test whether class initializers are
executed.

This CL have to be submitted after /c/432328, /c/432154, /c/433242 and
/c/433342 because without that four this will break the build. Besides,
the other four CL will be tested in this CL.

Test: make test-art-host -j64
Change-Id: Ia25b9e18bdcd0fa3619be0058d459651f3b9a492
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index bd530ac..5eee7e0 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -2251,6 +2251,7 @@
     const char* descriptor = dex_file.StringDataByIdx(class_type_id.descriptor_idx_);
     ScopedObjectAccessUnchecked soa(Thread::Current());
     StackHandleScope<3> hs(soa.Self());
+    ClassLinker *class_linker = manager_->GetClassLinker();
     const bool is_boot_image = manager_->GetCompiler()->GetCompilerOptions().IsBootImage();
     const bool is_app_image = manager_->GetCompiler()->GetCompilerOptions().IsAppImage();
 
@@ -2264,7 +2265,7 @@
     if (klass->IsVerified()) {
       // Attempt to initialize the class but bail if we either need to initialize the super-class
       // or static fields.
-      manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, false);
+      class_linker->EnsureInitialized(soa.Self(), klass, false, false);
       old_status = klass->GetStatus();
       if (!klass->IsInitialized()) {
         // We don't want non-trivial class initialization occurring on multiple threads due to
@@ -2283,7 +2284,7 @@
         bool is_superclass_initialized = !is_app_image ? true :
             InitializeDependencies(klass, class_loader, soa.Self());
         if (!is_app_image || (is_app_image && is_superclass_initialized)) {
-          manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, true);
+          class_linker->EnsureInitialized(soa.Self(), klass, false, true);
         }
         // Otherwise it's in app image but superclasses can't be initialized, no need to proceed.
         old_status = klass->GetStatus();
@@ -2309,10 +2310,13 @@
             CHECK(is_app_image);
             // The boot image case doesn't need to recursively initialize the dependencies with
             // special logic since the class linker already does this.
+            // Optimization will be disabled in debuggable build, because in debuggable mode we
+            // want the <clinit> behavior to be observable for the debugger, so we don't do the
+            // <clinit> at compile time.
             can_init_static_fields =
+                !manager_->GetCompiler()->GetCompilerOptions().GetDebuggable() &&
                 !soa.Self()->IsExceptionPending() &&
-                is_superclass_initialized &&
-                NoClinitInDependency(klass, soa.Self(), &class_loader);
+                is_superclass_initialized;
             // TODO The checking for clinit can be removed since it's already
             // checked when init superclass. Currently keep it because it contains
             // processing of intern strings. Will be removed later when intern strings
@@ -2326,6 +2330,18 @@
             // a ReaderWriterMutex but we're holding the mutator lock so we fail mutex sanity
             // checks in Thread::AssertThreadSuspensionIsAllowable.
             Runtime* const runtime = Runtime::Current();
+            // Resolve and initialize the exception type before enabling the transaction in case
+            // the transaction aborts and cannot resolve the type.
+            // TransactionAbortError is not initialized ant not in boot image, needed only by
+            // compiler and will be pruned by ImageWriter.
+            Handle<mirror::Class> exception_class =
+                hs.NewHandle(class_linker->FindClass(Thread::Current(),
+                                                     Transaction::kAbortExceptionSignature,
+                                                     class_loader));
+            bool exception_initialized =
+                class_linker->EnsureInitialized(soa.Self(), exception_class, true, true);
+            DCHECK(exception_initialized);
+
             // Run the class initializer in transaction mode.
             runtime->EnterTransactionMode(is_app_image, klass.Get());
             bool success = manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, true,
@@ -2363,10 +2379,12 @@
               }
             }
 
-            if (!success) {
+            if (!success && is_boot_image) {
               // On failure, still intern strings of static fields and seen in <clinit>, as these
               // will be created in the zygote. This is separated from the transaction code just
               // above as we will allocate strings, so must be allowed to suspend.
+              // We only need to intern strings for boot image because classes that failed to be
+              // initialized will not appear in app image.
               if (&klass->GetDexFile() == manager_->GetDexFile()) {
                 InternStrings(klass, class_loader);
               } else {
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
index b1bc3f8..d8f9e22 100644
--- a/runtime/aot_class_linker.cc
+++ b/runtime/aot_class_linker.cc
@@ -27,6 +27,16 @@
 
 AotClassLinker::~AotClassLinker() {}
 
+bool AotClassLinker::CanAllocClass() {
+  // AllocClass doesn't work under transaction, so we abort.
+  if (Runtime::Current()->IsActiveTransaction()) {
+    Runtime::Current()->AbortTransactionAndThrowAbortError(Thread::Current(), "Can't resolve this "
+        "type within a transaction.");
+    return false;
+  }
+  return ClassLinker::CanAllocClass();
+}
+
 // Wrap the original InitializeClass with creation of transaction when in strict mode.
 bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
                                   bool can_init_statics, bool can_init_parents) {
@@ -38,6 +48,13 @@
     return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
   }
 
+  // When in strict_mode, don't initialize a class if it belongs to boot but not initialized.
+  if (strict_mode_ && klass->IsBootStrapClassLoaded()) {
+    runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
+        + klass->PrettyTypeOf() + " because it is an uninitialized boot class.");
+    return false;
+  }
+
   // Don't initialize klass if it's superclass is not initialized, because superclass might abort
   // the transaction and rolled back after klass's change is commited.
   if (strict_mode_ && !klass->IsInterface() && klass->HasSuperClass()) {
@@ -58,9 +75,8 @@
       // Exit Transaction if success.
       runtime->ExitTransactionMode();
     } else {
-      // If not successfully initialized, the last transaction must abort. Don't rollback
-      // immediately, leave the cleanup to compiler driver which needs abort message and exception.
-      DCHECK(runtime->IsTransactionAborted());
+      // If not successfully initialized, don't rollback immediately, leave the cleanup to compiler
+      // driver which needs abort message and exception.
       DCHECK(self->IsExceptionPending());
     }
   }
diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h
index 11bea86..e9a96fa 100644
--- a/runtime/aot_class_linker.h
+++ b/runtime/aot_class_linker.h
@@ -27,6 +27,13 @@
   explicit AotClassLinker(InternTable *intern_table);
   ~AotClassLinker();
 
+  // Override AllocClass because aot compiler will need to perform a transaction check to determine
+  // can we allocate class from heap.
+  bool CanAllocClass()
+      OVERRIDE
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Roles::uninterruptible_);
+
   bool InitializeClass(Thread *self,
                        Handle<mirror::Class> klass,
                        bool can_run_clinit,
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3ac87c5..46b0113 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2854,7 +2854,11 @@
     // Interface object should get the right size here. Regular class will
     // figure out the right size later and be replaced with one of the right
     // size when the class becomes resolved.
-    klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
+    if (CanAllocClass()) {
+      klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
+    } else {
+      return nullptr;
+    }
   }
   if (UNLIKELY(klass == nullptr)) {
     self->AssertPendingOOMException();
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index bf14aeb..584bd1d 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -710,6 +710,12 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_);
 
+  virtual bool CanAllocClass()
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::dex_lock_) {
+    return true;
+  }
+
  private:
   class LinkInterfaceMethodsHelper;
 
diff --git a/test/660-clinit/expected.txt b/test/660-clinit/expected.txt
index 9eb4941..ee1b479 100644
--- a/test/660-clinit/expected.txt
+++ b/test/660-clinit/expected.txt
@@ -1,4 +1,5 @@
 JNI_OnLoad called
+hello world
 A.a: 5
 A.a: 10
 B.b: 10
diff --git a/test/660-clinit/profile b/test/660-clinit/profile
index 0239f22..9eb4924 100644
--- a/test/660-clinit/profile
+++ b/test/660-clinit/profile
@@ -4,7 +4,10 @@
 LA;
 LB;
 LC;
+LE;
 LG;
 LGs;
 LObjectRef;
-
+LInvokeStatic;
+LClinitE;
+LPrint;
diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java
index f9b068e..51b4d60 100644
--- a/test/660-clinit/src/Main.java
+++ b/test/660-clinit/src/Main.java
@@ -24,19 +24,28 @@
     if (!checkAppImageLoaded()) {
       System.out.println("AppImage not loaded.");
     }
+    if (!checkAppImageContains(ClInit.class)) {
+      System.out.println("ClInit class is not in app image!");
+    }
 
-    expectNotPreInit(Day.class);
-    expectNotPreInit(ClInit.class); // should pass
-    expectNotPreInit(A.class); // should pass
-    expectNotPreInit(B.class); // should fail
-    expectNotPreInit(C.class); // should fail
-    expectNotPreInit(G.class); // should fail
-    expectNotPreInit(Gs.class); // should fail
-    expectNotPreInit(Gss.class); // should fail
+    expectPreInit(ClInit.class);
+    expectPreInit(A.class);
+    expectPreInit(E.class);
+    expectNotPreInit(B.class);
+    expectNotPreInit(C.class);
+    expectNotPreInit(G.class);
+    expectNotPreInit(Gs.class);
+    expectNotPreInit(Gss.class);
+    expectPreInit(InvokeStatic.class);
+    expectNotPreInit(ClinitE.class);
 
     expectNotPreInit(Add.class);
     expectNotPreInit(Mul.class);
     expectNotPreInit(ObjectRef.class);
+    expectNotPreInit(Print.class);
+
+    Print p = new Print();
+    Gs gs = new Gs();
 
     A x = new A();
     System.out.println("A.a: " + A.a);
@@ -62,6 +71,10 @@
       System.out.println("a != 101");
     }
 
+    try {
+      ClinitE e = new ClinitE();
+    } catch (Error err) { }
+
     return;
   }
 
@@ -154,6 +167,13 @@
   }
 }
 
+class E {
+  public static final int e;
+  static {
+    e = 100;
+  }
+}
+
 class G {
   static G g;
   static int i;
@@ -182,9 +202,36 @@
   }
 }
 
+// test of INVOKE_STATIC instruction
+class InvokeStatic {
+  static int a;
+  static int b;
+  static {
+    a = Add.exec(10, 20);
+    b = Mul.exec(10, 20);
+  }
+}
+
 // non-image
 class Mul {
   static int exec(int a, int b) {
     return a * b;
   }
 }
+
+class ClinitE {
+  static {
+    if (Math.sin(3) < 0.5) {
+      // throw anyway, can't initialized
+      throw new ExceptionInInitializerError("Can't initialize this class!");
+    }
+  }
+}
+
+// fail because JNI
+class Print {
+  static {
+    System.out.println("hello world");
+  }
+}
+
diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt
index 73b7129..85391fa 100644
--- a/test/906-iterate-heap/expected.txt
+++ b/test/906-iterate-heap/expected.txt
@@ -19,9 +19,7 @@
 1@0 (32, 2xD '0000000000000000000000000000f03f')
 2
 doTestPrimitiveFieldsClasses
-10000@0 (static, int, index=3) 0000000000000000
 10001
-10000@0 (static, int, index=11) 0000000000000000
 10001
 10001
 10001
diff --git a/test/906-iterate-heap/src/art/Test906.java b/test/906-iterate-heap/src/art/Test906.java
index 65c2c8c..1878687 100644
--- a/test/906-iterate-heap/src/art/Test906.java
+++ b/test/906-iterate-heap/src/art/Test906.java
@@ -143,19 +143,40 @@
 
   private static void doTestPrimitiveFieldsClasses() {
     System.out.println("doTestPrimitiveFieldsClasses");
+    boolean correctHeapValue = false;
+
     setTag(IntObject.class, 10000);
-    System.out.println(iterateThroughHeapPrimitiveFields(10000));
+    String heapTrace = iterateThroughHeapPrimitiveFields(10000);
+
+    if (!checkInitialized(IntObject.class)) {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000000");
+    } else {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000005");
+    }
+
+    if (!correctHeapValue)
+      System.out.println("Heap Trace for IntObject is not as expected:\n" + heapTrace);
+
     System.out.println(getTag(IntObject.class));
     setTag(IntObject.class, 0);
 
     setTag(FloatObject.class, 10000);
-    System.out.println(iterateThroughHeapPrimitiveFields(10000));
+    heapTrace = iterateThroughHeapPrimitiveFields(10000);
+
+    if (!checkInitialized(FloatObject.class)) {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000000");
+    } else {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000006");
+    }
+
+    if (!correctHeapValue)
+      System.out.println("Heap Trace for FloatObject is not as expected:\n" + heapTrace);
+
     System.out.println(getTag(FloatObject.class));
     setTag(FloatObject.class, 0);
 
-    boolean correctHeapValue = false;
     setTag(Inf1.class, 10000);
-    String heapTrace = iterateThroughHeapPrimitiveFields(10000);
+    heapTrace = iterateThroughHeapPrimitiveFields(10000);
 
     if (!checkInitialized(Inf1.class)) {
       correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000000");
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 6144881..844afe8 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -136,9 +136,7 @@
 4@0 (18, 3xS '010002000300')
 1@0 (14, 2xZ '0001')
 23456789
-10000@0 (static, int, index=3) 0000000000000000
 10001
-10000@0 (static, int, index=11) 0000000000000000
 10001
 10001
 10001
diff --git a/test/913-heaps/src/art/Test913.java b/test/913-heaps/src/art/Test913.java
index b999001..28f9546 100644
--- a/test/913-heaps/src/art/Test913.java
+++ b/test/913-heaps/src/art/Test913.java
@@ -185,19 +185,40 @@
   }
 
   private static void doTestPrimitiveFieldsClasses() {
+    boolean correctHeapValue = false;
+
     setTag(IntObject.class, 10000);
-    System.out.println(followReferencesPrimitiveFields(IntObject.class));
+    String heapTrace = followReferencesPrimitiveFields(IntObject.class);
+
+    if (!checkInitialized(IntObject.class)) {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000000");
+    } else {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000005");
+    }
+
+    if (!correctHeapValue)
+      System.out.println("Heap Trace for IntObject is not as expected:\n" + heapTrace);
+
     System.out.println(getTag(IntObject.class));
     setTag(IntObject.class, 0);
 
     setTag(FloatObject.class, 10000);
-    System.out.println(followReferencesPrimitiveFields(FloatObject.class));
+    heapTrace = followReferencesPrimitiveFields(FloatObject.class);
+
+    if (!checkInitialized(FloatObject.class)) {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000000");
+    } else {
+      correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000006");
+    }
+
+    if (!correctHeapValue)
+      System.out.println("Heap Trace for FloatObject is not as expected:\n" + heapTrace);
+
     System.out.println(getTag(FloatObject.class));
     setTag(FloatObject.class, 0);
 
-    boolean correctHeapValue = false;
     setTag(Inf1.class, 10000);
-    String heapTrace = followReferencesPrimitiveFields(Inf1.class);
+    heapTrace = followReferencesPrimitiveFields(Inf1.class);
 
     if (!checkInitialized(Inf1.class)) {
       correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000000");
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 5a67fbc..04de7a1 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -708,8 +708,8 @@
     },
     {
         "tests": "660-clinit",
-        "variant": "no-image | no-dex2oat | no-prebuild",
-        "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild and",
-                        "--no-dex2oat do not create"]
+        "variant": "no-image | no-dex2oat | no-prebuild | interp-ac",
+        "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild, interp-ac",
+                        "and --no-dex2oat do not create"]
     }
 ]