summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtime/arch/arm/quick_entrypoints_arm.S2
-rw-r--r--runtime/arch/arm64/quick_entrypoints_arm64.S77
-rw-r--r--runtime/arch/stub_test.cc129
-rw-r--r--runtime/arch/x86_64/quick_entrypoints_x86_64.S4
4 files changed, 163 insertions, 49 deletions
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index dcf4561a9..488656176 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -442,8 +442,8 @@ ENTRY art_quick_unlock_object
cmp r1, #65536
bpl .Lrecursive_thin_unlock
@ transition to unlocked, r3 holds 0
- str r3, [r0, #LOCK_WORD_OFFSET]
dmb ish @ full (StoreLoad) memory barrier
+ str r3, [r0, #LOCK_WORD_OFFSET]
bx lr
.Lrecursive_thin_unlock:
sub r1, r1, #65536
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index f7cb25408..c056b2f81 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -998,8 +998,81 @@ ENTRY art_quick_handle_fill_data
DELIVER_PENDING_EXCEPTION
END art_quick_handle_fill_data
-UNIMPLEMENTED art_quick_lock_object
-UNIMPLEMENTED art_quick_unlock_object
+ /*
+ * Entry from managed code that calls artLockObjectFromCode, may block for GC. x0 holds the
+ * possibly null object to lock.
+ *
+ * Derived from arm32 code.
+ */
+ .extern artLockObjectFromCode
+ENTRY art_quick_lock_object
+ cbz w0, .Lslow_lock
+ add x4, x0, #LOCK_WORD_OFFSET // exclusive load/store had no immediate anymore
+.Lretry_lock:
+ ldr w2, [xSELF, #THREAD_ID_OFFSET] // TODO: Can the thread ID really change during the loop?
+ ldxr w1, [x4]
+ cbnz w1, .Lnot_unlocked // already thin locked
+ stxr w3, w2, [x4]
+ cbnz w3, .Lstrex_fail // store failed, retry
+ dmb ishld // full (LoadLoad) memory barrier, TODO: acquire-release
+ ret
+.Lstrex_fail:
+ b .Lretry_lock // unlikely forward branch, need to reload and recheck r1/r2
+.Lnot_unlocked:
+ lsr w3, w1, 30
+ cbnz w3, .Lslow_lock // if either of the top two bits are set, go slow path
+ eor w2, w1, w2 // lock_word.ThreadId() ^ self->ThreadId()
+ uxth w2, w2 // zero top 16 bits
+ cbnz w2, .Lslow_lock // lock word and self thread id's match -> recursive lock
+ // else contention, go to slow path
+ add w2, w1, #65536 // increment count in lock word placing in w2 for storing
+ lsr w1, w2, 30 // if either of the top two bits are set, we overflowed.
+ cbnz w1, .Lslow_lock // if we overflow the count go slow path
+ str w2, [x0, #LOCK_WORD_OFFSET]// no need for stxr as we hold the lock
+ ret
+.Lslow_lock:
+ SETUP_REF_ONLY_CALLEE_SAVE_FRAME // save callee saves in case we block
+ mov x1, xSELF // pass Thread::Current
+ mov x2, sp // pass SP
+ bl artLockObjectFromCode // (Object* obj, Thread*, SP)
+ RESTORE_REF_ONLY_CALLEE_SAVE_FRAME
+ RETURN_IF_W0_IS_ZERO_OR_DELIVER
+END art_quick_lock_object
+
+ /*
+ * Entry from managed code that calls artUnlockObjectFromCode and delivers exception on failure.
+ * x0 holds the possibly null object to lock.
+ *
+ * Derived from arm32 code.
+ */
+ .extern artUnlockObjectFromCode
+ENTRY art_quick_unlock_object
+ cbz x0, .Lslow_unlock
+ ldr w1, [x0, #LOCK_WORD_OFFSET]
+ lsr w2, w1, 30
+ cbnz w2, .Lslow_unlock // if either of the top two bits are set, go slow path
+ ldr w2, [xSELF, #THREAD_ID_OFFSET]
+ eor w3, w1, w2 // lock_word.ThreadId() ^ self->ThreadId()
+ uxth w3, w3 // zero top 16 bits
+ cbnz w3, .Lslow_unlock // do lock word and self thread id's match?
+ cmp w1, #65536
+ bpl .Lrecursive_thin_unlock
+ // transition to unlocked, w3 holds 0
+ dmb ish // full (StoreLoad) memory barrier
+ str w3, [x0, #LOCK_WORD_OFFSET]
+ ret
+.Lrecursive_thin_unlock:
+ sub w1, w1, #65536
+ str w1, [x0, #LOCK_WORD_OFFSET]
+ ret
+.Lslow_unlock:
+ SETUP_REF_ONLY_CALLEE_SAVE_FRAME // save callee saves in case exception allocation triggers GC
+ mov x1, xSELF // pass Thread::Current
+ mov x2, sp // pass SP
+ bl artUnlockObjectFromCode // (Object* obj, Thread*, SP)
+ RESTORE_REF_ONLY_CALLEE_SAVE_FRAME
+ RETURN_IF_W0_IS_ZERO_OR_DELIVER
+END art_quick_unlock_object
/*
* Entry from managed code that calls artIsAssignableFromCode and on failure calls
diff --git a/runtime/arch/stub_test.cc b/runtime/arch/stub_test.cc
index 86f52aa46..76d028db7 100644
--- a/runtime/arch/stub_test.cc
+++ b/runtime/arch/stub_test.cc
@@ -56,6 +56,7 @@ class StubTest : public CommonRuntimeTest {
return &self->tlsPtr_;
}
+ public:
size_t Invoke3(size_t arg0, size_t arg1, size_t arg2, uintptr_t code, Thread* self) {
// Push a transition back into managed code onto the linked list in thread.
ManagedStack fragment;
@@ -169,7 +170,6 @@ class StubTest : public CommonRuntimeTest {
return result;
}
- public:
// TODO: Set up a frame according to referrer's specs.
size_t Invoke3WithReferrer(size_t arg0, size_t arg1, size_t arg2, uintptr_t code, Thread* self,
mirror::ArtMethod* referrer) {
@@ -357,12 +357,12 @@ TEST_F(StubTest, Memcpy) {
#endif
}
-#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__x86_64__)
extern "C" void art_quick_lock_object(void);
#endif
TEST_F(StubTest, LockObject) {
-#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__x86_64__)
static constexpr size_t kThinLockLoops = 100;
Thread* self = Thread::Current();
@@ -396,8 +396,21 @@ TEST_F(StubTest, LockObject) {
EXPECT_EQ(l_inc.ThinLockCount(), i);
}
- // TODO: Improve this test. Somehow force it to go to fat locked. But that needs another thread.
+ // Force a fat lock by running identity hashcode to fill up lock word.
+ SirtRef<mirror::Object> obj2(soa.Self(), mirror::String::AllocFromModifiedUtf8(soa.Self(),
+ "hello, world!"));
+
+ obj2->IdentityHashCode();
+
+ Invoke3(reinterpret_cast<size_t>(obj2.get()), 0U, 0U,
+ reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
+
+ LockWord lock_after2 = obj2->GetLockWord(false);
+ LockWord::LockState new_state2 = lock_after2.GetState();
+ EXPECT_EQ(LockWord::LockState::kFatLocked, new_state2);
+ EXPECT_NE(lock_after2.FatLockMonitor(), static_cast<Monitor*>(nullptr));
+ // Test done.
#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.
@@ -419,13 +432,14 @@ class RandGen {
};
-#if defined(__i386__) || defined(__arm__) || defined(__x86_64__)
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || 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__)
+// NO_THREAD_SAFETY_ANALYSIS as we do not want to grab exclusive mutator lock for MonitorInfo.
+static void TestUnlockObject(StubTest* test) NO_THREAD_SAFETY_ANALYSIS {
+#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__x86_64__)
static constexpr size_t kThinLockLoops = 100;
Thread* self = Thread::Current();
@@ -439,8 +453,8 @@ TEST_F(StubTest, UnlockObject) {
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);
+ test->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());
@@ -450,15 +464,15 @@ TEST_F(StubTest, UnlockObject) {
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);
+ test->Invoke3(reinterpret_cast<size_t>(obj.get()), 0U, 0U,
+ reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
LockWord lock_after2 = obj->GetLockWord(false);
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);
+ test->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();
@@ -472,13 +486,16 @@ TEST_F(StubTest, UnlockObject) {
constexpr size_t kNumberOfLocks = 10; // Number of objects = lock
constexpr size_t kIterations = 10000; // Number of iterations
+ constexpr size_t kMoveToFat = 1000; // Chance of 1:kMoveFat to make a lock fat.
size_t counts[kNumberOfLocks];
+ bool fat[kNumberOfLocks]; // Whether a lock should be thin or fat.
SirtRef<mirror::String>* objects[kNumberOfLocks];
// Initialize = allocate.
for (size_t i = 0; i < kNumberOfLocks; ++i) {
counts[i] = 0;
+ fat[i] = false;
objects[i] = new SirtRef<mirror::String>(soa.Self(),
mirror::String::AllocFromModifiedUtf8(soa.Self(), ""));
}
@@ -487,36 +504,57 @@ TEST_F(StubTest, UnlockObject) {
// 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;
- }
+ // Make lock fat?
+ if (!fat[index] && (r.next() % kMoveToFat == 0)) {
+ fat[index] = true;
+ objects[index]->get()->IdentityHashCode();
- if (lock) {
- Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
- reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
- counts[index]++;
+ LockWord lock_iter = objects[index]->get()->GetLockWord(false);
+ LockWord::LockState iter_state = lock_iter.GetState();
+ if (counts[index] == 0) {
+ EXPECT_EQ(LockWord::LockState::kHashCode, iter_state);
+ } else {
+ EXPECT_EQ(LockWord::LockState::kFatLocked, iter_state);
+ }
} else {
- Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
- reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
- counts[index]--;
- }
+ 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;
+ }
- EXPECT_FALSE(self->IsExceptionPending());
+ if (lock) {
+ test-> Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
+ reinterpret_cast<uintptr_t>(&art_quick_lock_object), self);
+ counts[index]++;
+ } else {
+ test->Invoke3(reinterpret_cast<size_t>(objects[index]->get()), 0U, 0U,
+ reinterpret_cast<uintptr_t>(&art_quick_unlock_object), self);
+ counts[index]--;
+ }
- // 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);
+ EXPECT_FALSE(self->IsExceptionPending());
+
+ // Check the new state.
+ LockWord lock_iter = objects[index]->get()->GetLockWord(true);
+ LockWord::LockState iter_state = lock_iter.GetState();
+ if (fat[index]) {
+ // Abuse MonitorInfo.
+ EXPECT_EQ(LockWord::LockState::kFatLocked, iter_state) << index;
+ MonitorInfo info(objects[index]->get());
+ EXPECT_EQ(counts[index], info.entry_count_) << index;
+ } else {
+ 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);
+ }
+ }
}
}
@@ -526,21 +564,21 @@ TEST_F(StubTest, UnlockObject) {
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);
+ test->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);
+ EXPECT_TRUE(LockWord::LockState::kUnlocked == new_state4
+ || LockWord::LockState::kFatLocked == new_state4);
delete objects[index];
}
- // TODO: Improve this test. Somehow force it to go to fat locked. But that needs another thread.
-
+ // Test done.
#else
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.
@@ -548,6 +586,9 @@ TEST_F(StubTest, UnlockObject) {
#endif
}
+TEST_F(StubTest, UnlockObject) {
+ TestUnlockObject(this);
+}
#if defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__x86_64__)
extern "C" void art_quick_check_cast(void);
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index a55dbb6b2..b886fb0f9 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -697,8 +697,8 @@ DEFINE_FUNCTION art_quick_unlock_object
jz .Lslow_unlock
movl LOCK_WORD_OFFSET(%edi), %ecx // ecx := lock word
movl %gs:THREAD_ID_OFFSET, %edx // edx := thread id
- test %ecx, %ecx
- jb .Lslow_unlock // lock word contains a monitor
+ test LITERAL(0xC0000000), %ecx
+ jnz .Lslow_unlock // lock word contains a monitor
cmpw %cx, %dx // does the thread id match?
jne .Lslow_unlock
cmpl LITERAL(65536), %ecx