jdk-24/test/jdk/com/sun/jdi/EATests.java
Axel Boldt-Christmas 6fad6af0de 8341819: LightweightSynchronizer::enter_for races with deflation
Reviewed-by: pchilanomate, rkennke
2024-10-10 17:02:54 +00:00

3530 lines
130 KiB
Java

/*
* Copyright (c) 2020, 2024 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/**
* @test
* @bug 8227745
* @summary Collection of test cases that check if optimizations based on escape analysis are reverted just before non-escaping objects escape through JVMTI.
* @author Richard Reingruber richard DOT reingruber AT sap DOT com
*
* @requires ((vm.compMode == "Xmixed") & vm.compiler2.enabled)
* @library /test/lib /test/hotspot/jtreg
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run compile -g EATests.java
* @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=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=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=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=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.
* @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:+IgnoreUnrecognizedVMOptions -XX:+DeoptimizeObjectsALot
*
* @bug 8324881
* @comment Regression test for using the wrong thread when logging during re-locking from deoptimization.
*
* @comment DiagnoseSyncOnValueBasedClasses=2 will cause logging when locking on \@ValueBased objects.
* @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=1
* -XX:DiagnoseSyncOnValueBasedClasses=2
*
* @comment Re-lock may inflate monitors when re-locking, which cause monitorinflation trace logging.
* @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
* -Xlog:monitorinflation=trace:file=monitorinflation.log
*
* @comment Re-lock may race with deflation.
* @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=0
* -XX:GuaranteedAsyncDeflationInterval=1000
*
* @bug 8341819
* @comment Regression test for re-locking racing with deflation with LM_LIGHTWEIGHT.
* @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
* -XX:GuaranteedAsyncDeflationInterval=1
*/
/**
* @test
* @bug 8227745
*
* @summary This is another configuration of EATests.java to test Graal. Some testcases are expected
* to fail because Graal does not provide all information about non-escaping objects in
* scope. These are skipped.
*
* @author Richard Reingruber richard DOT reingruber AT sap DOT com
*
* @requires ((vm.compMode == "Xmixed") & vm.graal.enabled)
*
* @library /test/lib /test/hotspot/jtreg
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run compile -g EATests.java
*
* @comment Test with Graal. Some testcases are expected to fail because Graal does not provide all information about non-escaping
* objects in scope. These are skipped.
* @run driver EATests
* -XX:+UnlockDiagnosticVMOptions
* -Xms256m -Xmx256m
* -Xbootclasspath/a:.
* -XX:CompileCommand=dontinline,*::dontinline_*
* -XX:+WhiteBoxAPI
* -Xbatch
* -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import compiler.testlibrary.CompilerUtils;
import compiler.whitebox.CompilerWhiteBoxTest;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import jdk.test.lib.Asserts;
import jdk.test.whitebox.WhiteBox;
import jdk.test.whitebox.gc.GC;
//
// ANALYZING TEST FAILURES
//
// - Executing just a single test case with the property EATests.onlytestcase.
//
// Example: java -DEATests.onlytestcase=<test case name> ... EATests
//
// - Interactive execution allows for attaching a native debugger, e.g. gdb
//
// Example: java -DEATests.interactive=true ... EATests
//
// - Java arguments to the test are passed as vm options to the debuggee:
//
// Example: java ... EATests -XX:+UseNewCode
//
/////////////////////////////////////////////////////////////////////////////
//
// Shared base class for test cases for both, debugger and debuggee.
//
/////////////////////////////////////////////////////////////////////////////
class EATestCaseBaseShared {
// In interactive mode we wait for a keypress before every test case.
public static final boolean INTERACTIVE =
System.getProperty("EATests.interactive") != null &&
System.getProperty("EATests.interactive").equals("true");
// If the property is given, then just the test case it refers to is executed.
// Use it to diagnose test failures.
public static final String RUN_ONLY_TEST_CASE_PROPERTY = "EATests.onlytestcase";
public static final String RUN_ONLY_TEST_CASE = System.getProperty(RUN_ONLY_TEST_CASE_PROPERTY);
public final String testCaseName;
public EATestCaseBaseShared() {
String clName = getClass().getName();
int tidx = clName.lastIndexOf("Target");
testCaseName = tidx > 0 ? clName.substring(0, tidx) : clName;
}
public boolean shouldSkip() {
return EATestCaseBaseShared.RUN_ONLY_TEST_CASE != null &&
EATestCaseBaseShared.RUN_ONLY_TEST_CASE.length() > 0 &&
!testCaseName.equals(EATestCaseBaseShared.RUN_ONLY_TEST_CASE);
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Target main class, i.e. the program to be debugged.
//
/////////////////////////////////////////////////////////////////////////////
class EATestsTarget {
public static void main(String[] args) {
EATestCaseBaseTarget.staticSetUp();
EATestCaseBaseTarget.staticSetUpDone();
// Materializing test cases, i.e. reallocating objects on the heap
new EAMaterializeLocalVariableUponGetTarget() .run();
new EAGetWithoutMaterializeTarget() .run();
new EAMaterializeLocalAtObjectReturnTarget() .run();
new EAMaterializeLocalAtObjectPollReturnReturnTarget() .run();
new EAMaterializeIntArrayTarget() .run();
new EAMaterializeLongArrayTarget() .run();
new EAMaterializeFloatArrayTarget() .run();
new EAMaterializeDoubleArrayTarget() .run();
new EAMaterializeObjectArrayTarget() .run();
new EAMaterializeObjectWithConstantAndNotConstantValuesTarget() .run();
new EAMaterializeObjReferencedBy2LocalsTarget() .run();
new EAMaterializeObjReferencedBy2LocalsAndModifyTarget() .run();
new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesTarget() .run();
new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModifyTarget() .run();
new EAMaterializeObjReferencedFromOperandStackTarget() .run();
new EAMaterializeLocalVariableUponGetAfterSetIntegerTarget() .run();
// Relocking test cases
new EARelockingSimpleTarget() .run();
new EARelockingWithManyLightweightLocksTarget() .run();
new EARelockingSimpleWithAccessInOtherThreadTarget() .run();
new EARelockingSimpleWithAccessInOtherThread_02_DynamicCall_Target() .run();
new EARelockingRecursiveTarget() .run();
new EARelockingNestedInflatedTarget() .run();
new EARelockingNestedInflated_02Target() .run();
new EARelockingNestedInflated_03Target() .run();
new EARelockingArgEscapeLWLockedInCalleeFrameTarget() .run();
new EARelockingArgEscapeLWLockedInCalleeFrame_2Target() .run();
new EARelockingArgEscapeLWLockedInCalleeFrameNoRecursiveTarget() .run();
new EAGetOwnedMonitorsTarget() .run();
new EAEntryCountTarget() .run();
new EARelockingObjectCurrentlyWaitingOnTarget() .run();
new EARelockingValueBasedTarget() .run();
// Test cases that require deoptimization even though neither
// locks nor allocations are eliminated at the point where
// escape state is changed.
new EADeoptFrameAfterReadLocalObject_01Target() .run();
new EADeoptFrameAfterReadLocalObject_01BTarget() .run();
new EADeoptFrameAfterReadLocalObject_02Target() .run();
new EADeoptFrameAfterReadLocalObject_02BTarget() .run();
new EADeoptFrameAfterReadLocalObject_02CTarget() .run();
new EADeoptFrameAfterReadLocalObject_03Target() .run();
// PopFrame test cases
new EAPopFrameNotInlinedTarget() .run();
new EAPopFrameNotInlinedReallocFailureTarget() .run();
new EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget() .run();
// ForceEarlyReturn test cases
new EAForceEarlyReturnNotInlinedTarget() .run();
new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget() .run();
new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget().run();
// Instances of ReferenceType
new EAGetInstancesOfReferenceTypeTarget() .run();
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Debugger main class
//
/////////////////////////////////////////////////////////////////////////////
public class EATests extends TestScaffold {
public TargetVMOptions targetVMOptions;
public ThreadReference targetMainThread;
EATests(String args[]) {
super(args);
}
public static void main(String[] args) throws Exception {
if (EATestCaseBaseShared.RUN_ONLY_TEST_CASE != null) {
args = Arrays.copyOf(args, args.length + 1);
args[args.length - 1] = "-D" + EATestCaseBaseShared.RUN_ONLY_TEST_CASE_PROPERTY + "=" + EATestCaseBaseShared.RUN_ONLY_TEST_CASE;
}
new EATests(args).startTests();
}
public static class TargetVMOptions {
public final boolean UseJVMCICompiler;
public final boolean EliminateAllocations;
public final boolean DeoptimizeObjectsALot;
public final boolean DoEscapeAnalysis;
public final boolean ZGCIsSelected;
public final boolean ShenandoahGCIsSelected;
public final boolean StressReflectiveCode;
public TargetVMOptions(EATests env, ClassType testCaseBaseTargetClass) {
Value val;
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("DoEscapeAnalysis"));
DoEscapeAnalysis = ((PrimitiveValue) val).booleanValue();
// Escape analysis is a prerequisite for scalar replacement (EliminateAllocations)
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("EliminateAllocations"));
EliminateAllocations = DoEscapeAnalysis && ((PrimitiveValue) val).booleanValue();
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("DeoptimizeObjectsALot"));
DeoptimizeObjectsALot = ((PrimitiveValue) val).booleanValue();
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("UseJVMCICompiler"));
UseJVMCICompiler = ((PrimitiveValue) val).booleanValue();
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("ZGCIsSelected"));
ZGCIsSelected = ((PrimitiveValue) val).booleanValue();
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("ShenandoahGCIsSelected"));
ShenandoahGCIsSelected = ((PrimitiveValue) val).booleanValue();
val = testCaseBaseTargetClass.getValue(testCaseBaseTargetClass.fieldByName("StressReflectiveCode"));
StressReflectiveCode = ((PrimitiveValue) val).booleanValue();
}
}
// Execute known test cases
protected void runTests() throws Exception {
String targetProgName = EATestsTarget.class.getName();
msg("starting to main method in class " + targetProgName);
startToMain(targetProgName);
msg("resuming to EATestCaseBaseTarget.staticSetUpDone()V");
targetMainThread = resumeTo("EATestCaseBaseTarget", "staticSetUpDone", "()V").thread();
Location loc = targetMainThread.frame(0).location();
Asserts.assertEQ("staticSetUpDone", loc.method().name());
targetVMOptions = new TargetVMOptions(this, (ClassType) loc.declaringType());
// Materializing test cases, i.e. reallocating objects on the heap
new EAMaterializeLocalVariableUponGet() .run(this);
new EAGetWithoutMaterialize() .run(this);
new EAMaterializeLocalAtObjectReturn() .run(this);
new EAMaterializeLocalAtObjectPollReturnReturn() .run(this);
new EAMaterializeIntArray() .run(this);
new EAMaterializeLongArray() .run(this);
new EAMaterializeFloatArray() .run(this);
new EAMaterializeDoubleArray() .run(this);
new EAMaterializeObjectArray() .run(this);
new EAMaterializeObjectWithConstantAndNotConstantValues() .run(this);
new EAMaterializeObjReferencedBy2Locals() .run(this);
new EAMaterializeObjReferencedBy2LocalsAndModify() .run(this);
new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFrames() .run(this);
new EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModify() .run(this);
new EAMaterializeObjReferencedFromOperandStack() .run(this);
new EAMaterializeLocalVariableUponGetAfterSetInteger() .run(this);
// Relocking test cases
new EARelockingSimple() .run(this);
new EARelockingWithManyLightweightLocks() .run(this);
new EARelockingSimpleWithAccessInOtherThread() .run(this);
new EARelockingSimpleWithAccessInOtherThread_02_DynamicCall() .run(this);
new EARelockingRecursive() .run(this);
new EARelockingNestedInflated() .run(this);
new EARelockingNestedInflated_02() .run(this);
new EARelockingNestedInflated_03() .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);
new EARelockingValueBased() .run(this);
// Test cases that require deoptimization even though neither
// locks nor allocations are eliminated at the point where
// escape state is changed.
new EADeoptFrameAfterReadLocalObject_01() .run(this);
new EADeoptFrameAfterReadLocalObject_01B() .run(this);
new EADeoptFrameAfterReadLocalObject_02() .run(this);
new EADeoptFrameAfterReadLocalObject_02B() .run(this);
new EADeoptFrameAfterReadLocalObject_02C() .run(this);
new EADeoptFrameAfterReadLocalObject_03() .run(this);
// PopFrame test cases
new EAPopFrameNotInlined() .run(this);
new EAPopFrameNotInlinedReallocFailure() .run(this);
new EAPopInlinedMethodWithScalarReplacedObjectsReallocFailure() .run(this);
// ForceEarlyReturn test cases
new EAForceEarlyReturnNotInlined() .run(this);
new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjects() .run(this);
new EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailure().run(this);
// Instances of ReferenceType
new EAGetInstancesOfReferenceType() .run(this);
// resume the target listening for events
listenUntilVMDisconnect();
}
// Print a Message
public void msg(String m) {
System.out.println();
System.out.println("###(Debugger) " + m);
System.out.println();
}
// Highlighted message.
public void msgHL(String m) {
System.out.println();
System.out.println();
System.out.println("##########################################################");
System.out.println("### " + m);
System.out.println("### ");
System.out.println();
System.out.println();
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Base class for debugger side of test cases.
//
/////////////////////////////////////////////////////////////////////////////
abstract class EATestCaseBaseDebugger extends EATestCaseBaseShared {
protected EATests env;
public ObjectReference testCase;
public static final String TARGET_TESTCASE_BASE_NAME = EATestCaseBaseTarget.class.getName();
public static final String XYVAL_NAME = XYVal.class.getName();
public abstract void runTestCase() throws Exception;
@Override
public boolean shouldSkip() {
// Skip if StressReflectiveCode because it effectively disables escape analysis
return super.shouldSkip() || env.targetVMOptions.StressReflectiveCode;
}
public void run(EATests env) {
this.env = env;
if (shouldSkip()) {
msg("skipping " + testCaseName);
return;
}
try {
msgHL("Executing test case " + getClass().getName());
env.testFailed = false;
if (INTERACTIVE)
env.waitForInput();
resumeToWarmupDone();
runTestCase();
Asserts.assertTrue(env.targetMainThread.isSuspended(), "must be suspended after the testcase");
resumeToTestCaseDone();
checkPostConditions();
} catch (Exception e) {
Asserts.fail("Unexpected exception in test case " + getClass().getName(), e);
}
}
/**
* Set a breakpoint in the given method and resume all threads. The
* breakpoint is configured to suspend just the thread that reaches it
* instead of all threads. This is important when running with graal.
*/
public BreakpointEvent resumeTo(String clsName, String methodName, String signature) {
boolean suspendThreadOnly = true;
return env.resumeTo(clsName, methodName, signature, suspendThreadOnly);
}
public void resumeToWarmupDone() throws Exception {
msg("resuming to " + TARGET_TESTCASE_BASE_NAME + ".warmupDone()V");
resumeTo(TARGET_TESTCASE_BASE_NAME, "warmupDone", "()V");
testCase = env.targetMainThread.frame(0).thisObject();
}
public void resumeToTestCaseDone() {
msg("resuming to " + TARGET_TESTCASE_BASE_NAME + ".testCaseDone()V");
resumeTo(TARGET_TESTCASE_BASE_NAME, "testCaseDone", "()V");
}
public void checkPostConditions() throws Exception {
Asserts.assertFalse(env.getExceptionCaught(), "Uncaught exception in Debuggee");
String testName = getClass().getName();
if (!env.testFailed) {
env.println(testName + ": passed");
} else {
throw new Exception(testName + ": failed");
}
}
public void printStack(ThreadReference thread) throws Exception {
msg("Debuggee Stack:");
List<StackFrame> stack_frames = thread.frames();
int i = 0;
for (StackFrame ff : stack_frames) {
System.out.println("frame[" + i++ +"]: " + ff.location().method() + " (bci:" + ff.location().codeIndex() + ")");
}
}
public void msg(String m) {
env.msg(m);
}
public void msgHL(String m) {
env.msgHL(m);
}
// See Field Descriptors in The Java Virtual Machine Specification
// (https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2)
enum FD {
I, // int
J, // long
F, // float
D, // double
}
// Map field descriptor to jdi type string
public static final Map<FD, String> FD2JDIArrType = Map.of(FD.I, "int[]", FD.J, "long[]", FD.F, "float[]", FD.D, "double[]");
// Map field descriptor to PrimitiveValue getter
public static final Function<PrimitiveValue, Integer> v2I = PrimitiveValue::intValue;
public static final Function<PrimitiveValue, Long> v2J = PrimitiveValue::longValue;
public static final Function<PrimitiveValue, Float> v2F = PrimitiveValue::floatValue;
public static final Function<PrimitiveValue, Double> v2D = PrimitiveValue::doubleValue;
Map<FD, Function<PrimitiveValue, ?>> FD2getter = Map.of(FD.I, v2I, FD.J, v2J, FD.F, v2F, FD.D, v2D);
/**
* Retrieve array of primitive values referenced by a local variable in target and compare with
* an array of expected values.
* @param frame Frame in the target holding the local variable
* @param lName Name of the local variable referencing the array to be retrieved
* @param desc Array element type given as field descriptor.
* @param expVals Array of expected values.
* @throws Exception
*/
protected void checkLocalPrimitiveArray(StackFrame frame, String lName, FD desc, Object expVals) throws Exception {
String lType = FD2JDIArrType.get(desc);
Asserts.assertNotNull(lType, "jdi type not found");
Asserts.assertEQ(EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, frame .location().method().name());
List<LocalVariable> localVars = frame.visibleVariables();
msg("Check if the local array variable '" + lName + "' in " + EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME + " has the expected elements: ");
boolean found = false;
for (LocalVariable lv : localVars) {
if (lv.name().equals(lName)) {
found = true;
Value lVal = frame.getValue(lv);
Asserts.assertNotNull(lVal);
Asserts.assertEQ(lVal.type().name(), lType);
ArrayReference aRef = (ArrayReference) lVal;
Asserts.assertEQ(3, aRef.length());
// now check the elements
for (int i = 0; i < aRef.length(); i++) {
Object actVal = FD2getter.get(desc).apply((PrimitiveValue)aRef.getValue(i));
Object expVal = Array.get(expVals, i);
Asserts.assertEQ(expVal, actVal, "checking element at index " + i);
}
}
}
Asserts.assertTrue(found);
msg("OK.");
}
/**
* Retrieve array of objects referenced by a local variable in target and compare with an array
* of expected values.
* @param frame Frame in the target holding the local variable
* @param lName Name of the local variable referencing the array to be retrieved
* @param lType Local type, e.g. java.lang.Long[]
* @param expVals Array of expected values.
* @throws Exception
*/
protected void checkLocalObjectArray(StackFrame frame, String lName, String lType, ObjectReference[] expVals) throws Exception {
Asserts.assertEQ(EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, frame .location().method().name());
List<LocalVariable> localVars = frame.visibleVariables();
msg("Check if the local array variable '" + lName + "' in " + EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME + " has the expected elements: ");
boolean found = false;
for (LocalVariable lv : localVars) {
if (lv.name().equals(lName)) {
found = true;
Value lVal = frame.getValue(lv);
Asserts.assertNotNull(lVal);
Asserts.assertEQ(lType, lVal.type().name());
ArrayReference aRef = (ArrayReference) lVal;
Asserts.assertEQ(3, aRef.length());
// now check the elements
for (int i = 0; i < aRef.length(); i++) {
ObjectReference actVal = (ObjectReference)aRef.getValue(i);
Asserts.assertSame(expVals[i], actVal, "checking element at index " + i);
}
}
}
Asserts.assertTrue(found);
msg("OK.");
}
/**
* Retrieve a reference held by a local variable in the given frame. Check if the frame's method
* is the expected method if the retrieved local value has the expected type and is not null.
* @param frame The frame to retrieve the local variable value from.
* @param expectedMethodName The name of the frames method should match the expectedMethodName.
* @param lName The name of the local variable which is read.
* @param expectedType Is the expected type of the object referenced by the local variable.
* @return
* @throws Exception
*/
protected ObjectReference getLocalRef(StackFrame frame, String expectedMethodName, String lName, String expectedType) throws Exception {
Asserts.assertEQ(expectedMethodName, frame.location().method().name());
List<LocalVariable> localVars = frame.visibleVariables();
msg("Get and check local variable '" + lName + "' in " + expectedMethodName);
ObjectReference lRef = null;
for (LocalVariable lv : localVars) {
if (lv.name().equals(lName)) {
Value lVal = frame.getValue(lv);
Asserts.assertNotNull(lVal);
Asserts.assertEQ(expectedType, lVal.type().name());
lRef = (ObjectReference) lVal;
break;
}
}
Asserts.assertNotNull(lRef, "Local variable '" + lName + "' not found");
msg("OK.");
return lRef;
}
/**
* Retrieve a reference held by a local variable in the given frame. Check if the frame's method
* matches {@link EATestCaseBaseTarget#TESTMETHOD_DEFAULT_NAME} if the retrieved local value has
* the expected type and is not null.
* @param frame The frame to retrieve the local variable value from.
* @param expectedMethodName The name of the frames method should match the expectedMethodName.
* @param lName The name of the local variable which is read.
* @param expectedType Is the expected type of the object referenced by the local variable.
* @return
* @throws Exception
*/
protected ObjectReference getLocalRef(StackFrame frame, String lType, String lName) throws Exception {
return getLocalRef(frame, EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, lName, lType);
}
/**
* Set the value of a local variable in the given frame. Check if the frame's method is the expected method.
* @param frame The frame holding the local variable.
* @param expectedMethodName The expected name of the frame's method.
* @param lName The name of the local variable to change.
* @param val The new value of the local variable.
* @throws Exception
*/
public void setLocal(StackFrame frame, String expectedMethodName, String lName, Value val) throws Exception {
Asserts.assertEQ(expectedMethodName, frame.location().method().name());
List<LocalVariable> localVars = frame.visibleVariables();
msg("Set local variable '" + lName + "' = " + val + " in " + expectedMethodName);
for (LocalVariable lv : localVars) {
if (lv.name().equals(lName)) {
frame.setValue(lv, val);
break;
}
}
msg("OK.");
}
/**
* Set the value of a local variable in the given frame. Check if the frame's method matches
* {@link EATestCaseBaseTarget#TESTMETHOD_DEFAULT_NAME}.
* @param frame The frame holding the local variable.
* @param expectedMethodName The expected name of the frame's method.
* @param lName The name of the local variable to change.
* @param val The new value of the local variable.
* @throws Exception
*/
public void setLocal(StackFrame frame, String lName, Value val) throws Exception {
setLocal(frame, EATestCaseBaseTarget.TESTMETHOD_DEFAULT_NAME, lName, val);
}
/**
* Check if a field has the expected primitive value.
* @param o Object holding the field.
* @param desc Field descriptor.
* @param fName Field name
* @param expVal Expected primitive value
* @throws Exception
*/
protected void checkPrimitiveField(ObjectReference o, FD desc, String fName, Object expVal) throws Exception {
msg("check field " + fName);
ReferenceType rt = o.referenceType();
Field fld = rt.fieldByName(fName);
Value val = o.getValue(fld);
Object actVal = FD2getter.get(desc).apply((PrimitiveValue) val);
Asserts.assertEQ(expVal, actVal, "field '" + fName + "' has unexpected value.");
msg("ok");
}
/**
* Check if a field references the expected object.
* @param obj Object holding the field.
* @param fName Field name
* @param expVal Object expected to be referenced by the field
* @throws Exception
*/
protected void checkObjField(ObjectReference obj, String fName, ObjectReference expVal) throws Exception {
msg("check field " + fName);
ReferenceType rt = obj.referenceType();
Field fld = rt.fieldByName(fName);
Value actVal = obj.getValue(fld);
Asserts.assertEQ(expVal, actVal, "field '" + fName + "' has unexpected value.");
msg("ok");
}
protected void setField(ObjectReference obj, String fName, Value val) throws Exception {
msg("set field " + fName + " = " + val);
ReferenceType rt = obj.referenceType();
Field fld = rt.fieldByName(fName);
obj.setValue(fld, val);
msg("ok");
}
protected Value getField(ObjectReference obj, String fName) throws Exception {
msg("get field " + fName);
ReferenceType rt = obj.referenceType();
Field fld = rt.fieldByName(fName);
Value val = obj.getValue(fld);
msg("result : " + val);
return val;
}
/**
* Free the memory consumed in the target by {@link EATestCaseBaseTarget#consumedMemory}
* @throws Exception
*/
public void freeAllMemory() throws Exception {
msg("free consumed memory");
setField(testCase, "consumedMemory", null);
}
/**
* @return The value of {@link EATestCaseBaseTarget#targetIsInLoop}. The target must set that field to true as soon as it
* enters the endless loop.
* @throws Exception
*/
public boolean targetHasEnteredEndlessLoop() throws Exception {
Value v = getField(testCase, "targetIsInLoop");
return ((PrimitiveValue) v).booleanValue();
}
/**
* Poll {@link EATestCaseBaseTarget#targetIsInLoop} and return if it is found to be true.
* @throws Exception
*/
public void waitUntilTargetHasEnteredEndlessLoop() throws Exception {
while(!targetHasEnteredEndlessLoop()) {
msg("Target has not yet entered the loop. Sleep 200ms.");
try { Thread.sleep(200); } catch (InterruptedException e) { /*ignore */ }
}
}
/**
* Set {@link EATestCaseBaseTarget#doLoop} to <code>false</code>. This will allow the target to
* leave the endless loop.
* @throws Exception
*/
public void terminateEndlessLoop() throws Exception {
msg("terminate loop");
setField(testCase, "doLoop", env.vm().mirrorOf(false));
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Base class for debuggee side of test cases.
//
/////////////////////////////////////////////////////////////////////////////
abstract class EATestCaseBaseTarget extends EATestCaseBaseShared implements Runnable {
/**
* The target must set that field to true as soon as it enters the endless loop.
*/
public volatile boolean targetIsInLoop;
/**
* Used for busy loops. See {@link #dontinline_endlessLoop()}.
*/
public volatile long loopCount;
/**
* Used in {@link EATestCaseBaseDebugger#terminateEndlessLoop()} to signal target to leave the endless loop.
*/
public volatile boolean doLoop;
public long checkSum;
public static final String TESTMETHOD_DEFAULT_NAME = "dontinline_testMethod";
public static final WhiteBox WB = WhiteBox.getWhiteBox();
public static boolean unbox(Boolean value, boolean dflt) {
return value == null ? dflt : value;
}
// Some of the fields are only read by the debugger
public static final boolean UseJVMCICompiler = unbox(WB.getBooleanVMFlag("UseJVMCICompiler"), false);
public static final boolean DoEscapeAnalysis = unbox(WB.getBooleanVMFlag("DoEscapeAnalysis"), UseJVMCICompiler);
public static final boolean EliminateAllocations = unbox(WB.getBooleanVMFlag("EliminateAllocations"), UseJVMCICompiler);
public static final boolean DeoptimizeObjectsALot = WB.getBooleanVMFlag("DeoptimizeObjectsALot");
public static final boolean ZGCIsSelected = GC.Z.isSelected();
public static final boolean ShenandoahGCIsSelected = GC.Shenandoah.isSelected();
public static final boolean StressReflectiveCode = unbox(WB.getBooleanVMFlag("StressReflectiveCode"), false);
public String testMethodName;
public int testMethodDepth;
// Results produced by dontinline_testMethod()
public int iResult;
public long lResult;
public float fResult;
public double dResult;
public boolean warmupDone;
// an object with an inflated monitor
public static XYVal inflatedLock;
public static Thread inflatorThread;
public static boolean inflatedLockIsPermanentlyInflated;
public static int NOT_CONST_1I = 1;
public static long NOT_CONST_1L = 1L;
public static float NOT_CONST_1F = 1.1F;
public static double NOT_CONST_1D = 1.1D;
public static Long NOT_CONST_1_OBJ = Long.valueOf(1);
public static final Long CONST_2_OBJ = Long.valueOf(2);
public static final Long CONST_3_OBJ = Long.valueOf(3);
@Override
public boolean shouldSkip() {
// Skip if StressReflectiveCode because it effectively disables escape analysis
return super.shouldSkip() || StressReflectiveCode;
}
/**
* Main driver of a test case.
* <ul>
* <li> Skips test case if not selected (see {@link EATestCaseBaseShared#RUN_ONLY_TEST_CASE}
* <li> Call {@link #setUp()}
* <li> warm-up and compile {@link #dontinline_testMethod()} (see {@link #compileTestMethod()}
* <li> calling {@link #dontinline_testMethod()}
* <li> checking the result (see {@link #checkResult()}
* <ul>
*/
public void run() {
try {
if (shouldSkip()) {
msg("skipping " + testCaseName);
return;
}
setUp();
msg(testCaseName + " is up and running.");
compileTestMethod();
msg(testCaseName + " warmup done.");
warmupDone();
checkCompLevel();
dontinline_testMethod();
checkResult();
msg(testCaseName + " done.");
testCaseDone();
} catch (Exception e) {
Asserts.fail("Caught unexpected exception", e);
}
}
public static void staticSetUp() {
inflatedLock = new XYVal(1, 1);
synchronized (inflatedLock) {
inflatorThread = DebuggeeWrapper.newThread(() -> {
synchronized (inflatedLock) {
inflatedLockIsPermanentlyInflated = true;
inflatedLock.notify(); // main thread
while (true) {
try {
// calling wait() on a monitor will cause inflation into a heavy monitor
inflatedLock.wait();
} catch (InterruptedException e) { /* ignored */ }
}
}
}, "Lock Inflator (test thread)");
inflatorThread.setDaemon(true);
inflatorThread.start();
// wait until the lock is permanently inflated by the inflatorThread
while(!inflatedLockIsPermanentlyInflated) {
try {
inflatedLock.wait(); // until inflated
} catch (InterruptedException e1) { /* ignored */ }
}
}
}
// Debugger will set breakpoint here to sync with target.
public static void staticSetUpDone() {
}
public void setUp() {
testMethodDepth = 1;
testMethodName = TESTMETHOD_DEFAULT_NAME;
}
public abstract void dontinline_testMethod() throws Exception;
public int dontinline_brkpt_iret() {
dontinline_brkpt();
return 42;
}
/**
* It is a common protocol to have the debugger set a breakpoint in this method and have {@link
* #dontinline_testMethod()} call it and then perform some test actions on debugger side.
* After that it is checked if a frame of {@link #dontinline_testMethod()} is found at the
* expected depth on stack and if it is (not) marked for deoptimization as expected.
*/
public void dontinline_brkpt() {
// will set breakpoint here after warmup
if (warmupDone) {
// check if test method is at expected depth
StackTraceElement[] frames = Thread.currentThread().getStackTrace();
int stackTraceDepth = testMethodDepth + 1; // ignore java.lang.Thread.getStackTrace()
Asserts.assertEQ(testMethodName, frames[stackTraceDepth].getMethodName(),
testCaseName + ": test method not found at depth " + testMethodDepth);
// check if the frame is (not) deoptimized as expected
if (!DeoptimizeObjectsALot) {
if (testFrameShouldBeDeoptimized()) {
Asserts.assertTrue(WB.isFrameDeoptimized(testMethodDepth+1),
testCaseName + ": expected test method frame at depth " + testMethodDepth + " to be deoptimized");
} else {
Asserts.assertFalse(WB.isFrameDeoptimized(testMethodDepth+1),
testCaseName + ": expected test method frame at depth " + testMethodDepth + " not to be deoptimized");
}
}
}
}
/**
* Some test cases run busy endless loops by initializing {@link #loopCount}
* to {@link Long#MAX_VALUE} after warm-up and then counting down to 0 in their main test method.
* During warm-up {@link #loopCount} is initialized to a small value.
*/
public long dontinline_endlessLoop() {
long cs = checkSum;
doLoop = true;
while (loopCount-- > 0 && doLoop) {
targetIsInLoop = true;
checkSum += checkSum % ++cs;
}
loopCount = 3;
targetIsInLoop = false;
return checkSum;
}
public boolean testFrameShouldBeDeoptimized() {
return DoEscapeAnalysis;
}
public void warmupDone() {
warmupDone = true;
}
// Debugger will set breakpoint here to sync with target.
public void testCaseDone() {
}
public void compileTestMethod() throws Exception {
int callCount = CompilerWhiteBoxTest.THRESHOLD;
while (callCount-- > 0) {
dontinline_testMethod();
}
}
public void checkCompLevel() {
java.lang.reflect.Method m = null;
try {
m = getClass().getMethod(TESTMETHOD_DEFAULT_NAME);
} catch (NoSuchMethodException | SecurityException e) {
Asserts.fail("could not check compilation level of", e);
}
int highestLevel = CompilerUtils.getMaxCompilationLevel();
int compLevel = WB.getMethodCompilationLevel(m);
if (!UseJVMCICompiler) {
Asserts.assertEQ(highestLevel, compLevel,
m + " not on expected compilation level");
} else {
// Background compilation (-Xbatch) will block a thread with timeout
// (see CompileBroker::wait_for_jvmci_completion()). Therefore it is
// possible to reach here before the main test method is compiled.
// In that case we wait for it to be compiled.
while (compLevel != highestLevel) {
msg(TESTMETHOD_DEFAULT_NAME + " is compiled on level " + compLevel +
". Wait until highes level (" + highestLevel + ") is reached.");
try {
Thread.sleep(200);
} catch (InterruptedException e) { /* ignored */ }
compLevel = WB.getMethodCompilationLevel(m);
}
}
}
// to be overridden as appropriate
public int getExpectedIResult() {
return 0;
}
// to be overridden as appropriate
public long getExpectedLResult() {
return 0;
}
// to be overridden as appropriate
public float getExpectedFResult() {
return 0f;
}
// to be overridden as appropriate
public double getExpectedDResult() {
return 0d;
}
private void checkResult() {
Asserts.assertEQ(getExpectedIResult(), iResult, "checking iResult");
Asserts.assertEQ(getExpectedLResult(), lResult, "checking lResult");
Asserts.assertEQ(getExpectedFResult(), fResult, "checking fResult");
Asserts.assertEQ(getExpectedDResult(), dResult, "checking dResult");
}
public void msg(String m) {
System.out.println();
System.out.println("###(Target) " + m);
System.out.println();
}
// The object passed will be ArgEscape if it was NoEscape before.
public final void dontinline_make_arg_escape(XYVal xy) {
}
/**
* Call a method indirectly using reflection. The indirection is a limit for escape
* analysis in the sense that the VM need not search beyond for frames that might have
* an object being read by an JVMTI agent as ArgEscape.
* @param receiver The receiver object of the call.
* @param methodName The name of the method to be called.
*/
public final void dontinline_call_with_entry_frame(Object receiver, String methodName) {
Asserts.assertTrue(warmupDone, "We want to take the slow path through jni, so don't call in warmup");
Class<?> cls = receiver.getClass();
Class<?>[] none = {};
java.lang.reflect.Method m;
try {
m = cls.getDeclaredMethod(methodName, none);
m.invoke(receiver);
} catch (Exception e) {
Asserts.fail("Call through reflection failed", e);
}
}
static class LinkedList {
LinkedList l;
public long[] array;
public LinkedList(LinkedList l, int size) {
this.array = size > 0 ? new long[size] : null;
this.l = l;
}
}
public LinkedList consumedMemory;
public void consumeAllMemory() {
msg("consume all memory");
int size = 128 * 1024 * 1024;
while(true) {
try {
while(true) {
consumedMemory = new LinkedList(consumedMemory, size);
}
} catch(OutOfMemoryError oom) {
if (size == 0) break;
}
size = size / 2;
}
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Test Cases
//
/////////////////////////////////////////////////////////////////////////////
// make sure a compiled frame is not deoptimized if an escaping local is accessed
class EAGetWithoutMaterializeTarget extends EATestCaseBaseTarget {
public XYVal getAway;
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
getAway = xy; // allocated object escapes
dontinline_brkpt();
iResult = xy.x + xy.y;
}
@Override
public int getExpectedIResult() {
return 4 + 2;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
return false;
}
}
class EAGetWithoutMaterialize extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
checkPrimitiveField(o, FD.I, "x", 4);
checkPrimitiveField(o, FD.I, "y", 2);
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Tests the following:
//
// 1. Debugger can obtain a reference to a scalar replaced object R from java thread J.
// See runTestCase.
//
// 2. Subsequent modifications of R by J are noticed by the debugger.
// See checkPostConditions.
//
class EAMaterializeLocalVariableUponGet extends EATestCaseBaseDebugger {
private ObjectReference o;
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
// check 1.
o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
// o is referenced in checkPostConditions() and must not be gc'ed.
o.disableCollection();
checkPrimitiveField(o, FD.I, "x", 4);
checkPrimitiveField(o, FD.I, "y", 2);
}
@Override
public void checkPostConditions() throws Exception {
super.checkPostConditions();
// check 2.
checkPrimitiveField(o, FD.I, "x", 5);
}
}
class EAMaterializeLocalVariableUponGetTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
dontinline_brkpt(); // Debugger obtains scalar replaced object at this point.
xy.x += 1; // Change scalar replaced object after debugger obtained a reference to it.
iResult = xy.x + xy.y;
}
@Override
public int getExpectedIResult() {
return 4 + 2 + 1;
}
}
/////////////////////////////////////////////////////////////////////////////
// Test if an eliminated object can be reallocated in a frame with an active
// call that will return another object
class EAMaterializeLocalAtObjectReturn extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference o = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy");
checkPrimitiveField(o, FD.I, "x", 4);
checkPrimitiveField(o, FD.I, "y", 2);
}
}
class EAMaterializeLocalAtObjectReturnTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
Integer io = // Read xy here triggers reallocation
dontinline_brkpt_return_Integer();
iResult = xy.x + xy.y + io;
}
public Integer dontinline_brkpt_return_Integer() {
// We can't break directly in this method, as this results in making
// the test method not entrant caused by an existing dependency
dontinline_brkpt();
return Integer.valueOf(23);
}
@Override
public int getExpectedIResult() {
return 4 + 2 + 23;
}
}
/////////////////////////////////////////////////////////////////////////////
// Test if an eliminated object can be reallocated *just* before a call returns an object.
// (See nmethod::is_at_poll_return())
// Details: the callee method has just one safepoint poll at the return. The other safepoint
// is at the end of an iteration of the endless loop. We can detect if we suspended the target
// there because the local xy is out of scope there.
class EAMaterializeLocalAtObjectPollReturnReturn extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
msg("Resume " + env.targetMainThread);
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
ObjectReference o = null;
int retryCount = 0;
do {
env.targetMainThread.suspend();
printStack(env.targetMainThread);
try {
o = getLocalRef(env.targetMainThread.frame(0), XYVAL_NAME, "xy");
} catch (Exception e) {
++retryCount;
msg("The local variable xy is out of scope because we suspended at the wrong bci. Resume and try again! (" + retryCount + ")");
env.targetMainThread.resume();
if ((retryCount % 10) == 0) {
Thread.sleep(200);
}
}
} while (o == null);
checkPrimitiveField(o, FD.I, "x", 4);
checkPrimitiveField(o, FD.I, "y", 2);
terminateEndlessLoop();
}
}
class EAMaterializeLocalAtObjectPollReturnReturnTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
loopCount = 3;
doLoop = true;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
public void dontinline_testMethod() {
long result = 0;
while (doLoop && loopCount-- > 0) {
targetIsInLoop = true;
XYVal xy = new XYVal(4, 2);
Integer io = // Read xy here triggers reallocation just before the call returns
dontinline_brkpt_return_Integer();
result += xy.x + xy.y + io;
} // Here is a second safepoint. We were suspended here if xy is not in scope.
targetIsInLoop = false;
lResult = result;
}
public Integer dontinline_brkpt_return_Integer() {
return Integer.valueOf(23);
}
@Override
public long getExpectedLResult() {
return (Long.MAX_VALUE - loopCount) * (4+2+23);
}
}
/////////////////////////////////////////////////////////////////////////////
// Test case collection that tests rematerialization of different
// array types where the first element is always not constant and the
// other elements are constants. Not constant values are stored in
// the stack frame for rematerialization whereas constants are kept
// in the debug info of the nmethod.
class EAMaterializeIntArrayTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
int nums[] = {NOT_CONST_1I , 2, 3};
dontinline_brkpt();
iResult = nums[0] + nums[1] + nums[2];
}
@Override
public int getExpectedIResult() {
return NOT_CONST_1I + 2 + 3;
}
}
class EAMaterializeIntArray extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
int[] expectedVals = {1, 2, 3};
checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.I, expectedVals);
}
}
/////////////////////////////////////////////////////////////////////////////
class EAMaterializeLongArrayTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
long nums[] = {NOT_CONST_1L , 2, 3};
dontinline_brkpt();
lResult = nums[0] + nums[1] + nums[2];
}
@Override
public long getExpectedLResult() {
return NOT_CONST_1L + 2 + 3;
}
}
class EAMaterializeLongArray extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
long[] expectedVals = {1, 2, 3};
checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.J, expectedVals);
}
}
/////////////////////////////////////////////////////////////////////////////
class EAMaterializeFloatArrayTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
float nums[] = {NOT_CONST_1F , 2.2f, 3.3f};
dontinline_brkpt();
fResult = nums[0] + nums[1] + nums[2];
}
@Override
public float getExpectedFResult() {
return NOT_CONST_1F + 2.2f + 3.3f;
}
}
class EAMaterializeFloatArray extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
float[] expectedVals = {1.1f, 2.2f, 3.3f};
checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.F, expectedVals);
}
}
/////////////////////////////////////////////////////////////////////////////
class EAMaterializeDoubleArrayTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
double nums[] = {NOT_CONST_1D , 2.2d, 3.3d};
dontinline_brkpt();
dResult = nums[0] + nums[1] + nums[2];
}
@Override
public double getExpectedDResult() {
return NOT_CONST_1D + 2.2d + 3.3d;
}
}
class EAMaterializeDoubleArray extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
double[] expectedVals = {1.1d, 2.2d, 3.3d};
checkLocalPrimitiveArray(bpe.thread().frame(1), "nums", FD.D, expectedVals);
}
}
/////////////////////////////////////////////////////////////////////////////
class EAMaterializeObjectArrayTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
Long nums[] = {NOT_CONST_1_OBJ , CONST_2_OBJ, CONST_3_OBJ};
dontinline_brkpt();
lResult = nums[0] + nums[1] + nums[2];
}
@Override
public long getExpectedLResult() {
return 1 + 2 + 3;
}
}
class EAMaterializeObjectArray extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ReferenceType clazz = bpe.thread().frame(0).location().declaringType();
ObjectReference[] expectedVals = {
(ObjectReference) clazz.getValue(clazz.fieldByName("NOT_CONST_1_OBJ")),
(ObjectReference) clazz.getValue(clazz.fieldByName("CONST_2_OBJ")),
(ObjectReference) clazz.getValue(clazz.fieldByName("CONST_3_OBJ"))
};
checkLocalObjectArray(bpe.thread().frame(1), "nums", "java.lang.Long[]", expectedVals);
}
}
/////////////////////////////////////////////////////////////////////////////
// Materialize an object whose fields have constant and not constant values at
// the point where the object is materialized.
class EAMaterializeObjectWithConstantAndNotConstantValuesTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
ILFDO o = new ILFDO(NOT_CONST_1I, 2,
NOT_CONST_1L, 2L,
NOT_CONST_1F, 2.1F,
NOT_CONST_1D, 2.1D,
NOT_CONST_1_OBJ, CONST_2_OBJ
);
dontinline_brkpt();
dResult =
o.i + o.i2 + o.l + o.l2 + o.f + o.f2 + o.d + o.d2 + o.o + o.o2;
}
@Override
public double getExpectedDResult() {
return NOT_CONST_1I + 2 + NOT_CONST_1L + 2L + NOT_CONST_1F + 2.1F + NOT_CONST_1D + 2.1D + NOT_CONST_1_OBJ + CONST_2_OBJ;
}
}
class EAMaterializeObjectWithConstantAndNotConstantValues extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference o = getLocalRef(bpe.thread().frame(1), "ILFDO", "o");
checkPrimitiveField(o, FD.I, "i", 1);
checkPrimitiveField(o, FD.I, "i2", 2);
checkPrimitiveField(o, FD.J, "l", 1L);
checkPrimitiveField(o, FD.J, "l2", 2L);
checkPrimitiveField(o, FD.F, "f", 1.1f);
checkPrimitiveField(o, FD.F, "f2", 2.1f);
checkPrimitiveField(o, FD.D, "d", 1.1d);
checkPrimitiveField(o, FD.D, "d2", 2.1d);
ReferenceType clazz = bpe.thread().frame(1).location().declaringType();
ObjectReference[] expVals = {
(ObjectReference) clazz.getValue(clazz.fieldByName("NOT_CONST_1_OBJ")),
(ObjectReference) clazz.getValue(clazz.fieldByName("CONST_2_OBJ")),
};
checkObjField(o, "o", expVals[0]);
checkObjField(o, "o2", expVals[1]);
}
}
/////////////////////////////////////////////////////////////////////////////
// Two local variables reference the same object.
// Check if the debugger obtains the same object when reading the two variables
class EAMaterializeObjReferencedBy2LocalsTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(2, 3);
XYVal alias = xy;
dontinline_brkpt();
iResult = xy.x + alias.x;
}
@Override
public int getExpectedIResult() {
return 2 + 2;
}
}
class EAMaterializeObjReferencedBy2Locals extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
ObjectReference alias = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "alias");
Asserts.assertSame(xy, alias, "xy and alias are expected to reference the same object");
}
}
/////////////////////////////////////////////////////////////////////////////
// Two local variables reference the same object.
// Check if it has the expected effect in the target if the debugger modifies the object.
class EAMaterializeObjReferencedBy2LocalsAndModifyTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(2, 3);
XYVal alias = xy;
dontinline_brkpt(); // debugger: alias.x = 42
iResult = xy.x + alias.x;
}
@Override
public int getExpectedIResult() {
return 42 + 42;
}
}
class EAMaterializeObjReferencedBy2LocalsAndModify extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference alias = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "alias");
setField(alias, "x", env.vm().mirrorOf(42));
}
}
/////////////////////////////////////////////////////////////////////////////
// Two local variables of the same compiled frame but in different virtual frames reference the same
// object.
// Check if the debugger obtains the same object when reading the two variables
class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal xy = new XYVal(2, 3);
testMethod_inlined(xy);
iResult += xy.x;
}
public void testMethod_inlined(XYVal xy) {
XYVal alias = xy;
dontinline_brkpt();
iResult = alias.x;
}
@Override
public int getExpectedIResult() {
return 2 + 2;
}
}
class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFrames extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference xy = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy");
ObjectReference alias = getLocalRef(bpe.thread().frame(1), "testMethod_inlined", "alias", XYVAL_NAME);
Asserts.assertSame(xy, alias, "xy and alias are expected to reference the same object");
}
}
/////////////////////////////////////////////////////////////////////////////
// Two local variables of the same compiled frame but in different virtual frames reference the same
// object.
// Check if it has the expected effect in the target if the debugger modifies the object.
class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModifyTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal xy = new XYVal(2, 3);
testMethod_inlined(xy); // debugger: xy.x = 42
iResult += xy.x;
}
public void testMethod_inlined(XYVal xy) {
XYVal alias = xy;
dontinline_brkpt();
iResult = alias.x;
}
@Override
public int getExpectedIResult() {
return 42 + 42;
}
}
class EAMaterializeObjReferencedBy2LocalsInDifferentVirtFramesAndModify extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference alias = getLocalRef(bpe.thread().frame(1), "testMethod_inlined", "alias", XYVAL_NAME);
setField(alias, "x", env.vm().mirrorOf(42));
}
}
/////////////////////////////////////////////////////////////////////////////
// Test materialization of an object referenced only from expression stack
class EAMaterializeObjReferencedFromOperandStackTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
@SuppressWarnings("unused")
XYVal xy1 = new XYVal(2, 3);
// Debugger breaks in call to dontinline_brkpt_ret_100() and reads
// the value of the local 'xy1'. This triggers materialization
// of the object on the operand stack
iResult = testMethodInlined(new XYVal(4, 2), dontinline_brkpt_ret_100());
}
public int testMethodInlined(XYVal xy2, int dontinline_brkpt_ret_100) {
return xy2.x + dontinline_brkpt_ret_100;
}
public int dontinline_brkpt_ret_100() {
dontinline_brkpt();
return 100;
}
@Override
public int getExpectedIResult() {
return 4 + 100;
}
}
class EAMaterializeObjReferencedFromOperandStack extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference xy1 = getLocalRef(bpe.thread().frame(2), XYVAL_NAME, "xy1");
checkPrimitiveField(xy1, FD.I, "x", 2);
checkPrimitiveField(xy1, FD.I, "y", 3);
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Tests a regression in the implementation by setting the value of a local int which triggers the
* creation of a deferred update and then getting the reference to a scalar replaced object. The
* issue was that the scalar replaced object was not reallocated. Because of the deferred update it
* was assumed that the reallocation already happened.
*/
class EAMaterializeLocalVariableUponGetAfterSetInteger extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
setLocal(bpe.thread().frame(1), "i", env.vm().mirrorOf(43));
ObjectReference o = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
checkPrimitiveField(o, FD.I, "x", 4);
checkPrimitiveField(o, FD.I, "y", 2);
}
}
class EAMaterializeLocalVariableUponGetAfterSetIntegerTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
int i = 42;
dontinline_brkpt();
iResult = xy.x + xy.y + i;
}
@Override
public int getExpectedIResult() {
return 4 + 2 + 43;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
return true; // setting local variable i always triggers deoptimization
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Locking Tests
//
/////////////////////////////////////////////////////////////////////////////
class EARelockingSimple 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(1), XYVAL_NAME, "l1");
}
}
class EARelockingSimpleTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal l1 = new XYVal(4, 2);
synchronized (l1) {
dontinline_brkpt();
}
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Like {@link EARelockingSimple}. The difference is that there are many
* lightweight locked objects when the relocking is done. With
* <code>-XX:LockingMode=2</code> the lock stack of the thread will be full
* because of this.
*/
class EARelockingWithManyLightweightLocks 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(1), XYVAL_NAME, "l1");
}
}
class EARelockingWithManyLightweightLocksTarget extends EATestCaseBaseTarget {
static class Lock {
}
public static Lock L0, L1, L2, L3, L4, L5, L6, L7, L8, L9;
void allocateLocks() {
L0 = new Lock();
L1 = new Lock();
L2 = new Lock();
L3 = new Lock();
L4 = new Lock();
L5 = new Lock();
L6 = new Lock();
L7 = new Lock();
L8 = new Lock();
L9 = new Lock();
}
@Override
public void setUp() {
super.setUp();
allocateLocks();
}
@Override
public void warmupDone() {
super.warmupDone();
allocateLocks(); // get rid of already inflated ones
}
public void dontinline_testMethod() {
XYVal l1 = new XYVal(4, 2);
synchronized(L0) {
synchronized(L1) {
synchronized(L2) {
synchronized(L3) {
synchronized(L4) {
synchronized(L5) {
synchronized(L6) {
synchronized(L7) {
synchronized(L8) {
synchronized(L9) {
synchronized (l1) {
dontinline_brkpt();
}
}
}
}
}
}
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////
// 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;
}
}
/////////////////////////////////////////////////////////////////////////////
// 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.
// This is a variant of EARelockingSimpleWithAccessInOtherThread with a dynamic call (not devirtualized).
class EARelockingSimpleWithAccessInOtherThread_02_DynamicCall extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
String l1ClassName = EARelockingSimpleWithAccessInOtherThread_02_DynamicCall_Target.SyncCounter.class.getName();
ObjectReference ctr = getLocalRef(bpe.thread().frame(2), l1ClassName, "l1");
setField(testCase, "sharedCounter", ctr);
terminateEndlessLoop();
}
}
class EARelockingSimpleWithAccessInOtherThread_02_DynamicCall_Target extends EATestCaseBaseTarget {
public static final BrkPtDispatchA[] disp =
{new BrkPtDispatchA(), new BrkPtDispatchB(), new BrkPtDispatchC(), new BrkPtDispatchD()};
public static class BrkPtDispatchA {
public EATestCaseBaseTarget testCase;
public void dontinline_brkpt() { testCase.dontinline_brkpt(); }
}
public static class BrkPtDispatchB extends BrkPtDispatchA {
@Override
public void dontinline_brkpt() { testCase.dontinline_brkpt(); }
}
public static class BrkPtDispatchC extends BrkPtDispatchA {
@Override
public void dontinline_brkpt() { testCase.dontinline_brkpt(); }
}
public static class BrkPtDispatchD extends BrkPtDispatchA {
@Override
public void dontinline_brkpt() {
testCase.dontinline_brkpt();
}
}
public static class SyncCounter {
private int val;
public synchronized int inc() { return val++; }
}
public volatile SyncCounter sharedCounter;
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
for (BrkPtDispatchA d : disp) {
d.testCase = this;
}
doLoop = true;
new Thread(() -> {
while (doLoop) {
SyncCounter ctr = sharedCounter;
if (ctr != null) {
ctr.inc();
}
}
}).start();
}
public int dispCount;
public void dontinline_testMethod() {
SyncCounter l1 = new SyncCounter();
synchronized (l1) { // Eliminated locking
l1.inc();
// Use different types for the subsequent call to prevent devirtualization.
BrkPtDispatchA d = disp[(dispCount++) & 3];
d.dontinline_brkpt(); // Dynamic call. 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 {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal l1 = new XYVal(4, 2);
synchronized (l1) {
testMethod_inlined(l1);
}
}
public void testMethod_inlined(XYVal l2) {
synchronized (l2) {
dontinline_brkpt();
}
}
}
class EARelockingRecursive 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");
}
}
/////////////////////////////////////////////////////////////////////////////
// Object ref l1 is retrieved by the debugger at a location where nested locks are omitted. The
// accessed object is globally reachable already before the access, therefore no relocking is done.
class EARelockingNestedInflatedTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
// Access does not trigger deopt., as escape state is already global escape.
return false;
}
public void dontinline_testMethod() {
XYVal l1 = inflatedLock;
synchronized (l1) {
testMethod_inlined(l1);
}
}
public void testMethod_inlined(XYVal l2) {
synchronized (l2) { // eliminated nested locking
dontinline_brkpt();
}
}
}
class EARelockingNestedInflated 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");
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Like {@link EARelockingNestedInflated} with the difference that there is
* a scalar replaced object in the scope from which the object with eliminated nested locking
* is read. This triggers materialization and relocking.
*/
class EARelockingNestedInflated_02 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 EARelockingNestedInflated_02Target extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
@SuppressWarnings("unused")
XYVal xy = new XYVal(1, 1); // scalar replaced
XYVal l1 = inflatedLock; // read by debugger
synchronized (l1) {
testMethod_inlined(l1);
}
}
public void testMethod_inlined(XYVal l2) {
synchronized (l2) { // eliminated nested locking
dontinline_brkpt();
}
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Like {@link EARelockingNestedInflated_02} with the difference that the
* inflation of the lock happens because of contention.
*/
class EARelockingNestedInflated_03 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 EARelockingNestedInflated_03Target extends EATestCaseBaseTarget {
public XYVal lockInflatedByContention;
public boolean doLockNow;
public EATestCaseBaseTarget testCase;
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
lockInflatedByContention = new XYVal(1, 1);
testCase = this;
}
@Override
public void warmupDone() {
super.warmupDone();
// Use new lock. lockInflatedByContention might have been inflated because of recursion.
lockInflatedByContention = new XYVal(1, 1);
// Start thread that tries to enter lockInflatedByContention while the main thread owns it -> inflation
DebuggeeWrapper.newThread(() -> {
while (true) {
synchronized (testCase) {
try {
if (doLockNow) {
doLockNow = false; // reset for main thread
testCase.notify();
break;
}
testCase.wait();
} catch (InterruptedException e) { /* ignored */ }
}
}
synchronized (lockInflatedByContention) { // will block and trigger inflation
msg(Thread.currentThread().getName() + ": acquired lockInflatedByContention");
}
}, testCaseName + ": Lock Contender (test thread)").start();
}
public void dontinline_testMethod() {
@SuppressWarnings("unused")
XYVal xy = new XYVal(1, 1); // scalar replaced
XYVal l1 = lockInflatedByContention; // read by debugger
synchronized (l1) {
testMethod_inlined(l1);
}
}
public void testMethod_inlined(XYVal l2) {
synchronized (l2) { // eliminated nested locking
dontinline_notifyOtherThread();
dontinline_brkpt();
}
}
public void dontinline_notifyOtherThread() {
if (!warmupDone) {
return;
}
synchronized (testCase) {
doLockNow = true;
testCase.notify();
// wait for other thread to reset doLockNow again
while (doLockNow) {
try {
testCase.wait();
} catch (InterruptedException e) { /* ignored */ }
}
}
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Checks if an eliminated lock of an ArgEscape object l1 can be relocked if
* l1 is locked in a callee frame.
*/
class EARelockingArgEscapeLWLockedInCalleeFrame 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 EARelockingArgEscapeLWLockedInCalleeFrameTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal l1 = new XYVal(1, 1); // ArgEscape
synchronized (l1) { // eliminated
l1.dontinline_sync_method(this); // l1 escapes
}
}
@Override
public boolean testFrameShouldBeDeoptimized() {
// Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized
return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized();
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Similar to {@link EARelockingArgEscapeLWLockedInCalleeFrame}. In addition
* the test method has got a scalar replaced object with eliminated locking.
* This pattern matches a regression in the implementation.
*/
class EARelockingArgEscapeLWLockedInCalleeFrame_2 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 EARelockingArgEscapeLWLockedInCalleeFrame_2Target extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal l1 = new XYVal(1, 1); // ArgEscape
XYVal l2 = new XYVal(4, 2); // NoEscape, scalar replaced
synchronized (l1) { // eliminated
synchronized (l2) { // eliminated
l1.dontinline_sync_method(this); // l1 escapes
}
}
iResult = l2.x + l2.y;
}
@Override
public int getExpectedIResult() {
return 6;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* 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.
*/
class EARelockingObjectCurrentlyWaitingOn extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
env.vm().resume();
boolean inWait = false;
do {
Thread.sleep(100);
env.targetMainThread.suspend();
printStack(env.targetMainThread);
inWait = env.targetMainThread.frame(0).location().method().name().equals("wait0");
if (!inWait) {
msg("Target not yet in java.lang.Object.wait(long).");
env.targetMainThread.resume();
}
} while(!inWait);
StackFrame testMethodFrame = env.targetMainThread.frame(5);
// Access triggers relocking of all eliminated locks, including nested locks of l1 which references
// the object on which the target main thread is currently waiting.
ObjectReference l0 = getLocalRef(testMethodFrame, EARelockingObjectCurrentlyWaitingOnTarget.ForLocking.class.getName(), "l0");
Asserts.assertEQ(l0.entryCount(), 1, "wrong entry count");
ObjectReference l1 = getLocalRef(testMethodFrame, EARelockingObjectCurrentlyWaitingOnTarget.ForLocking.class.getName(), "l1");
Asserts.assertEQ(l1.entryCount(), 0, "wrong entry count");
setField(testCase, "objToNotifyOn", l1);
}
}
class EARelockingObjectCurrentlyWaitingOnTarget extends EATestCaseBaseTarget {
public static class ForLocking {
}
public volatile Object objToNotifyOn; // debugger assigns value when notify thread should call objToNotifyOn.notifyAll()
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
@Override
public void warmupDone() {
super.warmupDone();
Thread t = new Thread(() -> doNotify());
t.start();
}
public void doNotify() {
while (objToNotifyOn == null) {
try {
msg("objToNotifyOn is still null");
Thread.sleep(100);
} catch (InterruptedException e) { /* ignored */ }
}
synchronized (objToNotifyOn) {
// will be received by the target main thread waiting in dontinline_waitWhenWarmupDone
msg("calling objToNotifyOn.notifyAll()");
objToNotifyOn.notifyAll();
}
}
@Override
public boolean testFrameShouldBeDeoptimized() {
return false;
}
@Override
public void dontinline_testMethod() throws Exception {
ForLocking l0 = new ForLocking(); // will be scalar replaced; access triggers realloc/relock
ForLocking l1 = new ForLocking();
synchronized (l0) {
synchronized (l1) {
testMethod_inlined(l1);
}
}
}
public void testMethod_inlined(ForLocking l2) throws Exception {
synchronized (l2) { // eliminated nested locking
dontinline_waitWhenWarmupDone(l2);
}
}
public void dontinline_waitWhenWarmupDone(ForLocking l2) throws Exception {
if (warmupDone) {
l2.wait();
}
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Test relocking eliminated @ValueBased object.
*/
class EARelockingValueBased 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(1), Integer.class.getName(), "l1");
}
}
class EARelockingValueBasedTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
Integer l1 = new Integer(255);
synchronized (l1) {
dontinline_brkpt();
}
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Test cases that require deoptimization even though neither locks
// nor allocations are eliminated at the point where escape state is changed.
//
/////////////////////////////////////////////////////////////////////////////
/**
* Let xy be NoEscape whose allocation cannot be eliminated (simulated by
* -XX:-EliminateAllocations). The holding compiled frame has to be deoptimized when debugger
* accesses xy because afterwards locking on xy is omitted.
* Note: there are no EA based optimizations at the escape point.
*/
class EADeoptFrameAfterReadLocalObject_01 extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
@SuppressWarnings("unused")
ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
}
}
class EADeoptFrameAfterReadLocalObject_01Target extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(1, 1);
dontinline_brkpt(); // Debugger reads xy, when there are no virtual objects or eliminated locks in scope
synchronized (xy) { // Locking is eliminated.
xy.x++;
xy.y++;
}
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Similar to {@link EADeoptFrameAfterReadLocalObject_01} with the difference that the debugger
* reads xy from an inlined callee. So xy is NoEscape instead of ArgEscape.
*/
class EADeoptFrameAfterReadLocalObject_01BTarget extends EATestCaseBaseTarget {
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public void dontinline_testMethod() {
XYVal xy = new XYVal(1, 1);
callee(xy); // Debugger acquires ref to xy from inlined callee
// xy is NoEscape, nevertheless the object is not replaced
// by scalars if running with -XX:-EliminateAllocations.
// In that case there are no EA based optimizations were
// the debugger reads the NoEscape object.
synchronized (xy) { // Locking is eliminated.
xy.x++;
xy.y++;
}
}
public void callee(XYVal xy) {
dontinline_brkpt(); // Debugger reads xy.
// There are no virtual objects or eliminated locks.
}
}
class EADeoptFrameAfterReadLocalObject_01B extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
@SuppressWarnings("unused")
ObjectReference xy = getLocalRef(bpe.thread().frame(1), "callee", "xy", XYVAL_NAME);
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Let xy be ArgEscape. The frame dontinline_testMethod() has to be deoptimized when debugger
* acquires xy from dontinline_callee() because afterwards locking on xy is omitted.
* Note: there are no EA based optimizations at the escape point.
*/
class EADeoptFrameAfterReadLocalObject_02 extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
@SuppressWarnings("unused")
ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee", "xy", XYVAL_NAME);
}
}
class EADeoptFrameAfterReadLocalObject_02Target extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(1, 1);
dontinline_callee(xy); // xy is ArgEscape, debugger acquires ref to xy from callee
synchronized (xy) { // Locking is eliminated.
xy.x++;
xy.y++;
}
}
public void dontinline_callee(XYVal xy) {
dontinline_brkpt(); // Debugger reads xy.
// There are no virtual objects or eliminated locks.
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
// Graal does not provide debug info about arg escape objects, therefore the frame is not deoptimized
return !UseJVMCICompiler && super.testFrameShouldBeDeoptimized();
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Similar to {@link EADeoptFrameAfterReadLocalObject_02} there is an ArgEscape object xy, but in
* contrast it is not in the parameter list of a call when the debugger reads an object.
* Therefore the frame of the test method should not be deoptimized
*/
class EADeoptFrameAfterReadLocalObject_02B extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
@SuppressWarnings("unused")
ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee", "xy", XYVAL_NAME);
}
}
class EADeoptFrameAfterReadLocalObject_02BTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(1, 1);
dontinline_make_arg_escape(xy); // because of this call xy is ArgEscape
dontinline_callee(); // xy is ArgEscape, but not a parameter of this call
synchronized (xy) { // Locking is eliminated.
xy.x++;
xy.y++;
}
}
public void dontinline_callee() {
@SuppressWarnings("unused")
XYVal xy = new XYVal(2, 2);
dontinline_brkpt(); // Debugger reads xy.
// No need to deoptimize the caller frame
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
return false;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Similar to {@link EADeoptFrameAfterReadLocalObject_02} there is an ArgEscape object xy in
* dontinline_testMethod() which is being passed as parameter when the debugger accesses a local object.
* Nevertheless dontinline_testMethod must not be deoptimized because there is an entry frame
* between it and the frame accessed by the debugger.
*/
class EADeoptFrameAfterReadLocalObject_02C extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
@SuppressWarnings("unused")
ObjectReference xy = getLocalRef(bpe.thread().frame(1), "dontinline_callee_accessed_by_debugger", "xy", XYVAL_NAME);
}
}
class EADeoptFrameAfterReadLocalObject_02CTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(1, 1);
dontinline_callee(xy); // xy is ArgEscape and being passed as parameter
synchronized (xy) { // Locking is eliminated.
xy.x++;
xy.y++;
}
}
public void dontinline_callee(XYVal xy) {
if (warmupDone) {
dontinline_call_with_entry_frame(this, "dontinline_callee_accessed_by_debugger");
}
}
public void dontinline_callee_accessed_by_debugger() {
@SuppressWarnings("unused")
XYVal xy = new XYVal(2, 2);
dontinline_brkpt(); // Debugger reads xy.
// No need to deoptimize the caller frame
}
@Override
public void setUp() {
super.setUp();
// the method depth in debuggee is 11 as it includes all hidden frames
// the expected method depth is 6 excluding 5 hidden frames
testMethodDepth = 11-5;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
return false;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Let xy be NoEscape whose allocation cannot be eliminated (e.g. because of
* -XX:-EliminateAllocations). The holding compiled frame has to be deoptimized when debugger
* accesses xy because the following field accesses get eliminated. Note: there are no EA based
* optimizations at the escape point.
*/
class EADeoptFrameAfterReadLocalObject_03 extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
ObjectReference xy = getLocalRef(bpe.thread().frame(1), XYVAL_NAME, "xy");
setField(xy, "x", env.vm().mirrorOf(1));
}
}
class EADeoptFrameAfterReadLocalObject_03Target extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(0, 1);
dontinline_brkpt(); // Debugger reads xy, when there are no virtual objects or
// eliminated locks in scope and modifies xy.x
iResult = xy.x + xy.y; // Loads are replaced by constants 0 and 1.
}
@Override
public int getExpectedIResult() {
return 1 + 1;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Monitor info tests
//
/////////////////////////////////////////////////////////////////////////////
class EAGetOwnedMonitorsTarget extends EATestCaseBaseTarget {
public long checkSum;
public void dontinline_testMethod() {
XYVal l1 = new XYVal(4, 2);
synchronized (l1) {
dontinline_endlessLoop();
}
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
}
class EAGetOwnedMonitors extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
msg("resume");
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
// In contrast to JVMTI, JDWP requires a target thread to be suspended, before the owned monitors can be queried
msg("suspend target");
env.targetMainThread.suspend();
msg("Get owned monitors");
List<ObjectReference> monitors = env.targetMainThread.ownedMonitors();
Asserts.assertEQ(monitors.size(), 1, "unexpected number of owned monitors");
terminateEndlessLoop();
}
}
/////////////////////////////////////////////////////////////////////////////
class EAEntryCountTarget extends EATestCaseBaseTarget {
public long checkSum;
public void dontinline_testMethod() {
XYVal l1 = new XYVal(4, 2);
synchronized (l1) {
inline_testMethod2(l1);
}
}
public void inline_testMethod2(XYVal l1) {
synchronized (l1) {
dontinline_endlessLoop();
}
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
}
class EAEntryCount extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
msg("resume");
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
// In contrast to JVMTI, JDWP requires a target thread to be suspended, before the owned monitors can be queried
msg("suspend target");
env.targetMainThread.suspend();
msg("Get owned monitors");
List<ObjectReference> monitors = env.targetMainThread.ownedMonitors();
Asserts.assertEQ(monitors.size(), 1, "unexpected number of owned monitors");
msg("Get entry count");
int entryCount = monitors.get(0).entryCount();
Asserts.assertEQ(entryCount, 2, "wrong entry count");
terminateEndlessLoop();
}
}
/////////////////////////////////////////////////////////////////////////////
//
// PopFrame tests
//
/////////////////////////////////////////////////////////////////////////////
/**
* PopFrame into caller frame with scalar replaced objects.
*/
class EAPopFrameNotInlined extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
printStack(bpe.thread());
msg("PopFrame");
bpe.thread().popFrames(bpe.thread().frame(0));
msg("PopFrame DONE");
}
@Override
public boolean shouldSkip() {
// And Graal currently doesn't support PopFrame
return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler;
}
}
class EAPopFrameNotInlinedTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
dontinline_brkpt();
iResult = xy.x + xy.y;
}
@Override
public boolean testFrameShouldBeDeoptimized() {
// Test is only performed after the frame pop.
// Then dontinline_testMethod is interpreted.
return false;
}
@Override
public int getExpectedIResult() {
return 4 + 2;
}
@Override
public boolean shouldSkip() {
// And Graal currently doesn't support PopFrame
return super.shouldSkip() || UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Pop frames into {@link EAPopFrameNotInlinedReallocFailureTarget#dontinline_testMethod()} which
* holds scalar replaced objects. In preparation of the pop frame operations the vm eagerly
* reallocates scalar replaced objects to avoid failures when actually popping the frames. We provoke
* a reallocation failures and expect {@link VMOutOfMemoryException}.
*/
class EAPopFrameNotInlinedReallocFailure extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
ThreadReference thread = bpe.thread();
printStack(thread);
// frame[0]: EATestCaseBaseTarget.dontinline_brkpt()
// frame[1]: EAPopFrameNotInlinedReallocFailureTarget.dontinline_consume_all_memory_brkpt()
// frame[2]: EAPopFrameNotInlinedReallocFailureTarget.dontinline_testMethod()
// frame[3]: EATestCaseBaseTarget.run()
// frame[4]: EATestsTarget.main(java.lang.String[])
msg("PopFrame");
boolean coughtOom = false;
try {
// try to pop dontinline_consume_all_memory_brkpt
thread.popFrames(thread.frame(1));
} catch (VMOutOfMemoryException oom) {
// as expected
msg("cought OOM");
coughtOom = true;
}
freeAllMemory();
// We succeeded to pop just one frame. When we continue, we will call dontinline_brkpt() again.
Asserts.assertTrue(coughtOom, "PopFrame should have triggered an OOM exception in target");
String expectedTopFrame = "dontinline_consume_all_memory_brkpt";
Asserts.assertEQ(expectedTopFrame, thread.frame(0).location().method().name());
printStack(thread);
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't provide all information about non-escaping objects in debug info
return super.shouldSkip() ||
!env.targetVMOptions.EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
env.targetVMOptions.ZGCIsSelected ||
env.targetVMOptions.ShenandoahGCIsSelected ||
env.targetVMOptions.DeoptimizeObjectsALot ||
env.targetVMOptions.UseJVMCICompiler;
}
}
class EAPopFrameNotInlinedReallocFailureTarget extends EATestCaseBaseTarget {
public boolean doneAlready;
public void dontinline_testMethod() {
long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced
Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced
dontinline_consume_all_memory_brkpt();
lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9]
+ v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9;
}
public void dontinline_consume_all_memory_brkpt() {
if (warmupDone && !doneAlready) {
doneAlready = true;
consumeAllMemory(); // provoke reallocation failure
dontinline_brkpt();
}
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
@Override
public long getExpectedLResult() {
long n = 10;
return 2*n*(n+1)/2;
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't provide all information about non-escaping objects in debug info
return super.shouldSkip() ||
!EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
ZGCIsSelected ||
ShenandoahGCIsSelected ||
DeoptimizeObjectsALot ||
UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Pop inlined top frame dropping into method with scalar replaced opjects.
*/
class EAPopInlinedMethodWithScalarReplacedObjectsReallocFailure extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
ThreadReference thread = env.targetMainThread;
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
thread.suspend();
printStack(thread);
// frame[0]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn()
// frame[1]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod()
// frame[2]: EATestCaseBaseTarget.run()
msg("Pop Frames");
boolean coughtOom = false;
try {
thread.popFrames(thread.frame(0)); // Request pop frame of inlinedCallForcedToReturn()
// reallocation is triggered here
} catch (VMOutOfMemoryException oom) {
// as expected
msg("cought OOM");
coughtOom = true;
}
printStack(thread);
// frame[0]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn()
// frame[1]: EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod()
// frame[2]: EATestCaseBaseTarget.run()
freeAllMemory();
setField(testCase, "loopCount", env.vm().mirrorOf(0)); // terminate loop
Asserts.assertTrue(coughtOom, "PopFrame should have triggered an OOM exception in target");
String expectedTopFrame = "inlinedCallForcedToReturn";
Asserts.assertEQ(expectedTopFrame, thread.frame(0).location().method().name());
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't provide all information about non-escaping objects in debug info
return super.shouldSkip() ||
!env.targetVMOptions.EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
env.targetVMOptions.ZGCIsSelected ||
env.targetVMOptions.ShenandoahGCIsSelected ||
env.targetVMOptions.DeoptimizeObjectsALot ||
env.targetVMOptions.UseJVMCICompiler;
}
}
class EAPopInlinedMethodWithScalarReplacedObjectsReallocFailureTarget extends EATestCaseBaseTarget {
public long checkSum;
public void dontinline_testMethod() {
long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced
Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced
long l = inlinedCallForcedToReturn();
lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9]
+ v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9;
}
public long inlinedCallForcedToReturn() {
long cs = checkSum;
dontinline_consumeAllMemory();
while (loopCount-- > 0) {
targetIsInLoop = true;
checkSum += checkSum % ++cs;
}
loopCount = 3;
targetIsInLoop = false;
return checkSum;
}
public void dontinline_consumeAllMemory() {
if (warmupDone && (loopCount > 3)) {
consumeAllMemory();
}
}
@Override
public long getExpectedLResult() {
long n = 10;
return 2*n*(n+1)/2;
}
@Override
public void setUp() {
super.setUp();
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't provide all information about non-escaping objects in debug info
return super.shouldSkip() ||
!EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
ZGCIsSelected ||
ShenandoahGCIsSelected ||
DeoptimizeObjectsALot ||
UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// ForceEarlyReturn tests
//
/////////////////////////////////////////////////////////////////////////////
/**
* ForceEarlyReturn into caller frame with scalar replaced objects.
*/
class EAForceEarlyReturnNotInlined extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V");
ThreadReference thread = bpe.thread();
printStack(thread);
// frame[0]: EATestCaseBaseTarget.dontinline_brkpt()
// frame[1]: EATestCaseBaseTarget.dontinline_brkpt_iret()
// frame[2]: EAForceEarlyReturnNotInlinedTarget.dontinline_testMethod()
// frame[3]: EATestCaseBaseTarget.run()
// frame[4]: EATestsTarget.main(java.lang.String[])
msg("Step out");
env.stepOut(thread); // return from dontinline_brkpt
printStack(thread);
msg("ForceEarlyReturn");
thread.forceEarlyReturn(env.vm().mirrorOf(43)); // return from dontinline_brkpt_iret,
// does not trigger reallocation in contrast to PopFrame
msg("Step over line");
env.stepOverLine(thread); // reallocation is triggered here
printStack(thread);
msg("ForceEarlyReturn DONE");
}
@Override
public boolean shouldSkip() {
// Graal currently doesn't support Force Early Return
return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler;
}
}
class EAForceEarlyReturnNotInlinedTarget extends EATestCaseBaseTarget {
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
int i = dontinline_brkpt_iret();
iResult = xy.x + xy.y + i;
}
@Override
public int getExpectedIResult() {
return 4 + 2 + 43;
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
}
public boolean testFrameShouldBeDeoptimized() {
return true; // because of stepping
}
@Override
public boolean shouldSkip() {
// Graal currently doesn't support Force Early Return
return super.shouldSkip() || UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* ForceEarlyReturn at safepoint in frame with scalar replaced objects.
*/
class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjects extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
ThreadReference thread = env.targetMainThread;
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
thread.suspend();
printStack(thread);
// frame[0]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget.inlinedCallForcedToReturn()
// frame[1]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget.dontinline_testMethod()
// frame[2]: EATestCaseBaseTarget.run()
msg("ForceEarlyReturn");
thread.forceEarlyReturn(env.vm().mirrorOf(43)); // Request force return 43 from inlinedCallForcedToReturn()
// reallocation is triggered here
msg("Step over instruction to do the forced return");
env.stepOverInstruction(thread);
printStack(thread);
msg("ForceEarlyReturn DONE");
}
@Override
public boolean shouldSkip() {
// Graal currently doesn't support Force Early Return
return super.shouldSkip() || env.targetVMOptions.UseJVMCICompiler;
}
}
class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsTarget extends EATestCaseBaseTarget {
public int checkSum;
public void dontinline_testMethod() {
XYVal xy = new XYVal(4, 2);
int i = inlinedCallForcedToReturn();
iResult = xy.x + xy.y + i;
}
public int inlinedCallForcedToReturn() { // forced to return 43
int i = checkSum;
while (loopCount-- > 0) {
targetIsInLoop = true;
checkSum += checkSum % ++i;
}
loopCount = 3;
targetIsInLoop = false;
return checkSum;
}
@Override
public int getExpectedIResult() {
return 4 + 2 + 43;
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
public boolean testFrameShouldBeDeoptimized() {
return true; // because of stepping
}
@Override
public boolean shouldSkip() {
// Graal currently doesn't support Force Early Return
return super.shouldSkip() || UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* ForceEarlyReturn with reallocation failure.
*/
class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailure extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
ThreadReference thread = env.targetMainThread;
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
thread.suspend();
printStack(thread);
// frame[0]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.inlinedCallForcedToReturn()
// frame[1]: EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget.dontinline_testMethod()
// frame[2]: EATestCaseBaseTarget.run()
msg("ForceEarlyReturn");
boolean coughtOom = false;
try {
thread.forceEarlyReturn(env.vm().mirrorOf(43)); // Request force return 43 from inlinedCallForcedToReturn()
// reallocation is triggered here
} catch (VMOutOfMemoryException oom) {
// as expected
msg("cought OOM");
coughtOom = true;
}
freeAllMemory();
Asserts.assertTrue(coughtOom, "ForceEarlyReturn should have triggered an OOM exception in target");
printStack(thread);
msg("ForceEarlyReturn(2)");
thread.forceEarlyReturn(env.vm().mirrorOf(43));
msg("Step over instruction to do the forced return");
env.stepOverInstruction(thread);
printStack(thread);
msg("ForceEarlyReturn DONE");
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't support Force Early Return
return super.shouldSkip() ||
!env.targetVMOptions.EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
env.targetVMOptions.ZGCIsSelected ||
env.targetVMOptions.ShenandoahGCIsSelected ||
env.targetVMOptions.DeoptimizeObjectsALot ||
env.targetVMOptions.UseJVMCICompiler;
}
}
class EAForceEarlyReturnOfInlinedMethodWithScalarReplacedObjectsReallocFailureTarget extends EATestCaseBaseTarget {
public int checkSum;
public void dontinline_testMethod() {
long a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // scalar replaced
Vector10 v = new Vector10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // scalar replaced
long l = inlinedCallForcedToReturn();
lResult = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9]
+ v.i0 + v.i1 + v.i2 + v.i3 + v.i4 + v.i5 + v.i6 + v.i7 + v.i8 + v.i9 + l;
}
public long inlinedCallForcedToReturn() { // forced to return 43
long cs = checkSum;
dontinline_consumeAllMemory();
while (loopCount-- > 0) {
targetIsInLoop = true;
checkSum += checkSum % ++cs;
}
loopCount = 3;
targetIsInLoop = false;
return checkSum;
}
public void dontinline_consumeAllMemory() {
if (warmupDone) {
consumeAllMemory();
}
}
@Override
public long getExpectedLResult() {
long n = 10;
return 2*n*(n+1)/2 + 43;
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
@Override
public boolean shouldSkip() {
// OOMEs because of realloc failures with DeoptimizeObjectsALot are too random.
// And Graal currently doesn't support Force Early Return
return super.shouldSkip() ||
!EliminateAllocations ||
// With ZGC or Shenandoah the OOME is not always thrown as expected
ZGCIsSelected ||
ShenandoahGCIsSelected ||
DeoptimizeObjectsALot ||
UseJVMCICompiler;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Get Instances of ReferenceType
//
/////////////////////////////////////////////////////////////////////////////
/**
* Check if instances of a type are found even if they are scalar replaced. To stress the
* implementation a little more, the instances should be retrieved while the target is running.
*/
class EAGetInstancesOfReferenceType extends EATestCaseBaseDebugger {
public void runTestCase() throws Exception {
printStack(env.targetMainThread);
ReferenceType cls = ((ClassObjectReference)getField(testCase, "cls")).reflectedType();
msg("reflected type is " + cls);
msg("resume");
env.vm().resume();
waitUntilTargetHasEnteredEndlessLoop();
// do this while thread is running!
msg("Retrieve instances of " + cls.name());
List<ObjectReference> instances = cls.instances(10);
Asserts.assertEQ(instances.size(), 3, "unexpected number of instances of " + cls.name());
// invariant: main thread is suspended at the end of the test case
msg("suspend");
env.targetMainThread.suspend();
terminateEndlessLoop();
}
}
class EAGetInstancesOfReferenceTypeTarget extends EATestCaseBaseTarget {
public long checkSum;
public static Class<LocalXYVal> cls = LocalXYVal.class;
public static class LocalXYVal {
public int x, y;
public LocalXYVal(int x, int y) {
this.x = x; this.y = y;
}
}
@Override
public void dontinline_testMethod() {
LocalXYVal p1 = new LocalXYVal(4, 2);
LocalXYVal p2 = new LocalXYVal(5, 3);
LocalXYVal p3 = new LocalXYVal(6, 4);
dontinline_endlessLoop();
iResult = p1.x+p1.y + p2.x+p2.y + p3.x+p3.y;
}
@Override
public int getExpectedIResult() {
return 6+8+10;
}
@Override
public void setUp() {
super.setUp();
testMethodDepth = 2;
loopCount = 3;
}
public void warmupDone() {
super.warmupDone();
msg("enter 'endless' loop by setting loopCount = Long.MAX_VALUE");
loopCount = Long.MAX_VALUE; // endless loop
}
}
// End of test case collection
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Helper classes
class XYVal {
public int x, y;
public XYVal(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Note that we don't use a sync block here because javac would generate an synthetic exception
* handler for the synchronized block that catches Throwable E, unlocks and throws E
* again. The throw bytecode causes the BCEscapeAnalyzer to set the escape state to GlobalEscape
* (see comment on exception handlers in BCEscapeAnalyzer::iterate_blocks())
*/
public synchronized void dontinline_sync_method(EATestCaseBaseTarget target) {
target.dontinline_brkpt();
}
/**
* Just like {@link #dontinline_sync_method(EATestCaseBaseTarget)} but without the call to
* {@link EATestCaseBaseTarget#dontinline_brkpt()}.
*/
public synchronized void dontinline_sync_method_no_brkpt(EATestCaseBaseTarget target) {
}
}
class Vector10 {
int i0, i1, i2, i3, i4, i5, i6, i7, i8, i9;
public Vector10(int j0, int j1, int j2, int j3, int j4, int j5, int j6, int j7, int j8, int j9) {
i0=j0; i1=j1; i2=j2; i3=j3; i4=j4; i5=j5; i6=j6; i7=j7; i8=j8; i9=j9;
}
}
class ILFDO {
public int i;
public int i2;
public long l;
public long l2;
public float f;
public float f2;
public double d;
public double d2;
public Long o;
public Long o2;
public ILFDO(int i,
int i2,
long l,
long l2,
float f,
float f2,
double d,
double d2,
Long o,
Long o2) {
this.i = i;
this.i2 = i2;
this.l = l;
this.l2 = l2;
this.f = f;
this.f2 = f2;
this.d = d;
this.d2 = d2;
this.o = o;
this.o2 = o2;
}
}