blob: 5ca2bb5c04099da4a3c94e9a66ddc979280f11d1 [file] [log] [blame]
/*
* Copyright (c) 2013, 2014, Oracle and/or its affiliates. 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.
*/
import com.sun.management.HotSpotDiagnosticMXBean;
import com.sun.management.VMOption;
import sun.hotspot.WhiteBox;
import sun.hotspot.code.NMethod;
import sun.management.ManagementFactoryHelper;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Function;
/**
* Abstract class for WhiteBox testing of JIT.
*
* @author igor.ignatyev@oracle.com
*/
public abstract class CompilerWhiteBoxTest {
/** {@code CompLevel::CompLevel_none} -- Interpreter */
protected static int COMP_LEVEL_NONE = 0;
/** {@code CompLevel::CompLevel_any}, {@code CompLevel::CompLevel_all} */
protected static int COMP_LEVEL_ANY = -1;
/** {@code CompLevel::CompLevel_simple} -- C1 */
protected static int COMP_LEVEL_SIMPLE = 1;
/** {@code CompLevel::CompLevel_limited_profile} -- C1, invocation & backedge counters */
protected static int COMP_LEVEL_LIMITED_PROFILE = 2;
/** {@code CompLevel::CompLevel_full_profile} -- C1, invocation & backedge counters + mdo */
protected static int COMP_LEVEL_FULL_PROFILE = 3;
/** {@code CompLevel::CompLevel_full_optimization} -- C2 or Shark */
protected static int COMP_LEVEL_FULL_OPTIMIZATION = 4;
/** Maximal value for CompLevel */
protected static int COMP_LEVEL_MAX = COMP_LEVEL_FULL_OPTIMIZATION;
/** Instance of WhiteBox */
protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
/** Value of {@code -XX:CompileThreshold} */
protected static final int COMPILE_THRESHOLD
= Integer.parseInt(getVMOption("CompileThreshold", "10000"));
/** Value of {@code -XX:BackgroundCompilation} */
protected static final boolean BACKGROUND_COMPILATION
= Boolean.valueOf(getVMOption("BackgroundCompilation", "true"));
/** Value of {@code -XX:TieredCompilation} */
protected static final boolean TIERED_COMPILATION
= Boolean.valueOf(getVMOption("TieredCompilation", "false"));
/** Value of {@code -XX:TieredStopAtLevel} */
protected static final int TIERED_STOP_AT_LEVEL
= Integer.parseInt(getVMOption("TieredStopAtLevel", "0"));
/** Flag for verbose output, true if {@code -Dverbose} specified */
protected static final boolean IS_VERBOSE
= System.getProperty("verbose") != null;
/** invocation count to trigger compilation */
protected static final int THRESHOLD;
/** invocation count to trigger OSR compilation */
protected static final long BACKEDGE_THRESHOLD;
/** Value of {@code java.vm.info} (interpreted|mixed|comp mode) */
protected static final String MODE = System.getProperty("java.vm.info");
static {
if (TIERED_COMPILATION) {
BACKEDGE_THRESHOLD = THRESHOLD = 150000;
} else {
THRESHOLD = COMPILE_THRESHOLD;
BACKEDGE_THRESHOLD = COMPILE_THRESHOLD * Long.parseLong(getVMOption(
"OnStackReplacePercentage"));
}
}
/**
* Returns value of VM option.
*
* @param name option's name
* @return value of option or {@code null}, if option doesn't exist
* @throws NullPointerException if name is null
*/
protected static String getVMOption(String name) {
Objects.requireNonNull(name);
HotSpotDiagnosticMXBean diagnostic
= ManagementFactoryHelper.getDiagnosticMXBean();
VMOption tmp;
try {
tmp = diagnostic.getVMOption(name);
} catch (IllegalArgumentException e) {
tmp = null;
}
return (tmp == null ? null : tmp.getValue());
}
/**
* Returns value of VM option or default value.
*
* @param name option's name
* @param defaultValue default value
* @return value of option or {@code defaultValue}, if option doesn't exist
* @throws NullPointerException if name is null
* @see #getVMOption(String)
*/
protected static String getVMOption(String name, String defaultValue) {
String result = getVMOption(name);
return result == null ? defaultValue : result;
}
/** copy of is_c1_compile(int) from utilities/globalDefinitions.hpp */
protected static boolean isC1Compile(int compLevel) {
return (compLevel > COMP_LEVEL_NONE)
&& (compLevel < COMP_LEVEL_FULL_OPTIMIZATION);
}
/** copy of is_c2_compile(int) from utilities/globalDefinitions.hpp */
protected static boolean isC2Compile(int compLevel) {
return compLevel == COMP_LEVEL_FULL_OPTIMIZATION;
}
protected static void main(
Function<TestCase, CompilerWhiteBoxTest> constructor,
String[] args) {
if (args.length == 0) {
for (TestCase test : SimpleTestCase.values()) {
constructor.apply(test).runTest();
}
} else {
for (String name : args) {
constructor.apply(SimpleTestCase.valueOf(name)).runTest();
}
}
}
/** tested method */
protected final Executable method;
protected final TestCase testCase;
/**
* Constructor.
*
* @param testCase object, that contains tested method and way to invoke it.
*/
protected CompilerWhiteBoxTest(TestCase testCase) {
Objects.requireNonNull(testCase);
System.out.println("TEST CASE:" + testCase.name());
method = testCase.getExecutable();
this.testCase = testCase;
}
/**
* Template method for testing. Prints tested method's info before
* {@linkplain #test()} and after {@linkplain #test()} or on thrown
* exception.
*
* @throws RuntimeException if method {@linkplain #test()} throws any
* exception
* @see #test()
*/
protected final void runTest() {
if (ManagementFactoryHelper.getCompilationMXBean() == null) {
System.err.println(
"Warning: test is not applicable in interpreted mode");
return;
}
System.out.println("at test's start:");
printInfo();
try {
test();
} catch (Exception e) {
System.out.printf("on exception '%s':", e.getMessage());
printInfo();
e.printStackTrace();
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
System.out.println("at test's end:");
printInfo();
}
/**
* Checks, that {@linkplain #method} is not compiled at the given compilation
* level or above.
*
* @param compLevel
*
* @throws RuntimeException if {@linkplain #method} is in compiler queue or
* is compiled, or if {@linkplain #method} has zero
* compilation level.
*/
protected final void checkNotCompiled(int compLevel) {
if (WHITE_BOX.isMethodQueuedForCompilation(method)) {
throw new RuntimeException(method + " must not be in queue");
}
if (WHITE_BOX.getMethodCompilationLevel(method, false) >= compLevel) {
throw new RuntimeException(method + " comp_level must be >= maxCompLevel");
}
if (WHITE_BOX.getMethodCompilationLevel(method, true) >= compLevel) {
throw new RuntimeException(method + " osr_comp_level must be >= maxCompLevel");
}
}
/**
* Checks, that {@linkplain #method} is not compiled.
*
* @throws RuntimeException if {@linkplain #method} is in compiler queue or
* is compiled, or if {@linkplain #method} has zero
* compilation level.
*/
protected final void checkNotCompiled() {
checkNotCompiled(true);
checkNotCompiled(false);
}
/**
* Checks, that {@linkplain #method} is not (OSR-)compiled.
*
* @param isOsr Check for OSR compilation if true
* @throws RuntimeException if {@linkplain #method} is in compiler queue or
* is compiled, or if {@linkplain #method} has zero
* compilation level.
*/
protected final void checkNotCompiled(boolean isOsr) {
waitBackgroundCompilation();
if (WHITE_BOX.isMethodQueuedForCompilation(method)) {
throw new RuntimeException(method + " must not be in queue");
}
if (WHITE_BOX.isMethodCompiled(method, isOsr)) {
throw new RuntimeException(method + " must not be " +
(isOsr ? "osr_" : "") + "compiled");
}
if (WHITE_BOX.getMethodCompilationLevel(method, isOsr) != 0) {
throw new RuntimeException(method + (isOsr ? " osr_" : " ") +
"comp_level must be == 0");
}
}
/**
* Checks, that {@linkplain #method} is compiled.
*
* @throws RuntimeException if {@linkplain #method} isn't in compiler queue
* and isn't compiled, or if {@linkplain #method}
* has nonzero compilation level
*/
protected final void checkCompiled() {
final long start = System.currentTimeMillis();
waitBackgroundCompilation();
if (WHITE_BOX.isMethodQueuedForCompilation(method)) {
System.err.printf("Warning: %s is still in queue after %dms%n",
method, System.currentTimeMillis() - start);
return;
}
if (!WHITE_BOX.isMethodCompiled(method, testCase.isOsr())) {
throw new RuntimeException(method + " must be "
+ (testCase.isOsr() ? "osr_" : "") + "compiled");
}
if (WHITE_BOX.getMethodCompilationLevel(method, testCase.isOsr())
== 0) {
throw new RuntimeException(method
+ (testCase.isOsr() ? " osr_" : " ")
+ "comp_level must be != 0");
}
}
protected final void deoptimize() {
WHITE_BOX.deoptimizeMethod(method, testCase.isOsr());
if (testCase.isOsr()) {
WHITE_BOX.deoptimizeMethod(method, false);
}
}
protected final int getCompLevel() {
NMethod nm = NMethod.get(method, testCase.isOsr());
return nm == null ? COMP_LEVEL_NONE : nm.comp_level;
}
protected final boolean isCompilable() {
return WHITE_BOX.isMethodCompilable(method, COMP_LEVEL_ANY,
testCase.isOsr());
}
protected final boolean isCompilable(int compLevel) {
return WHITE_BOX
.isMethodCompilable(method, compLevel, testCase.isOsr());
}
protected final void makeNotCompilable() {
WHITE_BOX.makeMethodNotCompilable(method, COMP_LEVEL_ANY,
testCase.isOsr());
}
protected final void makeNotCompilable(int compLevel) {
WHITE_BOX.makeMethodNotCompilable(method, compLevel, testCase.isOsr());
}
/**
* Waits for completion of background compilation of {@linkplain #method}.
*/
protected final void waitBackgroundCompilation() {
waitBackgroundCompilation(method);
}
/**
* Waits for completion of background compilation of the given executable.
*
* @param executable Executable
*/
protected static final void waitBackgroundCompilation(Executable executable) {
if (!BACKGROUND_COMPILATION) {
return;
}
final Object obj = new Object();
for (int i = 0; i < 10
&& WHITE_BOX.isMethodQueuedForCompilation(executable); ++i) {
synchronized (obj) {
try {
obj.wait(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* Prints information about {@linkplain #method}.
*/
protected final void printInfo() {
System.out.printf("%n%s:%n", method);
System.out.printf("\tcompilable:\t%b%n",
WHITE_BOX.isMethodCompilable(method, COMP_LEVEL_ANY, false));
System.out.printf("\tcompiled:\t%b%n",
WHITE_BOX.isMethodCompiled(method, false));
System.out.printf("\tcomp_level:\t%d%n",
WHITE_BOX.getMethodCompilationLevel(method, false));
System.out.printf("\tosr_compilable:\t%b%n",
WHITE_BOX.isMethodCompilable(method, COMP_LEVEL_ANY, true));
System.out.printf("\tosr_compiled:\t%b%n",
WHITE_BOX.isMethodCompiled(method, true));
System.out.printf("\tosr_comp_level:\t%d%n",
WHITE_BOX.getMethodCompilationLevel(method, true));
System.out.printf("\tin_queue:\t%b%n",
WHITE_BOX.isMethodQueuedForCompilation(method));
System.out.printf("compile_queues_size:\t%d%n%n",
WHITE_BOX.getCompileQueuesSize());
}
/**
* Executes testing.
*/
protected abstract void test() throws Exception;
/**
* Tries to trigger compilation of {@linkplain #method} by call
* {@linkplain TestCase#getCallable()} enough times.
*
* @return accumulated result
* @see #compile(int)
*/
protected final int compile() {
if (testCase.isOsr()) {
return compile(1);
} else {
return compile(THRESHOLD);
}
}
/**
* Tries to trigger compilation of {@linkplain #method} by call
* {@linkplain TestCase#getCallable()} specified times.
*
* @param count invocation count
* @return accumulated result
*/
protected final int compile(int count) {
int result = 0;
Integer tmp;
for (int i = 0; i < count; ++i) {
try {
tmp = testCase.getCallable().call();
} catch (Exception e) {
tmp = null;
}
result += tmp == null ? 0 : tmp;
}
if (IS_VERBOSE) {
System.out.println("method was invoked " + count + " times");
}
return result;
}
/**
* Utility interface provides tested method and object to invoke it.
*/
public interface TestCase {
/** the name of test case */
String name();
/** tested method */
Executable getExecutable();
/** object to invoke {@linkplain #getExecutable()} */
Callable<Integer> getCallable();
/** flag for OSR test case */
boolean isOsr();
}
/**
* @return {@code true} if the current test case is OSR and the mode is
* Xcomp, otherwise {@code false}
*/
protected boolean skipXcompOSR() {
boolean result = testCase.isOsr()
&& CompilerWhiteBoxTest.MODE.startsWith("compiled ");
if (result && IS_VERBOSE) {
System.err.printf("Warning: %s is not applicable in %s%n",
testCase.name(), CompilerWhiteBoxTest.MODE);
}
return result;
}
}
enum SimpleTestCase implements CompilerWhiteBoxTest.TestCase {
/** constructor test case */
CONSTRUCTOR_TEST(Helper.CONSTRUCTOR, Helper.CONSTRUCTOR_CALLABLE, false),
/** method test case */
METHOD_TEST(Helper.METHOD, Helper.METHOD_CALLABLE, false),
/** static method test case */
STATIC_TEST(Helper.STATIC, Helper.STATIC_CALLABLE, false),
/** OSR constructor test case */
OSR_CONSTRUCTOR_TEST(Helper.OSR_CONSTRUCTOR,
Helper.OSR_CONSTRUCTOR_CALLABLE, true),
/** OSR method test case */
OSR_METHOD_TEST(Helper.OSR_METHOD, Helper.OSR_METHOD_CALLABLE, true),
/** OSR static method test case */
OSR_STATIC_TEST(Helper.OSR_STATIC, Helper.OSR_STATIC_CALLABLE, true);
private final Executable executable;
private final Callable<Integer> callable;
private final boolean isOsr;
private SimpleTestCase(Executable executable, Callable<Integer> callable,
boolean isOsr) {
this.executable = executable;
this.callable = callable;
this.isOsr = isOsr;
}
@Override
public Executable getExecutable() {
return executable;
}
@Override
public Callable<Integer> getCallable() {
return callable;
}
@Override
public boolean isOsr() {
return isOsr;
}
private static class Helper {
private static final Callable<Integer> CONSTRUCTOR_CALLABLE
= new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Helper(1337).hashCode();
}
};
private static final Callable<Integer> METHOD_CALLABLE
= new Callable<Integer>() {
private final Helper helper = new Helper();
@Override
public Integer call() throws Exception {
return helper.method();
}
};
private static final Callable<Integer> STATIC_CALLABLE
= new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return staticMethod();
}
};
private static final Callable<Integer> OSR_CONSTRUCTOR_CALLABLE
= new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Helper(null, CompilerWhiteBoxTest.BACKEDGE_THRESHOLD).hashCode();
}
};
private static final Callable<Integer> OSR_METHOD_CALLABLE
= new Callable<Integer>() {
private final Helper helper = new Helper();
@Override
public Integer call() throws Exception {
return helper.osrMethod(CompilerWhiteBoxTest.BACKEDGE_THRESHOLD);
}
};
private static final Callable<Integer> OSR_STATIC_CALLABLE
= new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return osrStaticMethod(CompilerWhiteBoxTest.BACKEDGE_THRESHOLD);
}
};
private static final Constructor CONSTRUCTOR;
private static final Constructor OSR_CONSTRUCTOR;
private static final Method METHOD;
private static final Method STATIC;
private static final Method OSR_METHOD;
private static final Method OSR_STATIC;
static {
try {
CONSTRUCTOR = Helper.class.getDeclaredConstructor(int.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(
"exception on getting method Helper.<init>(int)", e);
}
try {
OSR_CONSTRUCTOR = Helper.class.getDeclaredConstructor(
Object.class, long.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(
"exception on getting method Helper.<init>(Object, long)", e);
}
METHOD = getMethod("method");
STATIC = getMethod("staticMethod");
OSR_METHOD = getMethod("osrMethod", long.class);
OSR_STATIC = getMethod("osrStaticMethod", long.class);
}
private static Method getMethod(String name, Class<?>... parameterTypes) {
try {
return Helper.class.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(
"exception on getting method Helper." + name, e);
}
}
private static int staticMethod() {
return 1138;
}
private int method() {
return 42;
}
/**
* Deoptimizes all non-osr versions of the given executable after
* compilation finished.
*
* @param e Executable
* @throws Exception
*/
private static void waitAndDeoptimize(Executable e) {
CompilerWhiteBoxTest.waitBackgroundCompilation(e);
if (WhiteBox.getWhiteBox().isMethodQueuedForCompilation(e)) {
throw new RuntimeException(e + " must not be in queue");
}
// Deoptimize non-osr versions of executable
WhiteBox.getWhiteBox().deoptimizeMethod(e, false);
}
/**
* Executes the method multiple times to make sure we have
* enough profiling information before triggering an OSR
* compilation. Otherwise the C2 compiler may add uncommon traps.
*
* @param m Method to be executed
* @return Number of times the method was executed
* @throws Exception
*/
private static int warmup(Method m) throws Exception {
waitAndDeoptimize(m);
Helper helper = new Helper();
int result = 0;
for (long i = 0; i < CompilerWhiteBoxTest.THRESHOLD; ++i) {
result += (int)m.invoke(helper, 1);
}
// Wait to make sure OSR compilation is not blocked by
// non-OSR compilation in the compile queue
CompilerWhiteBoxTest.waitBackgroundCompilation(m);
return result;
}
/**
* Executes the constructor multiple times to make sure we
* have enough profiling information before triggering an OSR
* compilation. Otherwise the C2 compiler may add uncommon traps.
*
* @param c Constructor to be executed
* @return Number of times the constructor was executed
* @throws Exception
*/
private static int warmup(Constructor c) throws Exception {
waitAndDeoptimize(c);
int result = 0;
for (long i = 0; i < CompilerWhiteBoxTest.THRESHOLD; ++i) {
result += c.newInstance(null, 1).hashCode();
}
// Wait to make sure OSR compilation is not blocked by
// non-OSR compilation in the compile queue
CompilerWhiteBoxTest.waitBackgroundCompilation(c);
return result;
}
private static int osrStaticMethod(long limit) throws Exception {
int result = 0;
if (limit != 1) {
result = warmup(OSR_STATIC);
}
// Trigger osr compilation
for (long i = 0; i < limit; ++i) {
result += staticMethod();
}
return result;
}
private int osrMethod(long limit) throws Exception {
int result = 0;
if (limit != 1) {
result = warmup(OSR_METHOD);
}
// Trigger osr compilation
for (long i = 0; i < limit; ++i) {
result += method();
}
return result;
}
private final int x;
// for method and OSR method test case
public Helper() {
x = 0;
}
// for OSR constructor test case
private Helper(Object o, long limit) throws Exception {
int result = 0;
if (limit != 1) {
result = warmup(OSR_CONSTRUCTOR);
}
// Trigger osr compilation
for (long i = 0; i < limit; ++i) {
result += method();
}
x = result;
}
// for constructor test case
private Helper(int x) {
this.x = x;
}
@Override
public int hashCode() {
return x;
}
}
}