Elide ClinitCheck for superclasses with trivial init.

We cannot generally elide ClinitCheck for superclasses
because of escaping instances of erroneous classes and
because a subclass can be successfully initialized while
the superclass is initializing and the subclass remains
initialized even when the superclass initializer throws.

However, for those superclasses that have trivial init,
i.e. that class and its superclasses (and superinterfaces
with default methods) initialize only their own static
fields using constant values, there is no chance to end
up in one of these weird situations and we can safely
eliminate the ClinitCheck.

The size of the aosp_taimen-userdebug prebuilts:
  - before:
    arm/boot*.oat: 16856928
    arm64/boot*.oat: 19846592
    oat/arm64/services.odex: 24662880
  - after:
    arm/boot*.oat: 16848696 (-8.0KiB, -0.05%)
    arm64/boot*.oat: 19842320 (-4.2KiB, -0.02%)
    oat/arm64/services.odex: 24629952 (-32.2KiB, -0.13%)
with minor changes to other app prebuilts.

Test: Improved 478-checker-clinit-check-pruning.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: Pixel 2 XL boots.
Test: testrunner.py --target --optimizing
Bug: 62478025
Change-Id: I865f567443f1b7f172e5e6559d8eb3232adb7833
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 5c3fcc5..e555d0d 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -1147,6 +1147,151 @@
       MethodCompilationStat::kConstructorFenceGeneratedNew);
 }
 
+static bool IsInBootImage(ObjPtr<mirror::Class> cls, const CompilerOptions& compiler_options)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (compiler_options.IsBootImage()) {
+    std::string temp;
+    const char* descriptor = cls->GetDescriptor(&temp);
+    return compiler_options.IsImageClass(descriptor);
+  } else {
+    return Runtime::Current()->GetHeap()->FindSpaceFromObject(cls, false)->IsImageSpace();
+  }
+}
+
+static bool IsSubClass(ObjPtr<mirror::Class> to_test, ObjPtr<mirror::Class> super_class)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  return to_test != nullptr && !to_test->IsInterface() && to_test->IsSubClass(super_class);
+}
+
+static bool HasTrivialClinit(ObjPtr<mirror::Class> klass, PointerSize pointer_size)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Check if the class has encoded fields that trigger bytecode execution.
+  // (Encoded fields are just a different representation of <clinit>.)
+  if (klass->NumStaticFields() != 0u) {
+    DCHECK(klass->GetClassDef() != nullptr);
+    EncodedStaticFieldValueIterator it(klass->GetDexFile(), *klass->GetClassDef());
+    for (; it.HasNext(); it.Next()) {
+      switch (it.GetValueType()) {
+        case EncodedArrayValueIterator::ValueType::kBoolean:
+        case EncodedArrayValueIterator::ValueType::kByte:
+        case EncodedArrayValueIterator::ValueType::kShort:
+        case EncodedArrayValueIterator::ValueType::kChar:
+        case EncodedArrayValueIterator::ValueType::kInt:
+        case EncodedArrayValueIterator::ValueType::kLong:
+        case EncodedArrayValueIterator::ValueType::kFloat:
+        case EncodedArrayValueIterator::ValueType::kDouble:
+        case EncodedArrayValueIterator::ValueType::kNull:
+        case EncodedArrayValueIterator::ValueType::kString:
+          // Primitive, null or j.l.String initialization is permitted.
+          break;
+        case EncodedArrayValueIterator::ValueType::kType:
+          // Type initialization can load classes and execute bytecode through a class loader
+          // which can execute arbitrary bytecode. We do not optimize for known class loaders;
+          // kType is rarely used (if ever).
+          return false;
+        default:
+          // Other types in the encoded static field list are rejected by the DexFileVerifier.
+          LOG(FATAL) << "Unexpected type " << it.GetValueType();
+          UNREACHABLE();
+      }
+    }
+  }
+  // Check if the class has <clinit> that executes arbitrary code.
+  // Initialization of static fields of the class itself with constants is allowed.
+  ArtMethod* clinit = klass->FindClassInitializer(pointer_size);
+  if (clinit != nullptr) {
+    const DexFile& dex_file = *clinit->GetDexFile();
+    CodeItemInstructionAccessor accessor(dex_file, clinit->GetCodeItem());
+    for (DexInstructionPcPair it : accessor) {
+      switch (it->Opcode()) {
+        case Instruction::CONST_4:
+        case Instruction::CONST_16:
+        case Instruction::CONST:
+        case Instruction::CONST_HIGH16:
+        case Instruction::CONST_WIDE_16:
+        case Instruction::CONST_WIDE_32:
+        case Instruction::CONST_WIDE:
+        case Instruction::CONST_WIDE_HIGH16:
+        case Instruction::CONST_STRING:
+        case Instruction::CONST_STRING_JUMBO:
+          // Primitive, null or j.l.String initialization is permitted.
+          break;
+        case Instruction::RETURN_VOID:
+        case Instruction::RETURN_VOID_NO_BARRIER:
+          break;
+        case Instruction::SPUT:
+        case Instruction::SPUT_WIDE:
+        case Instruction::SPUT_OBJECT:
+        case Instruction::SPUT_BOOLEAN:
+        case Instruction::SPUT_BYTE:
+        case Instruction::SPUT_CHAR:
+        case Instruction::SPUT_SHORT:
+          // Only initialization of a static field of the same class is permitted.
+          if (dex_file.GetFieldId(it->VRegB_21c()).class_idx_ != klass->GetDexTypeIndex()) {
+            return false;
+          }
+          break;
+        case Instruction::NEW_ARRAY:
+          // Only primitive arrays are permitted.
+          if (Primitive::GetType(dex_file.GetTypeDescriptor(dex_file.GetTypeId(
+                  dex::TypeIndex(it->VRegC_22c())))[1]) == Primitive::kPrimNot) {
+            return false;
+          }
+          break;
+        case Instruction::APUT:
+        case Instruction::APUT_WIDE:
+        case Instruction::APUT_BOOLEAN:
+        case Instruction::APUT_BYTE:
+        case Instruction::APUT_CHAR:
+        case Instruction::APUT_SHORT:
+        case Instruction::FILL_ARRAY_DATA:
+        case Instruction::NOP:
+          // Allow initialization of primitive arrays (only constants can be stored).
+          // Note: We expect NOPs used for fill-array-data-payload but accept all NOPs
+          // (even unreferenced switch payloads if they make it through the verifier).
+          break;
+        default:
+          return false;
+      }
+    }
+  }
+  return true;
+}
+
+static bool HasTrivialInitialization(ObjPtr<mirror::Class> cls,
+                                     const CompilerOptions& compiler_options)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  Runtime* runtime = Runtime::Current();
+  PointerSize pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
+
+  // Check the superclass chain.
+  for (ObjPtr<mirror::Class> klass = cls; klass != nullptr; klass = klass->GetSuperClass()) {
+    if (klass->IsInitialized() && IsInBootImage(klass, compiler_options)) {
+      break;  // `klass` and its superclasses are already initialized in the boot image.
+    }
+    if (!HasTrivialClinit(klass, pointer_size)) {
+      return false;
+    }
+  }
+
+  // Also check interfaces with default methods as they need to be initialized as well.
+  ObjPtr<mirror::IfTable> iftable = cls->GetIfTable();
+  DCHECK(iftable != nullptr);
+  for (int32_t i = 0, count = iftable->Count(); i != count; ++i) {
+    ObjPtr<mirror::Class> iface = iftable->GetInterface(i);
+    if (!iface->HasDefaultMethods()) {
+      continue;  // Initializing `cls` does not initialize this interface.
+    }
+    if (iface->IsInitialized() && IsInBootImage(iface, compiler_options)) {
+      continue;  // This interface is already initialized in the boot image.
+    }
+    if (!HasTrivialClinit(iface, pointer_size)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool HInstructionBuilder::IsInitialized(ScopedObjectAccess& soa, Handle<mirror::Class> cls) const {
   if (cls == nullptr) {
     return false;
@@ -1162,35 +1307,60 @@
     }
     // Assume loaded only if klass is in the boot image. App classes cannot be assumed
     // loaded because we don't even know what class loader will be used to load them.
-    const CompilerOptions& compiler_options = compiler_driver_->GetCompilerOptions();
-    if (compiler_options.IsBootImage()) {
-      std::string temp;
-      const char* descriptor = cls->GetDescriptor(&temp);
-      if (compiler_options.IsImageClass(descriptor)) {
-        return true;
-      }
-    } else {
-      if (runtime->GetHeap()->FindSpaceFromObject(cls.Get(), false)->IsImageSpace()) {
-        return true;
-      }
+    if (IsInBootImage(cls.Get(), compiler_driver_->GetCompilerOptions())) {
+      return true;
     }
   }
 
-  // We can avoid the class initialization check for `cls` only in static methods in the
+  // We can avoid the class initialization check for `cls` in static methods in the
   // very same class. Instance methods of the same class can run on an escaped instance
   // of an erroneous class. Even a superclass may need to be checked as the subclass
   // can be completely initialized while the superclass is initializing and the subclass
   // remains initialized when the superclass initializer throws afterwards. b/62478025
   // Note: The HClinitCheck+HInvokeStaticOrDirect merging can still apply.
-  if ((dex_compilation_unit_->GetAccessFlags() & kAccStatic) != 0u) {
-    ObjPtr<mirror::Class> outermost_cls = ResolveOutermostCompilingClass(soa);
-    if (outermost_cls == cls.Get()) {
+  ObjPtr<mirror::Class> outermost_cls = ResolveOutermostCompilingClass(soa);
+  bool is_static = (dex_compilation_unit_->GetAccessFlags() & kAccStatic) != 0u;
+  if (is_static && outermost_cls == cls.Get()) {
+    return true;
+  }
+  // Remember if the compiled class is a subclass of `cls`. By the time this is used
+  // below the `outermost_cls` may be invalidated by calling ResolveCompilingClass().
+  bool is_subclass = IsSubClass(outermost_cls, cls.Get());
+  if (dex_compilation_unit_ != outer_compilation_unit_) {
+    // Check also the innermost method. Though excessive copies of ClinitCheck can be
+    // eliminated by GVN, that happens only after the decision whether to inline the
+    // graph or not and that may depend on the presence of the ClinitCheck.
+    // TODO: We should walk over the entire inlined method chain, but we don't pass that
+    // information to the builder.
+    ObjPtr<mirror::Class> innermost_cls = ResolveCompilingClass(soa);
+    if (is_static && innermost_cls == cls.Get()) {
       return true;
     }
+    is_subclass = is_subclass || IsSubClass(innermost_cls, cls.Get());
   }
 
-  // Note: We could walk over the inlined methods to avoid allocating excessive
-  // `HClinitCheck`s in inlined static methods but they shall be eliminated by GVN.
+  // Otherwise, we may be able to avoid the check if `cls` is a superclass of a method being
+  // compiled here (anywhere in the inlining chain) as the `cls` must have started initializing
+  // before calling any `cls` or subclass methods. Static methods require a clinit check and
+  // instance methods require an instance which cannot be created before doing a clinit check.
+  // When a subclass of `cls` starts initializing, it starts initializing its superclass
+  // chain up to `cls` without running any bytecode, i.e. without any opportunity for circular
+  // initialization weirdness.
+  //
+  // If the initialization of `cls` is trivial (`cls` and its superclasses and superinterfaces
+  // with default methods initialize only their own static fields using constant values), it must
+  // complete, either successfully or by throwing and marking `cls` erroneous, without allocating
+  // any instances of `cls` or subclasses (or any other class) and without calling any methods.
+  // If it completes by throwing, no instances of `cls` shall be created and no subclass method
+  // bytecode shall execute (see above), therefore the instruction we're building shall be
+  // unreachable. By reaching the instruction, we know that `cls` was initialized successfully.
+  //
+  // TODO: We should walk over the entire inlined methods chain, but we don't pass that
+  // information to the builder. (We could also check if we're guaranteed a non-null instance
+  // of `cls` at this location but that's outside the scope of the instruction builder.)
+  if (is_subclass && HasTrivialInitialization(cls.Get(), compiler_driver_->GetCompilerOptions())) {
+    return true;
+  }
 
   return false;
 }
diff --git a/test/478-checker-clinit-check-pruning/expected.txt b/test/478-checker-clinit-check-pruning/expected.txt
index 6f73b65..1f6e9d9 100644
--- a/test/478-checker-clinit-check-pruning/expected.txt
+++ b/test/478-checker-clinit-check-pruning/expected.txt
@@ -1,9 +1,13 @@
 Main$ClassWithClinit1's static initializer
 Main$ClassWithClinit2's static initializer
-Main$ClassWithClinit3's static initializer
-Main$ClassWithClinit4's static initializer
+Main$ClassWithClinit3Static's static initializer
+Main$ClassWithClinit3Instance's static initializer
+Main$ClassWithClinit4Static's static initializer
+Main$ClassWithClinit4Instance's static initializer
 Main$ClassWithClinit5's static initializer
+Main$SubClassOfClassWithoutClinit5's static initializer
 Main$ClassWithClinit6's static initializer
+Main$SubClassOfClassWithoutClinit6's static initializer
 Main$ClassWithClinit7's static initializer
 Main$ClassWithClinit8's static initializer
 Main$ClassWithClinit9's static initializer
diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java
index ca92e7a..e16fa69 100644
--- a/test/478-checker-clinit-check-pruning/src/Main.java
+++ b/test/478-checker-clinit-check-pruning/src/Main.java
@@ -16,8 +16,6 @@
 
 public class Main {
 
-  static boolean doThrow = false;
-
   /*
    * Ensure an inlined static invoke explicitly triggers the
    * initialization check of the called method's declaring class, and
@@ -100,43 +98,40 @@
       System.out.println("Main$ClassWithClinit2's static initializer");
     }
 
-    static boolean doThrow = false;
+    static boolean staticField = false;
 
     static void $noinline$staticMethod() {
-      // Try defeating inlining.
-      if (doThrow) { throw new Error(); }
     }
   }
 
   /*
-   * Ensure an inlined call to a static method whose declaring class
-   * is statically known to have been initialized does not require an
-   * explicit clinit check.
+   * Ensure an inlined call from a static method to a static method
+   * of the same class does not require an explicit clinit check
+   * (already initialized or initializing in the same thread).
    */
 
-  /// CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after)
+  /// CHECK-START: void Main$ClassWithClinit3Static.invokeStaticInlined() builder (after)
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
-  /// CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after)
+  /// CHECK-START: void Main$ClassWithClinit3Static.invokeStaticInlined() builder (after)
   /// CHECK-NOT:                           LoadClass
   /// CHECK-NOT:                           ClinitCheck
 
-  /// CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() inliner (after)
+  /// CHECK-START: void Main$ClassWithClinit3Static.invokeStaticInlined() inliner (after)
   /// CHECK-NOT:                           LoadClass
   /// CHECK-NOT:                           ClinitCheck
   /// CHECK-NOT:                           InvokeStaticOrDirect
 
-  static class ClassWithClinit3 {
+  static class ClassWithClinit3Static {
     static void invokeStaticInlined() {
-      // The invocation of invokeStaticInlined triggers the
-      // initialization of ClassWithClinit3, meaning that the
-      // hereinbelow call to $opt$inline$StaticMethod does not need a
-      // clinit check.
+      // The invocation of invokeStaticInlined happens only after a clinit check
+      // of ClassWithClinit3Static, meaning that the hereinbelow call to
+      // $opt$inline$StaticMethod does not need another clinit check.
       $opt$inline$StaticMethod();
     }
 
     static {
-      System.out.println("Main$ClassWithClinit3's static initializer");
+      System.out.println("Main$ClassWithClinit3Static's static initializer");
     }
 
     static void $opt$inline$StaticMethod() {
@@ -144,61 +139,120 @@
   }
 
   /*
-   * Ensure an non-inlined call to a static method whose declaring
-   * class is statically known to have been initialized does not
-   * require an explicit clinit check.
+   * Ensure an inlined call from an instance method to a static method
+   * of the same class actually requires an explicit clinit check when
+   * the class has a non-trivial initialization as we could be executing
+   * the instance method on an escaped object of an erroneous class. b/62478025
    */
 
-  /// CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after)
+  /// CHECK-START: void Main$ClassWithClinit3Instance.invokeStaticInlined() builder (after)
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
-  /// CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after)
+  /// CHECK-START: void Main$ClassWithClinit3Instance.invokeStaticInlined() inliner (after)
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
+
+  /// CHECK-START: void Main$ClassWithClinit3Instance.invokeStaticInlined() inliner (after)
+  /// CHECK-NOT:                           InvokeStaticOrDirect
+
+  static class ClassWithClinit3Instance {
+    void invokeStaticInlined() {
+      // ClinitCheck required.
+      $opt$inline$StaticMethod();
+    }
+
+    static {
+      System.out.println("Main$ClassWithClinit3Instance's static initializer");
+    }
+
+    static void $opt$inline$StaticMethod() {
+    }
+  }
+
+  /*
+   * Ensure a non-inlined call from a static method to a static method
+   * of the same class does not require an explicit clinit check
+   * (already initialized or initializing in the same thread).
+   */
+
+  /// CHECK-START: void Main$ClassWithClinit4Static.invokeStaticNotInlined() builder (after)
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  /// CHECK-START: void Main$ClassWithClinit4Static.invokeStaticNotInlined() builder (after)
   /// CHECK-NOT:                           LoadClass
   /// CHECK-NOT:                           ClinitCheck
 
-  /// CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after)
+  /// CHECK-START: void Main$ClassWithClinit4Static.invokeStaticNotInlined() inliner (after)
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
-  /// CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after)
+  /// CHECK-START: void Main$ClassWithClinit4Static.invokeStaticNotInlined() inliner (after)
   /// CHECK-NOT:                           LoadClass
   /// CHECK-NOT:                           ClinitCheck
 
-  static class ClassWithClinit4 {
+  static class ClassWithClinit4Static {
     static void invokeStaticNotInlined() {
       // The invocation of invokeStaticNotInlined triggers the
-      // initialization of ClassWithClinit4, meaning that the
+      // initialization of ClassWithClinit4Static, meaning that the
       // call to staticMethod below does not need a clinit
       // check.
       $noinline$staticMethod();
     }
 
     static {
-      System.out.println("Main$ClassWithClinit4's static initializer");
+      System.out.println("Main$ClassWithClinit4Static's static initializer");
     }
 
-    static boolean doThrow = false;
+    static void $noinline$staticMethod() {
+    }
+  }
+
+  /*
+   * Ensure a non-inlined call from an instance method to a static method
+   * of the same class actually requires an explicit clinit check when
+   * the class has a non-trivial initialization as we could be executing
+   * the instance method on an escaped object of an erroneous class. b/62478025
+   */
+
+  /// CHECK-START: void Main$ClassWithClinit4Instance.invokeStaticNotInlined() builder (after)
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  /// CHECK-START: void Main$ClassWithClinit4Instance.invokeStaticNotInlined() inliner (after)
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  static class ClassWithClinit4Instance {
+    void invokeStaticNotInlined() {
+      // ClinitCheck required.
+      $noinline$staticMethod();
+    }
+
+    static {
+      System.out.println("Main$ClassWithClinit4Instance's static initializer");
+    }
 
     static void $noinline$staticMethod() {
-        // Try defeating inlining.
-      if (doThrow) { throw new Error(); }
     }
   }
 
   /*
    * We used to remove clinit check for calls to static methods in a superclass. However, this
-   * is not a valid optimization when instances of erroneous classes can escape. b/62478025
+   * is not a valid optimization when instances of erroneous classes can escape, therefore
+   * we avoid this optimization for classes with non-trivial initialization. b/62478025
    */
 
   /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after)
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
-  /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after)
-  /// CHECK:                               LoadClass
-  /// CHECK:                               ClinitCheck
-
   /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() inliner (after)
-  /// CHECK:                               LoadClass
-  /// CHECK:                               ClinitCheck
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
   /// CHECK-NOT:                           InvokeStaticOrDirect
 
   static class ClassWithClinit5 {
@@ -217,16 +271,50 @@
   }
 
   /*
+   * Ensure an inlined call to a static method whose declaring class is a super class
+   * of the caller's class does not require an explicit clinit check if the declaring
+   * class has a trivial initialization. b/62478025
+   */
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit5.invokeStaticInlined() builder (after)
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit5.invokeStaticInlined() builder (after)
+  /// CHECK-NOT:                           LoadClass
+  /// CHECK-NOT:                           ClinitCheck
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit5.invokeStaticInlined() inliner (after)
+  /// CHECK-NOT:                           LoadClass
+  /// CHECK-NOT:                           ClinitCheck
+  /// CHECK-NOT:                           InvokeStaticOrDirect
+
+  static class ClassWithoutClinit5 {  // Mimicks ClassWithClinit5 but without the <clinit>.
+    static void $opt$inline$StaticMethod() {
+    }
+  }
+
+  static class SubClassOfClassWithoutClinit5 extends ClassWithoutClinit5 {
+    static {
+      System.out.println("Main$SubClassOfClassWithoutClinit5's static initializer");
+    }
+
+    static void invokeStaticInlined() {
+      ClassWithoutClinit5.$opt$inline$StaticMethod();
+    }
+  }
+
+  /*
    * We used to remove clinit check for calls to static methods in a superclass. However, this
-   * is not a valid optimization when instances of erroneous classes can escape. b/62478025
+   * is not a valid optimization when instances of erroneous classes can escape, therefore
+   * we avoid this optimization for classes with non-trivial initialization. b/62478025
    */
 
   /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after)
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
   /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after)
-  /// CHECK:                               LoadClass
-  /// CHECK:                               ClinitCheck
+  /// CHECK-DAG:                           LoadClass
+  /// CHECK-DAG:                           ClinitCheck
 
   /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after)
   /// CHECK-DAG:                           LoadClass
@@ -234,11 +322,7 @@
   /// CHECK-DAG:                           InvokeStaticOrDirect
 
   static class ClassWithClinit6 {
-    static boolean doThrow = false;
-
     static void $noinline$staticMethod() {
-        // Try defeating inlining.
-      if (doThrow) { throw new Error(); }
     }
 
     static {
@@ -252,6 +336,40 @@
     }
   }
 
+  /*
+   * Ensure a non-inlined call to a static method whose declaring class is a super class
+   * of the caller's class does not require an explicit clinit check if the declaring
+   * class has a trivial initialization. b/62478025
+   */
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit6.invokeStaticNotInlined() builder (after)
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit6.invokeStaticNotInlined() builder (after)
+  /// CHECK-NOT:                           LoadClass
+  /// CHECK-NOT:                           ClinitCheck
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit6.invokeStaticNotInlined() inliner (after)
+  /// CHECK-DAG:                           InvokeStaticOrDirect
+
+  /// CHECK-START: void Main$SubClassOfClassWithoutClinit6.invokeStaticNotInlined() inliner (after)
+  /// CHECK-NOT:                           LoadClass
+  /// CHECK-NOT:                           ClinitCheck
+
+  static class ClassWithoutClinit6 {  // Mimicks ClassWithClinit6 but without the <clinit>.
+    static void $noinline$staticMethod() {
+    }
+  }
+
+  static class SubClassOfClassWithoutClinit6 extends ClassWithoutClinit6 {
+    static {
+      System.out.println("Main$SubClassOfClassWithoutClinit6's static initializer");
+    }
+
+    static void invokeStaticNotInlined() {
+      ClassWithoutClinit6.$noinline$staticMethod();
+    }
+  }
 
   /*
    * Verify that if we have a static call immediately after the load class
@@ -269,7 +387,7 @@
 
   static void noClinitBecauseOfInvokeStatic() {
     ClassWithClinit2.$noinline$staticMethod();
-    ClassWithClinit2.doThrow = false;
+    ClassWithClinit2.staticField = false;
   }
 
   /*
@@ -286,7 +404,7 @@
   /// CHECK-START: void Main.clinitBecauseOfFieldAccess() liveness (before)
   /// CHECK-NOT:                           ClinitCheck
   static void clinitBecauseOfFieldAccess() {
-    ClassWithClinit2.doThrow = false;
+    ClassWithClinit2.staticField = false;
     ClassWithClinit2.$noinline$staticMethod();
   }
 
@@ -317,8 +435,6 @@
 
     static void $noinline$someStaticMethod(Iterable<?> it) {
       it.iterator();
-      // We're not inlining throw at the moment.
-      if (doThrow) { throw new Error(""); }
     }
   }
 
@@ -349,8 +465,6 @@
 
     static void $noinline$someStaticMethod(Iterable<?> it) {
       it.iterator();
-      // We're not inlining throw at the moment.
-      if (doThrow) { throw new Error(""); }
     }
   }
 
@@ -378,8 +492,6 @@
 
     static void $noinline$someStaticMethod(Iterable<?> it) {
       it.iterator();
-      // We're not inlining throw at the moment.
-      if (doThrow) { throw new Error(""); }
     }
   }
 
@@ -511,14 +623,11 @@
 
     public static void $noinline$getIterator(Iterable<?> it) {
       it.iterator();
-      // We're not inlining throw at the moment.
-      if (doThrow) { throw new Error(""); }
     }
   }
 
   // TODO: Write checker statements.
   static Object $noinline$testInliningAndNewInstance(Iterable<?> it) {
-    if (doThrow) { throw new Error(); }
     ClassWithClinit13.$inline$forwardToGetIterator(it);
     return new ClassWithClinit13();
   }
@@ -531,10 +640,14 @@
   public static void main(String[] args) {
     invokeStaticInlined();
     invokeStaticNotInlined();
-    ClassWithClinit3.invokeStaticInlined();
-    ClassWithClinit4.invokeStaticNotInlined();
+    ClassWithClinit3Static.invokeStaticInlined();
+    new ClassWithClinit3Instance().invokeStaticInlined();
+    ClassWithClinit4Static.invokeStaticNotInlined();
+    new ClassWithClinit4Instance().invokeStaticNotInlined();
     SubClassOfClassWithClinit5.invokeStaticInlined();
+    SubClassOfClassWithoutClinit5.invokeStaticInlined();
     SubClassOfClassWithClinit6.invokeStaticNotInlined();
+    SubClassOfClassWithoutClinit6.invokeStaticNotInlined();
     Iterable it = new Iterable() { public java.util.Iterator iterator() { return null; } };
     constClassAndInvokeStatic(it);
     sgetAndInvokeStatic(it);
diff --git a/test/551-checker-clinit/src/Main.java b/test/551-checker-clinit/src/Main.java
index 86fca80..ab92cd0 100644
--- a/test/551-checker-clinit/src/Main.java
+++ b/test/551-checker-clinit/src/Main.java
@@ -19,6 +19,16 @@
   public static void main(String[] args) {}
   public static int foo = 42;
 
+  // Primitive array initialization is trivial for purposes of the ClinitCheck. It cannot
+  // leak instances of erroneous classes or initialize subclasses of erroneous classes.
+  public static int[] array1 = new int[] { 1, 2, 3 };
+  public static int[] array2;
+  static {
+    int[] a = new int[4];
+    a[0] = 42;
+    array2 = a;
+  }
+
   /// CHECK-START: void Main.inlinedMethod() builder (after)
   /// CHECK:                        ClinitCheck
 
@@ -33,15 +43,17 @@
 
 class Sub extends Main {
   /// CHECK-START: void Sub.invokeSuperClass() builder (after)
-  /// CHECK:                        ClinitCheck
+  /// CHECK-NOT:                    ClinitCheck
   public void invokeSuperClass() {
-    int a = Main.foo;  // Class initialization check must be preserved. b/62478025
+    // No Class initialization check as Main.<clinit> is trivial. b/62478025
+    int a = Main.foo;
   }
 
   /// CHECK-START: void Sub.invokeItself() builder (after)
-  /// CHECK:                        ClinitCheck
+  /// CHECK-NOT:                    ClinitCheck
   public void invokeItself() {
-    int a = foo;  // Class initialization check must be preserved. b/62478025
+    // No Class initialization check as Sub.<clinit> and Main.<clinit> are trivial. b/62478025
+    int a = foo;
   }
 
   /// CHECK-START: void Sub.invokeSubClass() builder (after)
diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java
index 3a5fe33..9f4caec 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -409,11 +409,11 @@
   /// CHECK-DAG:        StaticFieldSet
   /// CHECK-DAG:        StaticFieldSet
   public void accessFields() {
-    static_variable = 0;  // Force ClinitCheck outside the loop. b/62478025
     my_obj = new ExampleObj(1, 2);
     for (int i = 0; i < 10; i++) {
       my_obj.n1++;
       my_obj.n2++;
+      // Note: ClinitCheck(Main) is eliminated because Main initialization is trivial. b/62478025
       number1++;
       number2++;
     }