aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Kennke <rkennke@openjdk.org>2023-11-27 16:14:23 +0000
committerVitaly Provodin <vitaly.provodin@jetbrains.com>2024-01-17 20:31:46 +0700
commita8fc42cf6c274e41fb6a0ce58cffc16137e6996e (patch)
tree08b1cbbdd046538a0a67768bf329da48a1d8b359
parentf8996f7e12f0f5ea3816f5e6045ec9ddf9e18d82 (diff)
downloadJetBrainsRuntime-a8fc42cf6c274e41fb6a0ce58cffc16137e6996e.tar.gz
8318895: Deoptimization results in incorrect lightweight locking stack
Backport-of: ea1ffa34192448317ce9a61a3588b0dee3a2ef44
-rw-r--r--src/hotspot/share/runtime/deoptimization.cpp17
-rw-r--r--test/jdk/com/sun/jdi/EATests.java143
2 files changed, 157 insertions, 3 deletions
diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp
index d62100c4cf8..a05f88c9182 100644
--- a/src/hotspot/share/runtime/deoptimization.cpp
+++ b/src/hotspot/share/runtime/deoptimization.cpp
@@ -82,6 +82,7 @@
#include "runtime/stackValue.hpp"
#include "runtime/stackWatermarkSet.hpp"
#include "runtime/stubRoutines.hpp"
+#include "runtime/synchronizer.hpp"
#include "runtime/threadSMR.hpp"
#include "runtime/threadWXSetters.inline.hpp"
#include "runtime/vframe.hpp"
@@ -1648,9 +1649,19 @@ bool Deoptimization::relock_objects(JavaThread* thread, GrowableArray<MonitorInf
}
}
}
- BasicLock* lock = mon_info->lock();
- ObjectSynchronizer::enter(obj, lock, deoptee_thread);
- assert(mon_info->owner()->is_locked(), "object must be locked now");
+ if (LockingMode == LM_LIGHTWEIGHT && exec_mode == Unpack_none) {
+ // We have lost information about the correct state of the lock stack.
+ // Inflate the locks instead. Enter then inflate to avoid races with
+ // deflation.
+ ObjectSynchronizer::enter(obj, nullptr, deoptee_thread);
+ assert(mon_info->owner()->is_locked(), "object must be locked now");
+ ObjectMonitor* mon = ObjectSynchronizer::inflate(deoptee_thread, obj(), ObjectSynchronizer::inflate_cause_vm_internal);
+ assert(mon->owner() == deoptee_thread, "must be");
+ } else {
+ BasicLock* lock = mon_info->lock();
+ ObjectSynchronizer::enter(obj, lock, deoptee_thread);
+ assert(mon_info->owner()->is_locked(), "object must be locked now");
+ }
}
}
}
diff --git a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java
index 9cdf2d285f2..1f99b2ccac6 100644
--- a/test/jdk/com/sun/jdi/EATests.java
+++ b/test/jdk/com/sun/jdi/EATests.java
@@ -42,6 +42,7 @@
* -XX:+WhiteBoxAPI
* -Xbatch
* -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=1
* @run driver EATests
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
@@ -50,6 +51,7 @@
* -XX:+WhiteBoxAPI
* -Xbatch
* -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:-EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=1
* @run driver EATests
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
@@ -58,6 +60,7 @@
* -XX:+WhiteBoxAPI
* -Xbatch
* -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=1
* @run driver EATests
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
@@ -66,6 +69,44 @@
* -XX:+WhiteBoxAPI
* -Xbatch
* -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=1
+ *
+ * @run driver EATests
+ * -XX:+UnlockDiagnosticVMOptions
+ * -Xms256m -Xmx256m
+ * -Xbootclasspath/a:.
+ * -XX:CompileCommand=dontinline,*::dontinline_*
+ * -XX:+WhiteBoxAPI
+ * -Xbatch
+ * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=2
+ * @run driver EATests
+ * -XX:+UnlockDiagnosticVMOptions
+ * -Xms256m -Xmx256m
+ * -Xbootclasspath/a:.
+ * -XX:CompileCommand=dontinline,*::dontinline_*
+ * -XX:+WhiteBoxAPI
+ * -Xbatch
+ * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:-EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=2
+ * @run driver EATests
+ * -XX:+UnlockDiagnosticVMOptions
+ * -Xms256m -Xmx256m
+ * -Xbootclasspath/a:.
+ * -XX:CompileCommand=dontinline,*::dontinline_*
+ * -XX:+WhiteBoxAPI
+ * -Xbatch
+ * -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=2
+ * @run driver EATests
+ * -XX:+UnlockDiagnosticVMOptions
+ * -Xms256m -Xmx256m
+ * -Xbootclasspath/a:.
+ * -XX:CompileCommand=dontinline,*::dontinline_*
+ * -XX:+WhiteBoxAPI
+ * -Xbatch
+ * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks
+ * -XX:LockingMode=2
*
* @comment Excercise -XX:+DeoptimizeObjectsALot. Mostly to prevent bit-rot because the option is meant to stress object deoptimization
* with non-synthetic workloads.
@@ -208,11 +249,13 @@ class EATestsTarget {
// Relocking test cases
new EARelockingSimpleTarget() .run();
+ new EARelockingSimpleWithAccessInOtherThreadTarget() .run();
new EARelockingRecursiveTarget() .run();
new EARelockingNestedInflatedTarget() .run();
new EARelockingNestedInflated_02Target() .run();
new EARelockingArgEscapeLWLockedInCalleeFrameTarget() .run();
new EARelockingArgEscapeLWLockedInCalleeFrame_2Target() .run();
+ new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget() .run();
new EAGetOwnedMonitorsTarget() .run();
new EAEntryCountTarget() .run();
new EARelockingObjectCurrentlyWaitingOnTarget() .run();
@@ -325,11 +368,13 @@ public class EATests extends TestScaffold {
// Relocking test cases
new EARelockingSimple() .run(this);
+ new EARelockingSimpleWithAccessInOtherThread() .run(this);
new EARelockingRecursive() .run(this);
new EARelockingNestedInflated() .run(this);
new EARelockingNestedInflated_02() .run(this);
new EARelockingArgEscapeLWLockedInCalleeFrame() .run(this);
new EARelockingArgEscapeLWLockedInCalleeFrame_2() .run(this);
+ new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive() .run(this);
new EAGetOwnedMonitors() .run(this);
new EAEntryCount() .run(this);
new EARelockingObjectCurrentlyWaitingOn() .run(this);
@@ -1703,6 +1748,62 @@ class EARelockingSimpleTarget extends EATestCaseBaseTarget {
/////////////////////////////////////////////////////////////////////////////
+// The debugger reads and publishes an object with eliminated locking to an instance field.
+// A 2nd thread in the debuggee finds it there and changes its state using a synchronized method.
+// Without eager relocking the accesses are unsynchronized which can be observed.
+class EARelockingSimpleWithAccessInOtherThread extends EATestCaseBaseDebugger {
+
+ public void runTestCase() throws Exception {
+ BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
+ printStack(bpe.thread());
+ String l1ClassName = EARelockingSimpleWithAccessInOtherThreadTarget.SyncCounter.class.getName();
+ ObjectReference ctr = getLocalRef(bpe.thread().frame(1), l1ClassName, "l1");
+ setField(testCase, "sharedCounter", ctr);
+ terminateEndlessLoop();
+ }
+}
+
+class EARelockingSimpleWithAccessInOtherThreadTarget extends EATestCaseBaseTarget {
+
+ public static class SyncCounter {
+ private int val;
+ public synchronized int inc() { return val++; }
+ }
+
+ public volatile SyncCounter sharedCounter;
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ doLoop = true;
+ Thread.ofPlatform().daemon().start(() -> {
+ while (doLoop) {
+ SyncCounter ctr = sharedCounter;
+ if (ctr != null) {
+ ctr.inc();
+ }
+ }
+ });
+ }
+
+ public void dontinline_testMethod() {
+ SyncCounter l1 = new SyncCounter();
+ synchronized (l1) { // Eliminated locking
+ l1.inc();
+ dontinline_brkpt(); // Debugger publishes l1 to sharedCounter.
+ iResult = l1.inc(); // Changes by the 2nd thread will be observed if l1
+ // was not relocked before passing it to the debugger.
+ }
+ }
+
+ @Override
+ public int getExpectedIResult() {
+ return 1;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
// Test recursive locking
class EARelockingRecursiveTarget extends EATestCaseBaseTarget {
@@ -1902,6 +2003,48 @@ class EARelockingArgEscapeLWLockedInCalleeFrame_2Target extends EATestCaseBaseTa
/////////////////////////////////////////////////////////////////////////////
/**
+ * Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame_2Target}. It does
+ * not use recursive locking and exposed a bug in the lightweight-locking implementation.
+ */
+class EARelockingArgEscapeLWLockedInCalleeFrameNoRecursive extends EATestCaseBaseDebugger {
+
+ public void runTestCase() throws Exception {
+ BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
+ printStack(bpe.thread());
+ @SuppressWarnings("unused")
+ ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "l1");
+ }
+}
+
+class EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget extends EATestCaseBaseTarget {
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ testMethodDepth = 2;
+ }
+
+ public void dontinline_testMethod() {
+ XYVal l1 = new XYVal(1, 1); // NoEscape, scalar replaced
+ XYVal l2 = new XYVal(4, 2); // NoEscape, scalar replaced
+ XYVal l3 = new XYVal(5, 3); // ArgEscape
+ synchronized (l1) { // eliminated
+ synchronized (l2) { // eliminated
+ l3.dontinline_sync_method(this); // l3 escapes
+ }
+ }
+ iResult = l2.x + l2.y;
+ }
+
+ @Override
+ public int getExpectedIResult() {
+ return 6;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
* Test relocking eliminated (nested) locks of an object on which the
* target thread currently waits.
*/