ART: Add art_quick_unlock_object to stub_test

Change-Id: Iaa8143fdc0144f5fa5e5c304995e2d7a2e0c91ca
diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc
index 8fbca94..5d9ad2c 100644
--- a/runtime/arch/stub_test.cc
+++ b/runtime/arch/stub_test.cc
@@ -182,6 +182,7 @@
 #endif
 }
 
+static constexpr size_t kThinLockLoops = 100;
 
 #if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
 extern "C" void art_quick_lock_object(void);
@@ -206,6 +207,70 @@
   LockWord lock_after = obj->GetLockWord(false);
   LockWord::LockState new_state = lock_after.GetState();
   EXPECT_EQ(LockWord::LockState::kThinLocked, new_state);
+  EXPECT_EQ(lock_after.ThinLockCount(), 0U);  // Thin lock starts count at zero
+
+  for (size_t i = 1; i < kThinLockLoops; ++i) {
+    Invoke3(reinterpret_cast<size_t>(obj.get()), 0U, 0U,
+              reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
+
+    // Check we're at lock count i
+
+    LockWord l_inc = obj->GetLockWord(false);
+    LockWord::LockState l_inc_state = l_inc.GetState();
+    EXPECT_EQ(LockWord::LockState::kThinLocked, l_inc_state);
+    EXPECT_EQ(l_inc.ThinLockCount(), i);
+  }
+
+  // TODO: Improve this test. Somehow force it to go to fat locked. But that needs another thread.
+
+#else
+  LOG(INFO) << "Skipping lock_object as I don't know how to do that on " << kRuntimeISA;
+  // Force-print to std::cout so it's also outside the logcat.
+  std::cout << "Skipping lock_object as I don't know how to do that on " << kRuntimeISA << std::endl;
+#endif
+}
+
+class RandGen {
+ public:
+  explicit RandGen(uint32_t seed) : val_(seed) {}
+
+  uint32_t next() {
+    val_ = val_ * 48271 % 2147483647 + 13;
+    return val_;
+  }
+
+  uint32_t val_;
+};
+
+
+#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+extern "C" void art_quick_lock_object(void);
+extern "C" void art_quick_unlock_object(void);
+#endif
+
+TEST_F(StubTest, UnlockObject) {
+#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+  Thread* self = Thread::Current();
+  // Create an object
+  ScopedObjectAccess soa(self);
+  // garbage is created during ClassLinker::Init
+
+  SirtRef<mirror::String> obj(soa.Self(),
+                              mirror::String::AllocFromModifiedUtf8(soa.Self(), "hello, world!"));
+  LockWord lock = obj->GetLockWord(false);
+  LockWord::LockState old_state = lock.GetState();
+  EXPECT_EQ(LockWord::LockState::kUnlocked, old_state);
+
+  Invoke3(reinterpret_cast<size_t>(obj.get()), 0U, 0U,
+          reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
+
+  // This should be an illegal monitor state.
+  EXPECT_TRUE(self->IsExceptionPending());
+  self->ClearException();
+
+  LockWord lock_after = obj->GetLockWord(false);
+  LockWord::LockState new_state = lock_after.GetState();
+  EXPECT_EQ(LockWord::LockState::kUnlocked, new_state);
 
   Invoke3(reinterpret_cast<size_t>(obj.get()), 0U, 0U,
           reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
@@ -214,12 +279,94 @@
   LockWord::LockState new_state2 = lock_after2.GetState();
   EXPECT_EQ(LockWord::LockState::kThinLocked, new_state2);
 
+  Invoke3(reinterpret_cast<size_t>(obj.get()), 0U, 0U,
+          reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
+
+  LockWord lock_after3 = obj->GetLockWord(false);
+  LockWord::LockState new_state3 = lock_after3.GetState();
+  EXPECT_EQ(LockWord::LockState::kUnlocked, new_state3);
+
+  // Stress test:
+  // Keep a number of objects and their locks in flight. Randomly lock or unlock one of them in
+  // each step.
+
+  RandGen r(0x1234);
+
+  constexpr size_t kNumberOfLocks = 10;  // Number of objects = lock
+  constexpr size_t kIterations = 10000;  // Number of iterations
+
+  size_t counts[kNumberOfLocks];
+  SirtRef<mirror::String>* objects[kNumberOfLocks];
+
+  // Initialize = allocate.
+  for (size_t i = 0; i < kNumberOfLocks; ++i) {
+    counts[i] = 0;
+    objects[i] = new SirtRef<mirror::String>(soa.Self(),
+                                             mirror::String::AllocFromModifiedUtf8(soa.Self(), ""));
+  }
+
+  for (size_t i = 0; i < kIterations; ++i) {
+    // Select which lock to update.
+    size_t index = r.next() % kNumberOfLocks;
+
+    bool lock;  // Whether to lock or unlock in this step.
+    if (counts[index] == 0) {
+      lock = true;
+    } else if (counts[index] == kThinLockLoops) {
+      lock = false;
+    } else {
+      // Randomly.
+      lock = r.next() % 2 == 0;
+    }
+
+    if (lock) {
+      Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
+              reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
+      counts[index]++;
+    } else {
+      Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
+              reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
+      counts[index]--;
+    }
+
+    EXPECT_FALSE(self->IsExceptionPending());
+
+    // Check the new state.
+    LockWord lock_iter = objects[index]->get()->GetLockWord(false);
+    LockWord::LockState iter_state = lock_iter.GetState();
+    if (counts[index] > 0) {
+      EXPECT_EQ(LockWord::LockState::kThinLocked, iter_state);
+      EXPECT_EQ(counts[index] - 1, lock_iter.ThinLockCount());
+    } else {
+      EXPECT_EQ(LockWord::LockState::kUnlocked, iter_state);
+    }
+  }
+
+  // Unlock the remaining count times and then check it's unlocked. Then deallocate.
+  // Go reverse order to correctly handle SirtRefs.
+  for (size_t i = 0; i < kNumberOfLocks; ++i) {
+    size_t index = kNumberOfLocks - 1 - i;
+    size_t count = counts[index];
+    while (count > 0) {
+      Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
+              reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
+
+      count--;
+    }
+
+    LockWord lock_after4 = objects[index]->get()->GetLockWord(false);
+    LockWord::LockState new_state4 = lock_after4.GetState();
+    EXPECT_EQ(LockWord::LockState::kUnlocked, new_state4);
+
+    delete objects[index];
+  }
+
   // TODO: Improve this test. Somehow force it to go to fat locked. But that needs another thread.
 
 #else
-  LOG(INFO) << "Skipping lock_object as I don't know how to do that on " << kRuntimeISA;
+  LOG(INFO) << "Skipping unlock_object as I don't know how to do that on " << kRuntimeISA;
   // Force-print to std::cout so it's also outside the logcat.
-  std::cout << "Skipping lock_object as I don't know how to do that on " << kRuntimeISA << std::endl;
+  std::cout << "Skipping unlock_object as I don't know how to do that on " << kRuntimeISA << std::endl;
 #endif
 }
 
@@ -699,6 +846,8 @@
     }
   }
 
+  // TODO: Deallocate things.
+
   // Tests done.
 #else
   LOG(INFO) << "Skipping string_compareto as I don't know how to do that on " << kRuntimeISA;