package org.openjdk.jmh.runner;
import org.openjdk.jmh.util.FileUtils;
import org.openjdk.jmh.util.JDKVersion;
import java.io.*;
import java.util.*;
public class CompilerHints extends AbstractResourceReader {
public static final String LIST = "/META-INF/CompilerHints";
static final String[] HINT_COMPATIBLE_JVMS = { "OpenJDK", "HotSpot", "GraalVM" };
static final String JVM_ZING = "Zing";
private static volatile CompilerHints defaultList;
private static volatile String hintsFile;
private final Set<String> hints;
static final String XX_COMPILE_COMMAND_FILE = "-XX:CompileCommandFile=";
static final String BLACKHOLE_PROP_NAME = "jmh.blackhole.mode";
public static CompilerHints defaultList() {
if (defaultList == null) {
defaultList = fromResource(LIST);
}
return defaultList;
}
public static String hintsFile() {
if (hintsFile == null) {
try {
final Set<String> defaultHints = defaultList().get();
List<String> hints = new ArrayList<>(defaultHints.size() + 2);
hints.add("quiet");
BlackholeMode bhMode = blackholeMode();
if (bhMode.shouldBlackhole()) {
hints.add("blackhole,org/openjdk/jmh/infra/Blackhole.consume");
}
if (bhMode.shouldNotInline()) {
hints.add("dontinline,org/openjdk/jmh/infra/Blackhole.consume");
}
hints.add("dontinline,org/openjdk/jmh/infra/Blackhole.consumeCPU");
hints.addAll(defaultHints);
hintsFile = FileUtils.createTempFileWithLines("compilecommand", hints);
} catch (IOException e) {
throw new IllegalStateException("Error creating compiler hints file", e);
}
}
return hintsFile;
}
public static CompilerHints fromResource(String resource) {
return new CompilerHints(null, resource);
}
public static CompilerHints fromFile(String file) {
return new CompilerHints(file, null);
}
private CompilerHints(String file, String resource) {
super(file, resource, null);
hints = Collections.unmodifiableSet(read());
}
private static boolean isHintCompatibleVM() {
String name = System.getProperty("java.vm.name");
for (String vmName : HINT_COMPATIBLE_JVMS) {
if (name.contains(vmName)) {
return true;
}
}
if (name.contains(JVM_ZING)) {
String version = System.getProperty("java.version");
try {
String[] versionDigits = version.substring(version.indexOf('_') + 1).split("\\.");
if (Integer.parseInt(versionDigits[0]) > 5) {
return true;
} else if (Integer.parseInt(versionDigits[0]) == 5 && Integer.parseInt(versionDigits[1]) >= 10) {
return true;
}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
System.err.println("ERROR: Zing version format does not match 1.*.0-zing_*.*.*.*");
}
}
return false;
}
public Set<String> get() {
return hints;
}
private Set<String> read() {
Set<String> result = new TreeSet<>();
try {
for (Reader r : getReaders()) {
try (BufferedReader reader = new BufferedReader(r)) {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (line.startsWith("#")) {
continue;
}
if (line.trim().isEmpty()) {
continue;
}
result.add(line);
}
}
}
} catch (IOException ex) {
throw new RuntimeException("Error reading compiler hints", ex);
}
return result;
}
public static List<String> getCompileCommandFiles(List<String> command){
List<String> compileCommandFiles = new ArrayList<>();
for (String cmdLineWord : command) {
if (cmdLineWord.startsWith(XX_COMPILE_COMMAND_FILE)) {
compileCommandFiles.add(cmdLineWord.substring(XX_COMPILE_COMMAND_FILE.length()));
}
}
return compileCommandFiles;
}
public static void addCompilerHints(List<String> command) {
if (!isHintCompatibleVM()) {
System.err.println("WARNING: Not a HotSpot compiler command compatible VM (\""
+ System.getProperty("java.vm.name") + "-" + System.getProperty("java.version")
+ "\"), compilerHints are disabled.");
return;
}
List<String> hintFiles = new ArrayList<>();
hintFiles.add(hintsFile());
removeCompileCommandFiles(command, hintFiles);
if (hintFiles.size() == 1) {
File hotspotCompilerFile = new File(".hotspot_compiler");
if (hotspotCompilerFile.exists()) {
hintFiles.add(hotspotCompilerFile.getAbsolutePath());
}
}
if (needsDiagnosticUnlock()) {
command.add("-XX:+UnlockDiagnosticVMOptions");
}
command.add(CompilerHints.XX_COMPILE_COMMAND_FILE + mergeHintFiles(hintFiles));
}
private static void removeCompileCommandFiles(List<String> command, List<String> compileCommandFiles){
Iterator<String> iterator = command.iterator();
while (iterator.hasNext()) {
String cmdLineWord = iterator.next();
if(cmdLineWord.startsWith(XX_COMPILE_COMMAND_FILE)) {
compileCommandFiles.add(cmdLineWord.substring(XX_COMPILE_COMMAND_FILE.length()));
iterator.remove();
}
}
}
private static String mergeHintFiles(List<String> compileCommandFiles) {
if (compileCommandFiles.size() == 1) {
return compileCommandFiles.get(0);
}
try {
Set<String> hints = new TreeSet<>();
for(String file : compileCommandFiles) {
hints.addAll(fromFile(file).get());
}
return FileUtils.createTempFileWithLines("compilecommand", hints);
} catch (IOException e) {
throw new IllegalStateException("Error merging compiler hints files", e);
}
}
private static BlackholeMode blackholeMode() {
String prop = System.getProperty(BLACKHOLE_PROP_NAME);
if (prop != null) {
try {
return BlackholeMode.valueOf(prop);
} catch (IllegalArgumentException iae) {
throw new IllegalStateException("Unknown Blackhole mode: " + prop);
}
}
return BlackholeMode.FULL_DONTINLINE;
}
public static void printBlackholeMode(PrintStream out) {
BlackholeMode mode = blackholeMode();
out.print("# JMH blackhole mode: " + mode.desc());
if (!mode.shouldBlackhole() && compilerBlackholeAvailable()) {
out.print("; set -D" + BLACKHOLE_PROP_NAME + "=" + BlackholeMode.COMPILER.name() + " to get compiler-assisted ones");
}
out.println();
}
private static boolean compilerBlackholeAvailable() {
return JDKVersion.parseMajor(System.getProperty("java.version")) >= 16;
}
private static boolean needsDiagnosticUnlock() {
return blackholeMode() == BlackholeMode.COMPILER;
}
private enum BlackholeMode {
COMPILER(true, false, "compiler-assisted blackhole"),
FULL_DONTINLINE(false, true, "full blackhole + dont-inline hint"),
FULL(false, false, "full blackhole"),
;
private final boolean shouldBlackhole;
private final boolean shouldNotInline;
private final String desc;
BlackholeMode(boolean shouldBlackhole, boolean shouldNotInline, String desc) {
this.shouldBlackhole = shouldBlackhole;
this.shouldNotInline = shouldNotInline;
this.desc = desc;
}
public boolean shouldBlackhole() {
return shouldBlackhole;
}
public boolean shouldNotInline() {
return shouldNotInline;
}
public String desc() { return desc; }
}
}