ART: Change UnresolvedMergedType internal representation

Squashed cherry-picks:
* 067f1ed7816cf4eb5d6258ca31b387ddb2073ab7
* 750f7c2827318f6d07620f2ef0321218ea4d8670
* 2f90b3415aadc2587d26c767c6bfb235797119a8
* 2ea7b70b2347969f3735bd0ec1b462bd6d2ff1bd

Bug: 22881413
diff --git a/compiler/dex/gvn_dead_code_elimination.cc b/compiler/dex/gvn_dead_code_elimination.cc
index 044989e..d29b865 100644
--- a/compiler/dex/gvn_dead_code_elimination.cc
+++ b/compiler/dex/gvn_dead_code_elimination.cc
@@ -74,7 +74,7 @@
 GvnDeadCodeElimination::VRegChains::VRegChains(uint32_t num_vregs, ScopedArenaAllocator* alloc)
     : num_vregs_(num_vregs),
       vreg_data_(alloc->AllocArray<VRegValue>(num_vregs, kArenaAllocMisc)),
-      vreg_high_words_(num_vregs, false, Allocator::GetNoopAllocator(),
+      vreg_high_words_(false, Allocator::GetNoopAllocator(),
                        BitVector::BitsToWords(num_vregs),
                        alloc->AllocArray<uint32_t>(BitVector::BitsToWords(num_vregs))),
       mir_data_(alloc->Adapter()) {
diff --git a/runtime/base/bit_vector.cc b/runtime/base/bit_vector.cc
index 39ce0d2..cfd3d24 100644
--- a/runtime/base/bit_vector.cc
+++ b/runtime/base/bit_vector.cc
@@ -24,11 +24,7 @@
 
 namespace art {
 
-// TODO: replace excessive argument defaulting when we are at gcc 4.7
-// or later on host with delegating constructor support. Specifically,
-// starts_bits and storage_size/storage are mutually exclusive.
-BitVector::BitVector(uint32_t start_bits,
-                     bool expandable,
+BitVector::BitVector(bool expandable,
                      Allocator* allocator,
                      uint32_t storage_size,
                      uint32_t* storage)
@@ -36,12 +32,31 @@
     storage_size_(storage_size),
     allocator_(allocator),
     expandable_(expandable) {
+  DCHECK(storage_ != nullptr);
+
   static_assert(sizeof(*storage_) == kWordBytes, "word bytes");
   static_assert(sizeof(*storage_) * 8u == kWordBits, "word bits");
-  if (storage_ == nullptr) {
-    storage_size_ = BitsToWords(start_bits);
-    storage_ = static_cast<uint32_t*>(allocator_->Alloc(storage_size_ * kWordBytes));
-  }
+}
+
+BitVector::BitVector(uint32_t start_bits,
+                     bool expandable,
+                     Allocator* allocator)
+  : BitVector(expandable,
+              allocator,
+              BitsToWords(start_bits),
+              static_cast<uint32_t*>(allocator->Alloc(BitsToWords(start_bits) * kWordBytes))) {
+}
+
+
+BitVector::BitVector(const BitVector& src,
+                     bool expandable,
+                     Allocator* allocator)
+  : BitVector(expandable,
+              allocator,
+              src.storage_size_,
+              static_cast<uint32_t*>(allocator->Alloc(src.storage_size_ * kWordBytes))) {
+  // Direct memcpy would be faster, but this should be fine too and is cleaner.
+  Copy(&src);
 }
 
 BitVector::~BitVector() {
@@ -357,4 +372,8 @@
   }
 }
 
+Allocator* BitVector::GetAllocator() const {
+  return allocator_;
+}
+
 }  // namespace art
diff --git a/runtime/base/bit_vector.h b/runtime/base/bit_vector.h
index 17835f5..237bc90 100644
--- a/runtime/base/bit_vector.h
+++ b/runtime/base/bit_vector.h
@@ -112,9 +112,16 @@
 
   BitVector(uint32_t start_bits,
             bool expandable,
+            Allocator* allocator);
+
+  BitVector(bool expandable,
             Allocator* allocator,
-            uint32_t storage_size = 0,
-            uint32_t* storage = nullptr);
+            uint32_t storage_size,
+            uint32_t* storage);
+
+  BitVector(const BitVector& src,
+            bool expandable,
+            Allocator* allocator);
 
   virtual ~BitVector();
 
@@ -231,6 +238,8 @@
 
   void Dump(std::ostream& os, const char* prefix) const;
 
+  Allocator* GetAllocator() const;
+
  private:
   /**
    * @brief Dump the bitvector into buffer in a 00101..01 format.
diff --git a/runtime/base/bit_vector_test.cc b/runtime/base/bit_vector_test.cc
index c51b9b0..76095c2 100644
--- a/runtime/base/bit_vector_test.cc
+++ b/runtime/base/bit_vector_test.cc
@@ -71,7 +71,7 @@
   uint32_t bits[kWords];
   memset(bits, 0, sizeof(bits));
 
-  BitVector bv(0U, false, Allocator::GetNoopAllocator(), kWords, bits);
+  BitVector bv(false, Allocator::GetNoopAllocator(), kWords, bits);
   EXPECT_EQ(kWords, bv.GetStorageSize());
   EXPECT_EQ(kWords * sizeof(uint32_t), bv.GetSizeOf());
   EXPECT_EQ(bits, bv.GetRawStorage());
@@ -128,7 +128,7 @@
   uint32_t bits[kWords];
   memset(bits, 0, sizeof(bits));
 
-  BitVector bv(0U, false, Allocator::GetNoopAllocator(), kWords, bits);
+  BitVector bv(false, Allocator::GetNoopAllocator(), kWords, bits);
   bv.SetInitialBits(0u);
   EXPECT_EQ(0u, bv.NumSetBits());
   bv.SetInitialBits(1u);
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 1435607..2cdb73d 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -16,6 +16,7 @@
 
 #include "reg_type-inl.h"
 
+#include "base/bit_vector-inl.h"
 #include "base/casts.h"
 #include "class_linker-inl.h"
 #include "dex_file-inl.h"
@@ -307,13 +308,17 @@
 
 std::string UnresolvedMergedType::Dump() const {
   std::stringstream result;
-  std::set<uint16_t> types = GetMergedTypes();
-  result << "UnresolvedMergedReferences(";
-  auto it = types.begin();
-  result << reg_type_cache_->GetFromId(*it).Dump();
-  for (++it; it != types.end(); ++it) {
-    result << ", ";
-    result << reg_type_cache_->GetFromId(*it).Dump();
+  result << "UnresolvedMergedReferences(" << GetResolvedPart().Dump() << " | ";
+  const BitVector& types = GetUnresolvedTypes();
+
+  bool first = true;
+  for (uint32_t idx : types.Indexes()) {
+    if (!first) {
+      result << ", ";
+    } else {
+      first = false;
+    }
+    result << reg_type_cache_->GetFromId(idx).Dump();
   }
   result << ")";
   return result.str();
@@ -490,32 +495,6 @@
   return true;
 }
 
-std::set<uint16_t> UnresolvedMergedType::GetMergedTypes() const {
-  std::pair<uint16_t, uint16_t> refs = GetTopMergedTypes();
-  const RegType& left = reg_type_cache_->GetFromId(refs.first);
-  const RegType& right = reg_type_cache_->GetFromId(refs.second);
-
-  std::set<uint16_t> types;
-  if (left.IsUnresolvedMergedReference()) {
-    types = down_cast<const UnresolvedMergedType*>(&left)->GetMergedTypes();
-  } else {
-    types.insert(refs.first);
-  }
-  if (right.IsUnresolvedMergedReference()) {
-    std::set<uint16_t> right_types =
-        down_cast<const UnresolvedMergedType*>(&right)->GetMergedTypes();
-    types.insert(right_types.begin(), right_types.end());
-  } else {
-    types.insert(refs.second);
-  }
-  if (kIsDebugBuild) {
-    for (const auto& type : types) {
-      CHECK(!reg_type_cache_->GetFromId(type).IsUnresolvedMergedReference());
-    }
-  }
-  return types;
-}
-
 const RegType& RegType::GetSuperClass(RegTypeCache* cache) const {
   if (!IsUnresolvedTypes()) {
     mirror::Class* super_klass = GetClass()->GetSuperClass();
@@ -805,12 +784,24 @@
   CHECK(klass_.IsNull()) << *this;
 }
 
+UnresolvedMergedType::UnresolvedMergedType(const RegType& resolved,
+                                           const BitVector& unresolved,
+                                           const RegTypeCache* reg_type_cache,
+                                           uint16_t cache_id)
+    : UnresolvedType("", cache_id),
+      reg_type_cache_(reg_type_cache),
+      resolved_part_(resolved),
+      unresolved_types_(unresolved, false, unresolved.GetAllocator()) {
+  if (kIsDebugBuild) {
+    CheckInvariants();
+  }
+}
 void UnresolvedMergedType::CheckInvariants() const {
   // Unresolved merged types: merged types should be defined.
   CHECK(descriptor_.empty()) << *this;
   CHECK(klass_.IsNull()) << *this;
-  CHECK_NE(merged_types_.first, 0U) << *this;
-  CHECK_NE(merged_types_.second, 0U) << *this;
+  CHECK(resolved_part_.IsReferenceTypes());
+  CHECK(!resolved_part_.IsUnresolvedTypes());
 }
 
 void UnresolvedReferenceType::CheckInvariants() const {
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index d08c937..2af0ead 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -22,6 +22,7 @@
 #include <set>
 #include <string>
 
+#include "base/bit_vector.h"
 #include "base/macros.h"
 #include "base/mutex.h"
 #include "gc_root.h"
@@ -230,6 +231,14 @@
   // from another.
   const RegType& Merge(const RegType& incoming_type, RegTypeCache* reg_types) const
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  // Same as above, but also handles the case where incoming_type == this.
+  const RegType& SafeMerge(const RegType& incoming_type, RegTypeCache* reg_types) const
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (Equals(incoming_type)) {
+      return *this;
+    }
+    return Merge(incoming_type, reg_types);
+  }
 
   /*
    * A basic Join operation on classes. For a pair of types S and T the Join,
@@ -868,30 +877,23 @@
   const RegTypeCache* const reg_type_cache_;
 };
 
-// A merge of two unresolved types. If the types were resolved this may be
-// Conflict or another
-// known ReferenceType.
+// A merge of unresolved (and resolved) types. If the types were resolved this may be
+// Conflict or another known ReferenceType.
 class UnresolvedMergedType FINAL : public UnresolvedType {
  public:
-  UnresolvedMergedType(uint16_t left_id, uint16_t right_id,
+  // Note: the constructor will copy the unresolved BitVector, not use it directly.
+  UnresolvedMergedType(const RegType& resolved, const BitVector& unresolved,
                        const RegTypeCache* reg_type_cache, uint16_t cache_id)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
-      : UnresolvedType("", cache_id),
-        reg_type_cache_(reg_type_cache),
-        merged_types_(left_id, right_id) {
-    if (kIsDebugBuild) {
-      CheckInvariants();
-    }
-  }
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  // The top of a tree of merged types.
-  std::pair<uint16_t, uint16_t> GetTopMergedTypes() const {
-    DCHECK(IsUnresolvedMergedReference());
-    return merged_types_;
+  // The resolved part. See description below.
+  const RegType& GetResolvedPart() const {
+    return resolved_part_;
   }
-
-  // The complete set of merged types.
-  std::set<uint16_t> GetMergedTypes() const;
+  // The unresolved part.
+  const BitVector& GetUnresolvedTypes() const {
+    return unresolved_types_;
+  }
 
   bool IsUnresolvedMergedReference() const OVERRIDE { return true; }
 
@@ -903,7 +905,16 @@
   void CheckInvariants() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   const RegTypeCache* const reg_type_cache_;
-  const std::pair<uint16_t, uint16_t> merged_types_;
+
+  // The original implementation of merged types was a binary tree. Collection of the flattened
+  // types ("leaves") can be expensive, so we store the expanded list now, as two components:
+  // 1) A resolved component. We use Zero when there is no resolved component, as that will be
+  //    an identity merge.
+  // 2) A bitvector of the unresolved reference types. A bitvector was chosen with the assumption
+  //    that there should not be too many types in flight in practice. (We also bias the index
+  //    against the index of Zero, which is one of the later default entries in any cache.)
+  const RegType& resolved_part_;
+  const BitVector unresolved_types_;
 };
 
 std::ostream& operator<<(std::ostream& os, const RegType& rhs)
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index b371d7e..a597c8f 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -317,39 +317,62 @@
 }
 
 const RegType& RegTypeCache::FromUnresolvedMerge(const RegType& left, const RegType& right) {
-  std::set<uint16_t> types;
+  BitVector types(1,                                    // Allocate at least a word.
+                  true,                                 // Is expandable.
+                  Allocator::GetMallocAllocator());     // TODO: Arenas in the verifier.
+  const RegType* left_resolved;
   if (left.IsUnresolvedMergedReference()) {
-    RegType& non_const(const_cast<RegType&>(left));
-    types = (down_cast<UnresolvedMergedType*>(&non_const))->GetMergedTypes();
+    const UnresolvedMergedType* left_merge = down_cast<const UnresolvedMergedType*>(&left);
+    types.Copy(&left_merge->GetUnresolvedTypes());
+    left_resolved = &left_merge->GetResolvedPart();
+  } else if (left.IsUnresolvedReference()) {
+    types.SetBit(left.GetId());
+    left_resolved = &Zero();
   } else {
-    types.insert(left.GetId());
+    left_resolved = &left;
   }
+
+  const RegType* right_resolved;
   if (right.IsUnresolvedMergedReference()) {
-    RegType& non_const(const_cast<RegType&>(right));
-    std::set<uint16_t> right_types = (down_cast<UnresolvedMergedType*>(&non_const))->GetMergedTypes();
-    types.insert(right_types.begin(), right_types.end());
+    const UnresolvedMergedType* right_merge = down_cast<const UnresolvedMergedType*>(&right);
+    types.Union(&right_merge->GetUnresolvedTypes());
+    right_resolved = &right_merge->GetResolvedPart();
+  } else if (right.IsUnresolvedReference()) {
+    types.SetBit(right.GetId());
+    right_resolved = &Zero();
   } else {
-    types.insert(right.GetId());
+    right_resolved = &right;
   }
+
+  // Merge the resolved parts. Left and right might be equal, so use SafeMerge.
+  const RegType& resolved_parts_merged = left_resolved->SafeMerge(*right_resolved, this);
+  // If we get a conflict here, the merge result is a conflict, not an unresolved merge type.
+  if (resolved_parts_merged.IsConflict()) {
+    return Conflict();
+  }
+
   // Check if entry already exists.
   for (size_t i = primitive_count_; i < entries_.size(); i++) {
     const RegType* cur_entry = entries_[i];
     if (cur_entry->IsUnresolvedMergedReference()) {
-      std::set<uint16_t> cur_entry_types =
-          (down_cast<const UnresolvedMergedType*>(cur_entry))->GetMergedTypes();
-      if (cur_entry_types == types) {
+      const UnresolvedMergedType* cmp_type = down_cast<const UnresolvedMergedType*>(cur_entry);
+      const RegType& resolved_part = cmp_type->GetResolvedPart();
+      const BitVector& unresolved_part = cmp_type->GetUnresolvedTypes();
+      // Use SameBitsSet. "types" is expandable to allow merging in the components, but the
+      // BitVector in the final RegType will be made non-expandable.
+      if (&resolved_part == &resolved_parts_merged &&
+              types.SameBitsSet(&unresolved_part)) {
         return *cur_entry;
       }
     }
   }
+
   // Create entry.
-  RegType* entry = new UnresolvedMergedType(left.GetId(), right.GetId(), this, entries_.size());
+  RegType* entry = new UnresolvedMergedType(resolved_parts_merged,
+                                            types,
+                                            this,
+                                            entries_.size());
   AddEntry(entry);
-  if (kIsDebugBuild) {
-    UnresolvedMergedType* tmp_entry = down_cast<UnresolvedMergedType*>(entry);
-    std::set<uint16_t> check_types = tmp_entry->GetMergedTypes();
-    CHECK(check_types == types);
-  }
   return *entry;
 }
 
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index 2fecc8b..971b1f5 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -18,6 +18,7 @@
 
 #include <set>
 
+#include "base/bit_vector.h"
 #include "base/casts.h"
 #include "common_runtime_test.h"
 #include "reg_type_cache-inl.h"
@@ -421,7 +422,7 @@
   EXPECT_EQ(expected, resolved_unintialiesd.Dump());
   expected = "Unresolved And Uninitialized Reference: java.lang.DoesNotExist Allocation PC: 12";
   EXPECT_EQ(expected, unresolved_unintialized.Dump());
-  expected = "UnresolvedMergedReferences(Unresolved Reference: java.lang.DoesNotExist, Unresolved Reference: java.lang.DoesNotExistEither)";
+  expected = "UnresolvedMergedReferences(Zero/null | Unresolved Reference: java.lang.DoesNotExist, Unresolved Reference: java.lang.DoesNotExistEither)";
   EXPECT_EQ(expected, unresolved_merged.Dump());
 }
 
@@ -477,9 +478,10 @@
   EXPECT_TRUE(merged.IsUnresolvedMergedReference());
   RegType& merged_nonconst = const_cast<RegType&>(merged);
 
-  std::set<uint16_t> merged_ids = (down_cast<UnresolvedMergedType*>(&merged_nonconst))->GetMergedTypes();
-  EXPECT_EQ(ref_type_0.GetId(), *(merged_ids.begin()));
-  EXPECT_EQ(ref_type_1.GetId(), *((++merged_ids.begin())));
+  const BitVector& unresolved_parts =
+      down_cast<UnresolvedMergedType*>(&merged_nonconst)->GetUnresolvedTypes();
+  EXPECT_TRUE(unresolved_parts.IsBitSet(ref_type_0.GetId()));
+  EXPECT_TRUE(unresolved_parts.IsBitSet(ref_type_1.GetId()));
 }
 
 TEST_F(RegTypeTest, MergingFloat) {
diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt
index 659f104..77668da 100644
--- a/test/800-smali/expected.txt
+++ b/test/800-smali/expected.txt
@@ -27,4 +27,5 @@
 b/22331663
 b/22331663 (pass)
 b/22331663 (fail)
+b/22881413
 Done!
diff --git a/test/800-smali/smali/b_22881413.smali b/test/800-smali/smali/b_22881413.smali
new file mode 100644
index 0000000..f624734
--- /dev/null
+++ b/test/800-smali/smali/b_22881413.smali
@@ -0,0 +1,291 @@
+.class public LB22881413;
+.super Ljava/lang/Object;
+
+# A couple of fields to allow "loading" resolved and unresolved types. Use non-final classes to
+# avoid automatically getting precise reference types.
+.field private static res1:Ljava/lang/Number;
+.field private static res2:Ljava/lang/ClassLoader;
+.field private static res3:Ljava/lang/Package;
+.field private static res4:Ljava/lang/RuntimeException;
+.field private static res5:Ljava/lang/Exception;
+.field private static res6:Ljava/util/ArrayList;
+.field private static res7:Ljava/util/LinkedList;
+.field private static res8:Ljava/lang/Thread;
+.field private static res9:Ljava/lang/ThreadGroup;
+.field private static res10:Ljava/lang/Runtime;
+
+.field private static unres1:La/b/c/d1;
+.field private static unres2:La/b/c/d2;
+.field private static unres3:La/b/c/d3;
+.field private static unres4:La/b/c/d4;
+.field private static unres5:La/b/c/d5;
+.field private static unres6:La/b/c/d6;
+.field private static unres7:La/b/c/d7;
+.field private static unres8:La/b/c/d8;
+.field private static unres9:La/b/c/d9;
+.field private static unres10:La/b/c/d10;
+
+.field private static unresBase0:La/b/c/dBase0;
+.field private static unresBase1:La/b/c/dBase1;
+.field private static unresBase2:La/b/c/dBase2;
+.field private static unresBase3:La/b/c/dBase3;
+.field private static unresBase4:La/b/c/dBase4;
+.field private static unresBase5:La/b/c/dBase5;
+.field private static unresBase6:La/b/c/dBase6;
+.field private static unresBase7:La/b/c/dBase7;
+.field private static unresBase8:La/b/c/dBase8;
+
+# Empty, ignore this. We want to see if the other method can be verified in a reasonable amount of
+# time.
+.method public static run()V
+.registers 2
+       return-void
+.end method
+
+.method public static foo(IZZ) V
+.registers 11
+       # v8 = int, v9 = boolean, v10 = boolean
+
+       sget-object v0, LB22881413;->unresBase0:La/b/c/dBase0;
+       const v1, 0
+       const v2, 0
+
+# We're trying to create something like this (with more loops to amplify things).
+#
+# v0 = Unresolved1
+# while (something) {
+#
+#   [Repeatedly]
+#   if (cond) {
+#     v0 = ResolvedX;
+#   } else {
+#     v0 = UnresolvedX;
+#   }
+#
+#   v0 = Unresolved2
+# };
+#
+# Important points:
+#   1) Use a while, so that the end of the loop is a goto. That way, the merging of outer-loop
+#      unresolved classes is postponed.
+#   2) Put the else cases after all if cases. That way there are backward gotos that will lead
+#      to stabilization loops in the body.
+#
+
+:Loop1
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop1End
+
+:Loop2
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop2End
+
+:Loop3
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop3End
+
+:Loop4
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop4End
+
+:Loop5
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop5End
+
+:Loop6
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop6End
+
+:Loop7
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop7End
+
+:Loop8
+
+       const v6, 0
+       add-int/lit16 v8, v8, -1
+       if-ge v8, v6, :Loop8End
+
+# Prototype:
+#
+#       if-eqz v9, :ElseX
+#       sget-object v0, LB22881413;->res1:Ljava/lang/Number;
+#:JoinX
+#
+# And somewhere at the end
+#
+#:ElseX
+#       sget-object v0, LB22881413;->unresX:La/b/c/dX;
+#       goto :JoinX
+#
+#
+
+       if-eqz v10, :Join1
+       if-eqz v9, :Else1
+       sget-object v0, LB22881413;->res1:Ljava/lang/Number;
+:Join1
+
+
+       if-eqz v10, :Join2
+       if-eqz v9, :Else2
+       sget-object v0, LB22881413;->res2:Ljava/lang/ClassLoader;
+:Join2
+
+
+       if-eqz v10, :Join3
+       if-eqz v9, :Else3
+       sget-object v0, LB22881413;->res3:Ljava/lang/Package;
+:Join3
+
+
+       if-eqz v10, :Join4
+       if-eqz v9, :Else4
+       sget-object v0, LB22881413;->res4:Ljava/lang/RuntimeException;
+:Join4
+
+
+       if-eqz v10, :Join5
+       if-eqz v9, :Else5
+       sget-object v0, LB22881413;->res5:Ljava/lang/Exception;
+:Join5
+
+
+       if-eqz v10, :Join6
+       if-eqz v9, :Else6
+       sget-object v0, LB22881413;->res6:Ljava/util/ArrayList;
+:Join6
+
+
+       if-eqz v10, :Join7
+       if-eqz v9, :Else7
+       sget-object v0, LB22881413;->res7:Ljava/util/LinkedList;
+:Join7
+
+
+       if-eqz v10, :Join8
+       if-eqz v9, :Else8
+       sget-object v0, LB22881413;->res8:Ljava/lang/Thread;
+:Join8
+
+
+       if-eqz v10, :Join9
+       if-eqz v9, :Else9
+       sget-object v0, LB22881413;->res9:Ljava/lang/ThreadGroup;
+:Join9
+
+
+       if-eqz v10, :Join10
+       if-eqz v9, :Else10
+       sget-object v0, LB22881413;->res10:Ljava/lang/Runtime;
+:Join10
+
+
+       goto :InnerMostLoopEnd
+
+:Else1
+       sget-object v0, LB22881413;->unres1:La/b/c/d1;
+       goto :Join1
+
+:Else2
+       sget-object v0, LB22881413;->unres2:La/b/c/d2;
+       goto :Join2
+
+:Else3
+       sget-object v0, LB22881413;->unres3:La/b/c/d3;
+       goto :Join3
+
+:Else4
+       sget-object v0, LB22881413;->unres4:La/b/c/d4;
+       goto :Join4
+
+:Else5
+       sget-object v0, LB22881413;->unres5:La/b/c/d5;
+       goto :Join5
+
+:Else6
+       sget-object v0, LB22881413;->unres6:La/b/c/d6;
+       goto :Join6
+
+:Else7
+       sget-object v0, LB22881413;->unres7:La/b/c/d7;
+       goto :Join7
+
+:Else8
+       sget-object v0, LB22881413;->unres8:La/b/c/d8;
+       goto :Join8
+
+:Else9
+       sget-object v0, LB22881413;->unres9:La/b/c/d9;
+       goto :Join9
+
+:Else10
+       sget-object v0, LB22881413;->unres10:La/b/c/d10;
+       goto :Join10
+
+:InnerMostLoopEnd
+
+       # Loop 8 end of body.
+       sget-object v0, LB22881413;->unresBase8:La/b/c/dBase8;
+       goto :Loop8
+
+:Loop8End
+
+       # Loop 7 end of body.
+       sget-object v0, LB22881413;->unresBase7:La/b/c/dBase7;
+       goto :Loop7
+
+:Loop7End
+
+       # Loop 6 end of body.
+       sget-object v0, LB22881413;->unresBase6:La/b/c/dBase6;
+       goto :Loop6
+
+:Loop6End
+
+       # Loop 5 end of body
+       sget-object v0, LB22881413;->unresBase5:La/b/c/dBase5;
+       goto :Loop5
+
+:Loop5End
+
+       # Loop 4 end of body
+       sget-object v0, LB22881413;->unresBase4:La/b/c/dBase4;
+       goto :Loop4
+
+:Loop4End
+
+       # Loop 3 end of body
+       sget-object v0, LB22881413;->unresBase3:La/b/c/dBase3;
+       goto :Loop3
+
+:Loop3End
+
+       # Loop 2 end of body
+       sget-object v0, LB22881413;->unresBase2:La/b/c/dBase2;
+       goto :Loop2
+
+:Loop2End
+
+       # Loop 1 end of body
+       sget-object v0, LB22881413;->unresBase1:La/b/c/dBase1;
+       goto :Loop1
+
+:Loop1End
+
+       return-void
+
+.end method
diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java
index 709c7f6..7ee1e45 100644
--- a/test/800-smali/src/Main.java
+++ b/test/800-smali/src/Main.java
@@ -101,6 +101,7 @@
                 new Object[] { false }, null, null));
         testCases.add(new TestCase("b/22331663 (fail)", "B22331663Fail", "run",
                 new Object[] { false }, new VerifyError(), null));
+        testCases.add(new TestCase("b/22881413", "B22881413", "run", null, null, null));
     }
 
     public void runTests() {
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 240ed41..1a8853a 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -316,6 +316,20 @@
                       --dex-file=$DEX_LOCATION/$TEST_NAME.jar \
                       --oat-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.dex | cut -d/ -f 2- | sed "s:/:@:g") \
                       --instruction-set=$ISA"
+  if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
+    dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
+  fi
+
+  # Add in a timeout. This is important for testing the compilation/verification time of
+  # pathological cases.
+  # Note: as we don't know how decent targets are (e.g., emulator), only do this on the host for
+  #       now. We should try to improve this.
+  #       The current value is rather arbitrary. run-tests should compile quickly.
+  if [ "$HOST" != "n" ]; then
+    # Use SIGRTMIN+2 to try to dump threads.
+    # Use -k 1m to SIGKILL it a minute later if it hasn't ended.
+    dex2oat_cmdline="timeout -k 1m -s SIGRTMIN+2 1m ${dex2oat_cmdline}"
+  fi
 fi
 
 dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ROOT/bin/$DALVIKVM \