8254108: ciReplay: Support incremental inlining

Reviewed-by: dlong, thartmann
This commit is contained in:
Christian Hagedorn 2021-11-23 15:22:11 +00:00
parent 64bdc84390
commit 38802ad56a
9 changed files with 495 additions and 178 deletions

View File

@ -103,6 +103,7 @@ typedef struct _ciInlineRecord {
int _inline_depth;
int _inline_bci;
bool _inline_late;
} ciInlineRecord;
class CompileReplay;
@ -720,7 +721,7 @@ class CompileReplay : public StackObj {
return NULL;
}
// compile <klass> <name> <signature> <entry_bci> <comp_level> inline <count> (<depth> <bci> <klass> <name> <signature>)*
// compile <klass> <name> <signature> <entry_bci> <comp_level> inline <count> (<depth> <bci> <inline_late> <klass> <name> <signature>)*
void process_compile(TRAPS) {
Method* method = parse_method(CHECK);
if (had_error()) return;
@ -762,11 +763,19 @@ class CompileReplay : public StackObj {
if (had_error()) {
break;
}
int inline_late = 0;
if (_version >= 2) {
inline_late = parse_int("inline_late");
if (had_error()) {
break;
}
}
Method* inl_method = parse_method(CHECK);
if (had_error()) {
break;
}
new_ciInlineRecord(inl_method, bci, depth);
new_ciInlineRecord(inl_method, bci, depth, inline_late);
}
}
if (_imethod != NULL) {
@ -1227,13 +1236,14 @@ class CompileReplay : public StackObj {
}
// Create and initialize a record for a ciInlineRecord
ciInlineRecord* new_ciInlineRecord(Method* method, int bci, int depth) {
ciInlineRecord* new_ciInlineRecord(Method* method, int bci, int depth, int inline_late) {
ciInlineRecord* rec = NEW_RESOURCE_OBJ(ciInlineRecord);
rec->_klass_name = method->method_holder()->name()->as_utf8();
rec->_method_name = method->name()->as_utf8();
rec->_signature = method->signature()->as_utf8();
rec->_inline_bci = bci;
rec->_inline_depth = depth;
rec->_inline_late = inline_late;
_ci_inline_records->append(rec);
return rec;
}
@ -1470,23 +1480,33 @@ bool ciReplay::should_not_inline(ciMethod* method) {
return replay_state->find_ciMethodRecord(method->get_Method()) == NULL;
}
bool ciReplay::should_inline(void* data, ciMethod* method, int bci, int inline_depth) {
bool ciReplay::should_inline(void* data, ciMethod* method, int bci, int inline_depth, bool& should_delay) {
if (data != NULL) {
GrowableArray<ciInlineRecord*>* records = (GrowableArray<ciInlineRecord*>*)data;
GrowableArray<ciInlineRecord*>* records = (GrowableArray<ciInlineRecord*>*)data;
VM_ENTRY_MARK;
// Inline record are ordered by bci and depth.
return CompileReplay::find_ciInlineRecord(records, method->get_Method(), bci, inline_depth) != NULL;
ciInlineRecord* record = CompileReplay::find_ciInlineRecord(records, method->get_Method(), bci, inline_depth);
if (record == NULL) {
return false;
}
should_delay = record->_inline_late;
return true;
} else if (replay_state != NULL) {
VM_ENTRY_MARK;
// Inline record are ordered by bci and depth.
return replay_state->find_ciInlineRecord(method->get_Method(), bci, inline_depth) != NULL;
ciInlineRecord* record = replay_state->find_ciInlineRecord(method->get_Method(), bci, inline_depth);
if (record == NULL) {
return false;
}
should_delay = record->_inline_late;
return true;
}
return false;
}
bool ciReplay::should_not_inline(void* data, ciMethod* method, int bci, int inline_depth) {
if (data != NULL) {
GrowableArray<ciInlineRecord*>* records = (GrowableArray<ciInlineRecord*>*)data;
GrowableArray<ciInlineRecord*>* records = (GrowableArray<ciInlineRecord*>*)data;
VM_ENTRY_MARK;
// Inline record are ordered by bci and depth.
return CompileReplay::find_ciInlineRecord(records, method->get_Method(), bci, inline_depth) == NULL;

View File

@ -121,7 +121,7 @@ class ciReplay {
static bool is_loaded(Method* method);
static bool should_not_inline(ciMethod* method);
static bool should_inline(void* data, ciMethod* method, int bci, int inline_depth);
static bool should_inline(void* data, ciMethod* method, int bci, int inline_depth, bool& should_delay);
static bool should_not_inline(void* data, ciMethod* method, int bci, int inline_depth);
#endif
@ -135,6 +135,7 @@ class ciReplay {
// 0: legacy (no version number)
// 1: first instanceKlass sets protection domain (8275868)
// replace current_mileage with invocation_count (8276095)
#define REPLAY_VERSION 1 // current version, bump up for incompatible changes
// 2: incremental inlining support (8254108)
#define REPLAY_VERSION 2 // current version, bump up for incompatible changes
#endif // SHARE_CI_CIREPLAY_HPP

View File

@ -46,6 +46,7 @@ InlineTree::InlineTree(Compile* c,
C(c),
_caller_jvms(caller_jvms),
_method(callee),
_late_inline(false),
_caller_tree((InlineTree*) caller_tree),
_count_inline_bcs(method()->code_size_for_inlining()),
_max_inline_level(max_inline_level),
@ -113,7 +114,7 @@ static bool is_unboxing_method(ciMethod* callee_method, Compile* C) {
// positive filter: should callee be inlined?
bool InlineTree::should_inline(ciMethod* callee_method, ciMethod* caller_method,
int caller_bci, ciCallProfile& profile) {
int caller_bci, NOT_PRODUCT_ARG(bool& should_delay) ciCallProfile& profile) {
// Allows targeted inlining
if (C->directive()->should_inline(callee_method)) {
set_msg("force inline by CompileCommand");
@ -128,9 +129,13 @@ bool InlineTree::should_inline(ciMethod* callee_method, ciMethod* caller_method,
}
#ifndef PRODUCT
int inline_depth = inline_level()+1;
if (ciReplay::should_inline(C->replay_inline_data(), callee_method, caller_bci, inline_depth)) {
set_msg("force inline by ciReplay");
int inline_depth = inline_level() + 1;
if (ciReplay::should_inline(C->replay_inline_data(), callee_method, caller_bci, inline_depth, should_delay)) {
if (should_delay) {
set_msg("force (incremental) inline by ciReplay");
} else {
set_msg("force inline by ciReplay");
}
_forced_inline = true;
return true;
}
@ -194,7 +199,7 @@ bool InlineTree::should_inline(ciMethod* callee_method, ciMethod* caller_method,
// negative filter: should callee NOT be inlined?
bool InlineTree::should_not_inline(ciMethod* callee_method, ciMethod* caller_method,
int caller_bci, ciCallProfile& profile) {
int caller_bci, NOT_PRODUCT_ARG(bool& should_delay) ciCallProfile& profile) {
const char* fail_msg = NULL;
// First check all inlining restrictions which are required for correctness
@ -232,9 +237,13 @@ bool InlineTree::should_not_inline(ciMethod* callee_method, ciMethod* caller_met
}
#ifndef PRODUCT
int inline_depth = inline_level()+1;
if (ciReplay::should_inline(C->replay_inline_data(), callee_method, caller_bci, inline_depth)) {
set_msg("force inline by ciReplay");
int inline_depth = inline_level() + 1;
if (ciReplay::should_inline(C->replay_inline_data(), callee_method, caller_bci, inline_depth, should_delay)) {
if (should_delay) {
set_msg("force (incremental) inline by ciReplay");
} else {
set_msg("force inline by ciReplay");
}
return false;
}
@ -369,10 +378,13 @@ bool InlineTree::try_to_inline(ciMethod* callee_method, ciMethod* caller_method,
}
_forced_inline = false; // Reset
if (!should_inline(callee_method, caller_method, caller_bci, profile)) {
// 'should_delay' can be overridden during replay compilation
if (!should_inline(callee_method, caller_method, caller_bci, NOT_PRODUCT_ARG(should_delay) profile)) {
return false;
}
if (should_not_inline(callee_method, caller_method, caller_bci, profile)) {
// 'should_delay' can be overridden during replay compilation
if (should_not_inline(callee_method, caller_method, caller_bci, NOT_PRODUCT_ARG(should_delay) profile)) {
return false;
}
@ -557,9 +569,8 @@ void InlineTree::print_inlining(ciMethod* callee_method, int caller_bci,
//------------------------------ok_to_inline-----------------------------------
bool InlineTree::ok_to_inline(ciMethod* callee_method, JVMState* jvms, ciCallProfile& profile,
bool& should_delay) {
assert(callee_method != NULL, "caller checks for optimized virtual!");
assert(!should_delay, "should be initialized to false");
#ifdef ASSERT
assert(callee_method != NULL, "caller checks for optimized virtual!");
// Make sure the incoming jvms has the same information content as me.
// This means that we can eventually make this whole class AllStatic.
if (jvms->caller() == NULL) {
@ -595,7 +606,11 @@ bool InlineTree::ok_to_inline(ciMethod* callee_method, JVMState* jvms, ciCallPro
set_msg("inline (hot)");
}
print_inlining(callee_method, caller_bci, caller_method, true /* success */);
build_inline_tree_for_callee(callee_method, jvms, caller_bci);
InlineTree* callee_tree = build_inline_tree_for_callee(callee_method, jvms, caller_bci);
if (should_delay) {
// Record late inlining decision in order to dump it for compiler replay
callee_tree->set_late_inline();
}
return true;
} else {
// Do not inline
@ -700,7 +715,7 @@ int InlineTree::count() const {
}
void InlineTree::dump_replay_data(outputStream* out) {
out->print(" %d %d ", inline_level(), caller_bci());
out->print(" %d %d %d ", inline_level(), caller_bci(), _late_inline);
method()->dump_name_as_ascii(out);
for (int i = 0 ; i < _subtrees.length(); i++) {
_subtrees.at(i)->dump_replay_data(out);

View File

@ -164,7 +164,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
// Try inlining a bytecoded method:
if (!call_does_dispatch) {
InlineTree* ilt = InlineTree::find_subtree_from_root(this->ilt(), jvms->caller(), jvms->method());
bool should_delay = false;
bool should_delay = AlwaysIncrementalInline;
if (ilt->ok_to_inline(callee, jvms, profile, should_delay)) {
CallGenerator* cg = CallGenerator::for_inline(callee, expected_uses);
// For optimized virtual calls assert at runtime that receiver object
@ -189,7 +189,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
return CallGenerator::for_boxing_late_inline(callee, cg);
} else if (should_delay_vector_reboxing_inlining(callee, jvms)) {
return CallGenerator::for_vector_reboxing_late_inline(callee, cg);
} else if ((should_delay || AlwaysIncrementalInline)) {
} else if (should_delay) {
return CallGenerator::for_late_inline(callee, cg);
} else {
return cg;

View File

@ -46,6 +46,7 @@ class InlineTree : public ResourceObj {
Compile* C; // cache
JVMState* _caller_jvms; // state of caller
ciMethod* _method; // method being called by the caller_jvms
bool _late_inline; // method is inlined incrementally
InlineTree* _caller_tree;
uint _count_inline_bcs; // Accumulated count of inlined bytecodes
const int _max_inline_level; // the maximum inline level for this sub-tree (may be adjusted)
@ -75,10 +76,12 @@ protected:
bool should_inline(ciMethod* callee_method,
ciMethod* caller_method,
int caller_bci,
NOT_PRODUCT_ARG(bool& should_delay)
ciCallProfile& profile);
bool should_not_inline(ciMethod* callee_method,
ciMethod* caller_method,
int caller_bci,
NOT_PRODUCT_ARG(bool& should_delay)
ciCallProfile& profile);
bool is_not_reached(ciMethod* callee_method,
ciMethod* caller_method,
@ -112,6 +115,10 @@ public:
// The call_method is an optimized virtual method candidate otherwise.
bool ok_to_inline(ciMethod *call_method, JVMState* caller_jvms, ciCallProfile& profile, bool& should_delay);
void set_late_inline() {
_late_inline = true;
}
// Information about inlined method
JVMState* caller_jvms() const { return _caller_jvms; }
ciMethod *method() const { return _method; }

View File

@ -24,10 +24,17 @@
package compiler.ciReplay;
import compiler.whitebox.CompilerWhiteBoxTest;
import java.io.IOException;
import java.io.File;
import jdk.test.lib.Asserts;
import jdk.test.lib.Platform;
import jdk.test.lib.Utils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.util.CoreUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -36,14 +43,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import jdk.test.lib.Platform;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.Asserts;
import jdk.test.lib.Utils;
import jdk.test.lib.util.CoreUtils;
public abstract class CiReplayBase {
public static final String REPLAY_FILE_NAME = "test_replay.txt";
@ -296,4 +295,41 @@ public abstract class CiReplayBase {
throw new Error("Can't create process builder: " + t, t);
}
}
protected void removeVersionFromReplayFile() {
setNewVersionLineInReplayFile(null);
}
protected void setNewVersionInReplayFile(int newVersionNumber) {
setNewVersionLineInReplayFile("version " + newVersionNumber);
}
private void setNewVersionLineInReplayFile(String firstLineString) {
List<String> newLines = new ArrayList<>();
Path replayFilePath = Paths.get(getReplayFileName());
try (var br = Files.newBufferedReader(replayFilePath)) {
String line;
boolean firstLine = true;
while ((line = br.readLine()) != null) {
if (firstLine) {
firstLine = false;
Asserts.assertTrue(line.startsWith("version"), "version number must exist in a proper replay file");
if (firstLineString != null) {
newLines.add(firstLineString);
}
// Else: Remove first line by skipping it.
} else {
newLines.add(line);
}
}
Asserts.assertFalse(firstLine, replayFilePath + " should not be empty");
} catch (IOException e) {
throw new Error("Failed to read replay data: " + e, e);
}
try {
Files.write(replayFilePath, newLines, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException e) {
throw new Error("Failed to write replay data: " + e, e);
}
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) 2021, 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.
*/
package compiler.ciReplay;
import jdk.test.lib.Asserts;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class InliningBase extends DumpReplayBase {
public static final String LOG_FILE_NORMAL = "hotspot_normal.log";
public static final String LOG_FILE_REPLAY = "hotspot_replay.log";
protected final String[] commandLineReplay;
protected final List<String> commandLineNormal;
protected final Class<?> testClass;
protected InliningBase(Class<?> testClass) {
this.testClass = testClass;
commandLineNormal = new ArrayList<>(List.of("-XX:LogFile=" + LOG_FILE_NORMAL + "", "-XX:+LogCompilation", "-XX:-TieredCompilation",
"-XX:CompileCommand=exclude," + testClass.getName() + "::main",
"-XX:CompileCommand=option," + testClass.getName() + "::test,bool,PrintInlining,true"));
commandLineReplay = new String[]
{"-XX:LogFile=" + LOG_FILE_REPLAY, "-XX:+LogCompilation",
"-XX:CompileCommand=option," + testClass.getName() + "::test,bool,PrintInlining,true"};
}
protected void runTest() {
runTest(commandLineNormal.toArray(new String[0]));
}
@Override
public String getTestClass() {
return testClass.getName();
}
@Override
public void cleanup() {
super.cleanup();
remove(LOG_FILE_NORMAL);
remove(LOG_FILE_REPLAY);
}
static class InlineEntry {
String klass;
String method;
String reason;
public InlineEntry(String klass, String method, String reason) {
this.klass = klass;
this.method = method;
this.reason = reason;
}
public boolean isNormalInline() {
return reason.equals("inline (hot)");
}
public boolean isForcedByReplay() {
return reason.equals("force inline by ciReplay");
}
public boolean isDisallowedByReplay() {
return reason.equals("disallowed by ciReplay");
}
public boolean isUnloadedSignatureClasses() {
return reason.equals("unloaded signature classes");
}
public boolean isForcedIncrementalInlineByReplay() {
return reason.equals("force (incremental) inline by ciReplay");
}
public boolean isForcedInline() {
return reason.equals("force inline by annotation");
}
public boolean isTooDeep() {
return reason.equals("inlining too deep");
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof InlineEntry)) {
return false;
}
InlineEntry e = (InlineEntry)other;
return klass.equals(e.klass) && method.equals(e.method);
}
public boolean compare(String klass, String method, boolean kind) {
return this.klass.equals(klass) && this.method.equals(method) && kind;
}
}
protected static List<InlineEntry> parseLogFile(String logFile, String rootMethod, String nmethodMatch, int inlineeCount) {
String nmethodStart = "<nmethod";
List<InlineEntry> inlinees = new ArrayList<>();
int foundLines = 0;
try (var br = Files.newBufferedReader(Paths.get(logFile))) {
String line;
boolean nmethodLine = false;
boolean inlinineLine = false;
while ((line = br.readLine()) != null) {
if (nmethodLine) {
// Ignore other entries which could be in between nmethod entry and inlining statements
if (line.startsWith(" ")) {
inlinineLine = true;
Pattern p = Pattern.compile("(\\S+)::(\\S+).*bytes\\)\s+(.*)");
Matcher matcher = p.matcher(line);
Asserts.assertTrue(matcher.find(), "must find inlinee method");
inlinees.add(new InlineEntry(matcher.group(1), matcher.group(2), matcher.group(3).trim()));
foundLines++;
} else if (inlinineLine) {
Asserts.assertEQ(foundLines, inlineeCount, "did not find all inlinees");
return inlinees;
}
} else {
nmethodLine = line.startsWith(nmethodStart) && line.contains(nmethodMatch);
if (nmethodLine) {
Asserts.assertTrue(line.contains(rootMethod), "should only dump inline information for " + rootMethod);
}
}
}
} catch (IOException e) {
throw new Error("Failed to read " + logFile + " data: " + e, e);
}
Asserts.fail("Should have found inlinees");
return inlinees;
}
protected void verifyLists(List<InlineEntry> inlineesNormal, List<InlineEntry> inlineesReplay, int expectedSize) {
if (!inlineesNormal.equals(inlineesReplay)) {
System.err.println("Normal entries:");
inlineesNormal.forEach(System.err::println);
System.err.println("Replay entries:");
inlineesReplay.forEach(System.err::println);
Asserts.fail("different inlining decision in normal run vs. replay run");
}
Asserts.assertEQ(expectedSize, inlineesNormal.size(), "unexpected number of inlinees found");
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright (c) 2021, 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.
*/
/*
* @test
* @bug 8254108
* @library / /test/lib
* @summary Testing of ciReplay with incremental inlining.
* @requires vm.flightRecorder != true & vm.compMode != "Xint" & vm.compMode != "Xcomp" & vm.debug == true & vm.compiler2.enabled
* @modules java.base/jdk.internal.misc
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
* compiler.ciReplay.TestIncrementalInlining
*/
package compiler.ciReplay;
import jdk.test.lib.Asserts;
import jdk.test.lib.Utils;
import jdk.test.whitebox.WhiteBox;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestIncrementalInlining extends InliningBase {
private List<InlineEntry> inlineesNormal;
private List<InlineEntry> inlineesReplay;
public static void main(String[] args) {
new TestIncrementalInlining();
}
TestIncrementalInlining() {
super(IncrementalInliningTest.class);
// Enable Whitebox access for test VM.
commandLineNormal.add("-Dtest.jdk=" + Utils.TEST_JDK);
commandLineNormal.add("-cp");
commandLineNormal.add(Utils.TEST_CLASS_PATH);
commandLineNormal.add("-Xbootclasspath/a:.");
commandLineNormal.add("-XX:+UnlockDiagnosticVMOptions");
commandLineNormal.add("-XX:+WhiteBoxAPI");
commandLineNormal.add("-XX:MaxInlineLevel=2");
commandLineNormal.add("-XX:-AlwaysIncrementalInline");
runTest();
}
@Override
public void testAction() {
positiveTest(commandLineReplay);
inlineesNormal = parseLogFile(LOG_FILE_NORMAL, getTestClass() + " " + "test", "compile_id='" + getCompileIdFromFile(getReplayFileName()), 5);
verify(true);
// Incremental inlining is supported in version 2+
// Test replay file version 1.
removeIncrementalInlineInfo();
setNewVersionInReplayFile(1);
positiveTest(commandLineReplay);
verify(false);
// Test replay file without version.
removeVersionFromReplayFile();
positiveTest(commandLineReplay);
verify(false);
}
private void verify(boolean isNewFormat) {
inlineesReplay = parseLogFile(LOG_FILE_REPLAY, getTestClass() + " " + "test", "test ()V", 5);
verifyLists(inlineesNormal, inlineesReplay, 5);
checkInlining(isNewFormat);
}
// Check if inlining is done correctly in ciReplay.
private void checkInlining(boolean isNewFormat) {
String klass = getTestClass();
Asserts.assertTrue(inlineesNormal.get(0).compare(klass, "level0", inlineesNormal.get(0).isForcedInline()));
Asserts.assertTrue(inlineesReplay.get(0).compare(klass, "level0", inlineesReplay.get(0).isForcedByReplay()));
Asserts.assertTrue(inlineesNormal.get(1).compare(klass, "level1", inlineesNormal.get(1).isNormalInline()));
Asserts.assertTrue(inlineesReplay.get(1).compare(klass, "level1", inlineesReplay.get(1).isForcedByReplay()));
Asserts.assertTrue(inlineesNormal.get(2).compare(klass, "level2", inlineesNormal.get(2).isForcedInline()));
Asserts.assertTrue(inlineesReplay.get(2).compare(klass, "level2", inlineesReplay.get(2).isForcedByReplay()));
Asserts.assertTrue(inlineesNormal.get(3).compare(klass, "late", inlineesNormal.get(3).isForcedInline()));
Asserts.assertTrue(inlineesReplay.get(3).compare(klass, "late", isNewFormat ?
inlineesReplay.get(3).isForcedIncrementalInlineByReplay()
: inlineesReplay.get(3).isForcedByReplay()));
Asserts.assertTrue(inlineesNormal.get(4).compare(klass, "level4", inlineesNormal.get(4).isTooDeep()));
Asserts.assertTrue(inlineesReplay.get(4).compare(klass, "level4", inlineesReplay.get(4).isDisallowedByReplay()));
}
private void removeIncrementalInlineInfo() {
try {
Path replayFilePath = Paths.get(getReplayFileName());
List<String> replayContent = Files.readAllLines(replayFilePath);
for (int i = 0; i < replayContent.size(); i++) {
String line = replayContent.get(i);
if (line.startsWith("compile ")) {
int lastIndex = 0;
StringBuilder newLine = new StringBuilder();
Pattern p = Pattern.compile("(\\d (-?\\d)) \\d compiler");
Matcher m = p.matcher(line);
boolean firstMatch = true;
while (m.find()) {
newLine.append(line, lastIndex, m.start())
.append(m.group(1))
.append(" compiler");
lastIndex = m.end();
String bci = m.group(2);
Asserts.assertTrue(firstMatch ? bci.equals("-1") : bci.equals("0"), "only root has -1");
firstMatch = false;
}
Asserts.assertLessThan(lastIndex, line.length(), "not reached end of line, yet");
newLine.append(line, lastIndex, line.length());
replayContent.set(i, newLine.toString());
}
}
Files.write(replayFilePath, replayContent, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException ioe) {
throw new Error("Failed to read/write replay data: " + ioe, ioe);
}
}
}
class IncrementalInliningTest {
private static final WhiteBox WB = WhiteBox.getWhiteBox();
private static String s;
public static void main(String[] args) throws NoSuchMethodException {
WB.testSetForceInlineMethod(IncrementalInliningTest.class.getDeclaredMethod("level0"), true);
WB.testSetForceInlineMethod(IncrementalInliningTest.class.getDeclaredMethod("level2"), true);
WB.testSetForceInlineMethod(IncrementalInliningTest.class.getDeclaredMethod("late"), true);
for (int i = 0; i < 10000; i++) {
test();
}
}
private static void test() {
level0();
}
public static void level0() {
level1();
}
public static void level1() {
level2();
}
public static void level2() {
late();
}
// Reached max inline level but forced to be inlined -> inline late.
public static void late() {
level4();
}
// Reached max inline level and not forced to be inlined -> no inline.
public static void level4() {
s = "HelloWorld";
}
}

View File

@ -28,173 +28,49 @@
* @summary Testing that ciReplay inlining does not fail with unresolved signature classes.
* @requires vm.flightRecorder != true & vm.compMode != "Xint" & vm.compMode != "Xcomp" & vm.debug == true & vm.compiler2.enabled
* @modules java.base/jdk.internal.misc
* @build sun.hotspot.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
* compiler.ciReplay.TestInliningProtectionDomain
* @run driver compiler.ciReplay.TestInliningProtectionDomain
*/
package compiler.ciReplay;
import jdk.test.lib.Asserts;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestInliningProtectionDomain extends DumpReplayBase {
public static final String LOG_FILE_NORMAL = "hotspot_normal.log";
public static final String LOG_FILE_REPLAY = "hotspot_replay.log";
private final String[] commandLineReplay;
private final String className;
public class TestInliningProtectionDomain extends InliningBase {
public static void main(String[] args) {
new TestInliningProtectionDomain("ProtectionDomainTestCompiledBefore", true);
new TestInliningProtectionDomain("ProtectionDomainTestNoOtherCompilationPublic", false);
new TestInliningProtectionDomain("ProtectionDomainTestNoOtherCompilationPrivate", false);
new TestInliningProtectionDomain("ProtectionDomainTestNoOtherCompilationPrivateString", false);
new TestInliningProtectionDomain(ProtectionDomainTestCompiledBefore.class, true);
new TestInliningProtectionDomain(ProtectionDomainTestNoOtherCompilationPublic.class, false);
new TestInliningProtectionDomain(ProtectionDomainTestNoOtherCompilationPrivate.class, false);
new TestInliningProtectionDomain(ProtectionDomainTestNoOtherCompilationPrivateString.class, false);
}
public TestInliningProtectionDomain(String className, boolean compileBar) {
this.className = className;
List<String> commandLineNormal = new ArrayList<>(List.of("-XX:LogFile=" + LOG_FILE_NORMAL + "", "-XX:+LogCompilation", "-XX:-TieredCompilation",
"-XX:CompileCommand=exclude," + getTestClass() + "::main",
"-XX:CompileCommand=option," + getTestClass() + "::test,bool,PrintInlining,true"));
public TestInliningProtectionDomain(Class<?> testClass, boolean compileBar) {
super(testClass);
if (compileBar) {
commandLineNormal.add("-XX:CompileCommand=compileonly," + getTestClass() + "::bar");
commandLineNormal.add("-XX:CompileCommand=compileonly," + testClass.getName() + "::bar");
}
commandLineReplay = new String[]
{"-XX:LogFile=" + LOG_FILE_REPLAY + "", "-XX:+LogCompilation",
"-XX:CompileCommand=option," + getTestClass() + "::test,bool,PrintInlining,true"};
runTest(commandLineNormal.toArray(new String[0]));
runTest();
}
@Override
public void testAction() {
positiveTest(commandLineReplay);
String klass = "compiler.ciReplay." + className;
String entryString = klass + " " + "test";
boolean inlineFails = className.equals("ProtectionDomainTestNoOtherCompilationPrivate");
String entryString = getTestClass() + " " + "test";
boolean inlineFails = testClass == ProtectionDomainTestNoOtherCompilationPrivate.class;
int inlineeCount = inlineFails ? 1 : 5;
List<Entry> inlineesNormal = parseLogFile(LOG_FILE_NORMAL, entryString, "compile_id='" + getCompileIdFromFile(getReplayFileName()), inlineeCount);
List<Entry> inlineesReplay = parseLogFile(LOG_FILE_REPLAY, entryString, "test ()V", inlineeCount);
List<InlineEntry> inlineesNormal = parseLogFile(LOG_FILE_NORMAL, entryString, "compile_id='" + getCompileIdFromFile(getReplayFileName()), inlineeCount);
List<InlineEntry> inlineesReplay = parseLogFile(LOG_FILE_REPLAY, entryString, "test ()V", inlineeCount);
verifyLists(inlineesNormal, inlineesReplay, inlineeCount);
if (inlineFails) {
Asserts.assertTrue(compare(inlineesNormal.get(0), "compiler.ciReplay.ProtectionDomainTestNoOtherCompilationPrivate",
"bar", inlineesNormal.get(0).isUnloadedSignatureClasses()));
Asserts.assertTrue(compare(inlineesReplay.get(0), "compiler.ciReplay.ProtectionDomainTestNoOtherCompilationPrivate",
"bar", inlineesReplay.get(0).isDisallowedByReplay()));
Asserts.assertTrue(inlineesNormal.get(0).compare("compiler.ciReplay.ProtectionDomainTestNoOtherCompilationPrivate", "bar", inlineesNormal.get(0).isUnloadedSignatureClasses()));
Asserts.assertTrue(inlineesReplay.get(0).compare("compiler.ciReplay.ProtectionDomainTestNoOtherCompilationPrivate", "bar", inlineesReplay.get(0).isDisallowedByReplay()));
} else {
Asserts.assertTrue(compare(inlineesNormal.get(4), "compiler.ciReplay.InliningBar", "bar2", inlineesNormal.get(4).isNormalInline()));
Asserts.assertTrue(compare(inlineesReplay.get(4), "compiler.ciReplay.InliningBar", "bar2", inlineesReplay.get(4).isForcedByReplay()));
}
remove(LOG_FILE_NORMAL);
remove(LOG_FILE_REPLAY);
}
private void verifyLists(List<Entry> inlineesNormal, List<Entry> inlineesReplay, int expectedSize) {
if (!inlineesNormal.equals(inlineesReplay)) {
System.err.println("Normal entries:");
inlineesNormal.forEach(System.err::println);
System.err.println("Replay entries:");
inlineesReplay.forEach(System.err::println);
Asserts.fail("different inlining decision in normal run vs. replay run");
}
Asserts.assertEQ(expectedSize, inlineesNormal.size(), "unexpected number of inlinees found");
}
public static boolean compare(Entry e, String klass, String method, boolean kind) {
return e.klass.equals(klass) && e.method.equals(method) && kind;
}
public static List<Entry> parseLogFile(String logFile, String rootMethod, String nmethodMatch, int inlineeCount) {
String nmethodStart = "<nmethod";
List<Entry> inlinees = new ArrayList<>();
int foundLines = 0;
try (var br = Files.newBufferedReader(Paths.get(logFile))) {
String line;
boolean nmethodLine = false;
boolean inlinineLine = false;
while ((line = br.readLine()) != null) {
if (nmethodLine) {
// Ignore other entries which could be in between nmethod entry and inlining statements
if (line.startsWith(" ")) {
inlinineLine = true;
Pattern p = Pattern.compile("(\\S+)::(\\S+).*bytes\\)\s+(.*)");
Matcher matcher = p.matcher(line);
Asserts.assertTrue(matcher.find(), "must find inlinee method");
inlinees.add(new Entry(matcher.group(1), matcher.group(2), matcher.group(3).trim()));
foundLines++;
} else if (inlinineLine) {
Asserts.assertEQ(foundLines, inlineeCount, "did not find all inlinees");
return inlinees;
}
} else {
nmethodLine = line.startsWith(nmethodStart) && line.contains(nmethodMatch);
if (nmethodLine) {
Asserts.assertTrue(line.contains(rootMethod), "should only dump inline information for " + rootMethod);
}
}
}
} catch (IOException e) {
throw new Error("Failed to read " + logFile + " data: " + e, e);
}
Asserts.fail("Should have found inlinees");
return inlinees;
}
@Override
public String getTestClass() {
return "compiler.ciReplay." + className;
}
static class Entry {
String klass;
String method;
String reason;
public Entry(String klass, String method, String reason) {
this.klass = klass;
this.method = method;
this.reason = reason;
}
public boolean isNormalInline() {
return reason.equals("inline (hot)");
}
public boolean isForcedByReplay() {
return reason.equals("force inline by ciReplay");
}
public boolean isDisallowedByReplay() {
return reason.equals("disallowed by ciReplay");
}
public boolean isUnloadedSignatureClasses() {
return reason.equals("unloaded signature classes");
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Entry)) {
return false;
}
Entry e = (Entry)other;
return klass.equals(e.klass) && method.equals(e.method);
Asserts.assertTrue(inlineesNormal.get(4).compare("compiler.ciReplay.InliningBar", "bar2", inlineesNormal.get(4).isNormalInline()));
Asserts.assertTrue(inlineesReplay.get(4).compare("compiler.ciReplay.InliningBar", "bar2", inlineesReplay.get(4).isForcedByReplay()));
}
}
}