From 81c9ea0682c54beb82f6259104f266bbc57c2fd6 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Mon, 3 Aug 2026 07:09:31 +0000 Subject: [PATCH] 8388709: [lworld] replay parts of JDK-8350865 Reviewed-by: dlong, thartmann --- src/hotspot/share/ci/ciEnv.cpp | 11 +- src/hotspot/share/ci/ciInstanceKlass.cpp | 36 +- src/hotspot/share/ci/ciReplay.cpp | 37 +- src/hotspot/share/ci/ciReplay.hpp | 2 +- src/hotspot/share/memory/oopFactory.cpp | 2 +- src/hotspot/share/oops/instanceKlass.cpp | 17 +- src/hotspot/share/oops/instanceKlass.hpp | 2 + src/hotspot/share/oops/symbol.hpp | 2 +- .../jtreg/compiler/ciReplay/CiReplayBase.java | 2 - .../compiler/ciReplay/DumpReplayBase.java | 2 +- .../jtreg/compiler/ciReplay/ReplayFile.java | 749 +++++++++++++++++- .../compiler/ciReplay/TestDumpReplay.java | 4 +- .../ciReplay/TestDumpReplayCommandLine.java | 10 +- .../ciReplay/TestIncrementalInlining.java | 2 +- .../jtreg/compiler/ciReplay/TestInlining.java | 2 +- .../TestInliningProtectionDomain.java | 2 +- .../ciReplay/TestInvalidReplayFile.java | 4 +- .../jtreg/compiler/ciReplay/TestLambdas.java | 2 +- .../compiler/ciReplay/TestNoClassFile.java | 2 +- .../ciReplay/TestNullStaticField.java | 2 +- .../jtreg/compiler/ciReplay/TestReplayV4.java | 238 ++++++ .../ciReplay/TestUnresolvedClasses.java | 2 +- .../ciReplay/TestValueClassArrays.java | 2 +- 23 files changed, 1081 insertions(+), 53 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 41d2395aefb..8123d21dd98 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -187,9 +187,9 @@ ciEnv::ciEnv(CompileTask* task) // { // RecordLocation fp(this, "field1"); // // location: "field1" -// { RecordLocation fp(this, " field2"); // location: "field1 field2" } +// { RecordLocation fp(this, "field2"); // location: "field1 field2" } // // location: "field1" -// { RecordLocation fp(this, " field3"); // location: "field1 field3" } +// { RecordLocation fp(this, "field3"); // location: "field1 field3" } // // location: "field1" // } // // location: "" @@ -225,10 +225,13 @@ public: // append a new component ATTRIBUTE_PRINTF(3, 4) RecordLocation(ciEnv* ci, const char* fmt, ...) { - end = ci->_dyno_name + strlen(ci->_dyno_name); + size_t len = strlen(ci->_dyno_name); + end = ci->_dyno_name + len; va_list args; va_start(args, fmt); - push(ci, " "); + if (len > 0) { + push(ci, " "); + } push_va(ci, fmt, args); va_end(args); } diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 41adc7de96b..596a0a8415a 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -838,8 +838,8 @@ public: StaticFieldPrinter(out), _obj(obj) { } void do_field(fieldDescriptor* fd) { - do_field_helper(fd, _obj, true); _out->print(" "); + do_field_helper(fd, _obj, true); } }; @@ -865,27 +865,48 @@ void StaticFieldPrinter::do_field_helper(fieldDescriptor* fd, oop mirror, bool i case T_ARRAY: // fall-through case T_OBJECT: if (!fd->is_null_free_inline_type()) { - _out->print("%s ", fd->signature()->as_quoted_ascii()); + _out->print("%s", fd->signature()->as_quoted_ascii()); oop value = mirror->obj_field_acquire(fd->offset()); if (value == nullptr) { if (field_type == T_ARRAY) { - _out->print("%d", -1); + _out->print(" %d", -1); } - _out->cr(); } else if (value->is_instance()) { assert(field_type == T_OBJECT, ""); if (value->is_a(vmClasses::String_klass())) { const char* ascii_value = java_lang_String::as_quoted_ascii(value); - _out->print("\"%s\"", (ascii_value != nullptr) ? ascii_value : ""); + _out->print(" \"%s\"", (ascii_value != nullptr) ? ascii_value : ""); } else { const char* klass_name = value->klass()->name()->as_quoted_ascii(); - _out->print("%s", klass_name); + _out->print(" %s", klass_name); } } else if (value->is_array()) { arrayOop a = (arrayOop)value; - _out->print("%d", a->length()); + _out->print(" %d", a->length()); if (value->is_objArray()) { objArrayOop oa = (objArrayOop)value; + if (value->is_flatArray()) { + FlatArrayKlass* klass = ((flatArrayOop)oa)->klass(); + LayoutKind lk = klass->layout_kind(); + _out->print(" flat"); + if (LayoutKindHelper::is_nullable_flat(lk)) { + _out->print(" nullable"); + } else { + _out->print(" null-free"); + } + if (LayoutKindHelper::is_atomic_flat(lk)) { + _out->print(" atomic"); + } else { + _out->print(" non-atomic"); + } + } else { + _out->print(" ref"); + if (oa->klass()->is_null_free_array_klass()) { + _out->print(" null-free"); + } else { + _out->print(" nullable"); + } + } const char* klass_name = value->klass()->name()->as_quoted_ascii(); _out->print(" %s", klass_name); } @@ -895,6 +916,7 @@ void StaticFieldPrinter::do_field_helper(fieldDescriptor* fd, oop mirror, bool i break; } else { // handling of null free inline type + _out->print("%s", fd->signature()->as_quoted_ascii()); ResetNoHandleMark rnhm; Thread* THREAD = Thread::current(); SignatureStream ss(fd->signature(), false); diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index be55687cca4..733748735fe 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -514,15 +514,14 @@ class CompileReplay : public StackObj { return k; } obj = ciReplay::obj_field(obj, field); - // TODO 8350865 I think we need to handle null-free/flat arrays here - if (obj != nullptr && obj->is_refArray()) { - refArrayOop arr = oop_cast(obj); + if (obj != nullptr && obj->is_objArray()) { + objArrayOop arr = oop_cast(obj); int index = parse_int("index"); if (index >= arr->length()) { report_error("bad array index"); return nullptr; } - obj = arr->obj_at(index); + obj = arr->obj_at(index, THREAD); } } while (obj != nullptr); if (obj == nullptr) { @@ -825,7 +824,7 @@ class CompileReplay : public StackObj { rec->_instructions_size = parse_int("instructions_size"); } - // ciMethodData orig * data * oops ( )* methods ( )* + // ciMethodData orig * data * oops ( ?)* methods ( )* void process_ciMethodData(TRAPS) { Method* method = parse_method(CHECK); if (had_error()) return; @@ -1139,12 +1138,26 @@ class CompileReplay : public StackObj { value = oopFactory::new_longArray(length, CHECK_(true)); } else if (field_signature[0] == JVM_SIGNATURE_ARRAY && field_signature[1] == JVM_SIGNATURE_CLASS) { - Klass* actual_array_klass = parse_klass(CHECK_(true)); - // TODO 8350865 I think we need to handle null-free/flat arrays here - // This handling will change the array property argument passed to the - // factory below - Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); - value = oopFactory::new_objArray(kelem, length, CHECK_(true)); + const char* flatness = parse_string(); + if (strcmp(flatness, "ref") == 0) { + const char* nullability = parse_string(); + bool null_restricted = (strcmp(nullability, "null-free") == 0); + Klass* actual_array_klass = parse_klass(CHECK_(true)); + Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); + ArrayProperties props = ArrayProperties::Default().with_non_atomic(false).with_null_restricted(null_restricted); + value = oopFactory::new_refArray(kelem, length, props, CHECK_(true)); + } else if (strcmp(flatness, "flat") == 0) { + const char* nullability = parse_string(); + const char* atomicity = parse_string(); + bool null_restricted = (strcmp(nullability, "null-free") == 0); + bool non_atomic = (strcmp(atomicity, "non-atomic") == 0); + Klass* actual_array_klass = parse_klass(CHECK_(true)); + Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); + ArrayProperties props = ArrayProperties::Default().with_non_atomic(non_atomic).with_null_restricted(null_restricted); + value = oopFactory::new_flatArray(InlineKlass::cast(kelem), length, props, CHECK_(true)); + } else { + report_error("unrecognized array kind"); + } } else { report_error("unhandled array staticfield"); } @@ -1190,7 +1203,7 @@ class CompileReplay : public StackObj { fieldDescriptor fd; Symbol* name = SymbolTable::new_symbol(field_name); Symbol* sig = SymbolTable::new_symbol(field_signature); - if (!k->find_local_field(name, sig, &fd) || + if (!k->find_local_field(name, sig, &fd, _version >= 3) || !fd.is_static() || fd.has_initial_value()) { report_error(field_name); diff --git a/src/hotspot/share/ci/ciReplay.hpp b/src/hotspot/share/ci/ciReplay.hpp index f8c3e73b120..6013c284510 100644 --- a/src/hotspot/share/ci/ciReplay.hpp +++ b/src/hotspot/share/ci/ciReplay.hpp @@ -134,7 +134,7 @@ class ciReplay { // 1: first instanceKlass sets protection domain (8275868) // replace current_mileage with invocation_count (8276095) // 2: incremental inlining support (8254108) -// 3: value class array support (8375548) +// 3: value class array support (8375548 & 8388709) #define REPLAY_VERSION 3 // current version, bump up for incompatible changes #endif // SHARE_CI_CIREPLAY_HPP diff --git a/src/hotspot/share/memory/oopFactory.cpp b/src/hotspot/share/memory/oopFactory.cpp index d7fdffb6c32..c1f9f8dacc8 100644 --- a/src/hotspot/share/memory/oopFactory.cpp +++ b/src/hotspot/share/memory/oopFactory.cpp @@ -117,7 +117,7 @@ objArrayOop oopFactory::new_objArray(Klass* klass, int length, ArrayProperties p } objArrayOop oopFactory::new_objArray(Klass* klass, int length, TRAPS) { - return new_objArray(klass, length, ArrayProperties::Default(), THREAD); + return new_objArray(klass, length, ArrayProperties::Default(), THREAD); } refArrayOop oopFactory::new_refArray(Klass* klass, int length, ArrayProperties properties, TRAPS) { diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index f335ccd5ed6..ec74f005e44 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -1583,7 +1583,7 @@ void InstanceKlass::initialize_impl(TRAPS) { call_class_initializer(THREAD); } - if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION) { + if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION && !ReplayCompiles) { // Step 9 also verifies that strict static fields have been initialized. // Status bits were set in ClassFileParser::post_process_parsed_stream. // After , bits must all be clear, or else we must throw an error. @@ -2134,12 +2134,25 @@ bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* if (fs.lookup(name, sig)) { assert(fs.name() == name, "name must match"); assert(fs.signature() == sig, "signature must match"); - fd->reinitialize(const_cast(this), fs.to_FieldInfo()); + fd->reinitialize(this, fs.to_FieldInfo()); return true; } return false; } +bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd, bool also_internal) const { + if (!also_internal) { + return find_local_field( name, sig, fd); + } + + for (AllFieldStream fs(this); !fs.done(); fs.next()) { + if (fs.name() == name && fs.signature() == sig) { + fd->reinitialize(this, fs.to_FieldInfo()); + return true; + } + } + return false; +} Klass* InstanceKlass::find_interface_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const { const int n = local_interfaces()->length(); diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 8ddf3893fd5..aba81900985 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -669,6 +669,8 @@ public: // find local field, returns true if found bool find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const; + // find local field, returns true if found + bool find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd, bool also_internal) const; // find field in direct superinterfaces, returns the interface in which the field is defined Klass* find_interface_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const; // find field according to JVM spec 5.4.3.2, returns the klass in which the field is defined diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp index b5277d3cde8..2fc664dfab2 100644 --- a/src/hotspot/share/oops/symbol.hpp +++ b/src/hotspot/share/oops/symbol.hpp @@ -249,7 +249,7 @@ class Symbol : public MetaspaceObj { int index_of_at(int i, const char* substr, int substr_len) const; // Three-way compare for sorting; returns -1/0/1 if receiver is than arg - // note that the ordering is not alfabetical + // note that the ordering is not alphabetical inline int fast_compare(const Symbol* other) const; // Returns receiver converted to null-terminated UTF-8 string; string is diff --git a/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java b/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java index 017242c6d9e..8fcdbca6ff3 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java +++ b/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java @@ -58,9 +58,7 @@ public abstract class CiReplayBase { public static final String CLIENT_VM_OPTION = "-client"; public static final String SERVER_VM_OPTION = "-server"; public static final String TEST_CORE_FILE_NAME = "test_core"; - public static final String RUN_SHELL_NO_LIMIT = "ulimit -c unlimited && "; private static final String REPLAY_FILE_OPTION = "-XX:ReplayDataFile=" + REPLAY_FILE_NAME; - private static final String LOCATIONS_STRING = "location: "; private static final String HS_ERR_NAME = "hs_err_pid"; private static final String RUN_SHELL_ZERO_LIMIT = "ulimit -S -c 0 && "; private static final String VERSION_OPTION = "-version"; diff --git a/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java b/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java index 3597c0729a1..c737971e28e 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java +++ b/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; public abstract class DumpReplayBase extends CiReplayBase { - private static final String DUMP_REPLAY_PATTERN = "replay_pid"; + protected static final String DUMP_REPLAY_PATTERN = "replay_pid"; private List replayFiles; private String replayFileName; diff --git a/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java b/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java index decd3033096..ebb7e6e5b39 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java @@ -25,13 +25,15 @@ package compiler.ciReplay; import jdk.test.lib.Asserts; +import java.io.File; 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.ArrayList; -import java.util.List; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; public class ReplayFile { private final Path replayFilePath; @@ -78,4 +80,747 @@ public class ReplayFile { throw new Error("Failed to read/write replay data: " + ioe, ioe); } } + + static public class ParsedReplayFile { + sealed interface Command permits + VersionCommand, + JvmtiExportCommand, + InstanceKlassCommand, + CiInstanceKlassCommand, + StaticFieldCommand, + CiMethodDataCommand, + CiMethodCommand, + CompileCommand {} + + // version + public record VersionCommand(int version) implements Command {} + // JvmtiExport + public record JvmtiExportCommand(String field, int value) implements Command {} + // instanceKlass + // | @bci * ; + // | @cpi * ; + sealed interface InstanceKlassCommand extends Command permits InstanceKlassCommandName, InstanceKlassCommandBci, InstanceKlassCommandCpi {} + public record InstanceKlassCommandName(String name) implements InstanceKlassCommand {} + public record InstanceKlassCommandBci(String klass, String name, String signature, int bci, List location) implements InstanceKlassCommand {} + public record InstanceKlassCommandCpi(String klass, int cpi, List location) implements InstanceKlassCommand {} + // ciInstanceKlass tag* + public record CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List tag) implements Command {} + // staticfield [IBCSZJFD] + // | "[" [IBCSZJFD] + // | "ref" ("nullable" | "null-free") + // | "flat" ("nullable" | "null-free") ("atomic" | "non-atomic") + // | "Ljava/lang/String;" + // | ? + sealed interface StaticFieldCommand extends Command permits + StaticFieldCommandPrimitive, + StaticFieldCommandPrimitiveArray, + StaticFieldCommandRefArray, + StaticFieldCommandFlatArray, + StaticFieldCommandNullArray, + StaticFieldCommandString, + StaticFieldCommandInstance { + String klass(); + String fieldName(); + String signature(); + } + public record StaticFieldCommandPrimitive(String klass, String fieldName, String signature, String value) implements StaticFieldCommand {} + public record StaticFieldCommandPrimitiveArray(String klass, String fieldName, String signature, int length) implements StaticFieldCommand {} + public record StaticFieldCommandRefArray(String klass, String fieldName, String signature, int length, boolean nullFree, String actualKlass) implements StaticFieldCommand {} + public record StaticFieldCommandFlatArray(String klass, String fieldName, String signature, int length, boolean nullFree, boolean nonAtomic, String actualKlass) implements StaticFieldCommand {} + public record StaticFieldCommandNullArray(String klass, String fieldName, String signature) implements StaticFieldCommand {} + public record StaticFieldCommandString(String klass, String fieldName, String value) implements StaticFieldCommand { + public String signature() { return "Ljava/lang/String;"; } + } + public record StaticFieldCommandInstance(String klass, String fieldName, String signature, List actualKlassOrValues) implements StaticFieldCommand {} + // ciMethodData orig * data * oops ( ?)* methods ( )* + sealed interface CiMethodDataCommandOop permits CiMethodDataCommandOopInstance, CiMethodDataCommandOopArray {} + public record CiMethodDataCommandOopInstance(int offset, String klass) implements CiMethodDataCommandOop {} + public record CiMethodDataCommandOopArray(int offset, String klass, int arrayProperties) implements CiMethodDataCommandOop {} + public record CiMethodDataCommandMethod(int offset, String klass, String name, String signature) {} + public record CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List orig, List data, List oops, List methods) implements Command {} + // ciMethod + public record CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) implements Command {} + // compile inline ( )* + public record CompileCommandInline(int depth, int bci, boolean inlineLate, String klass, String name, String signature) {} + public record CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines) implements Command {} + + ParsedReplayFile(List commands) { this.commands = commands; } + List commands; + + // Set by sanity checking + boolean checked = false; + // Set by indexing, only after sanity checking + public record StaticField(String klass, String name) {} + HashMap staticFieldCommands = null; + + static public ParsedReplayFile parse(File file) throws IOException { + return parse(Files.readAllLines(file.toPath())); + } + static public ParsedReplayFile parse(List lines) { + return new ParsedReplayFile(lines.stream().map(ParsedReplayFile::parseLine).filter(Objects::nonNull).toList()); + } + static Command parseLine(String line) { + List pieces = Arrays.stream(line.split(" ")).filter(piece -> !piece.isEmpty()).toList(); + int commentIdx = pieces.indexOf("#"); + if (commentIdx >= 0) { + pieces = pieces.subList(0, commentIdx); + } + if (pieces.isEmpty()) { + return null; + } + String command = pieces.getFirst(); + var linePieces = LinePieces.make(pieces, command); + var cmd = switch (command) { + case "version" -> parseVersion(linePieces); + case "JvmtiExport" -> parseJvmtiExport(linePieces); + case "instanceKlass" -> parseInstanceKlass(linePieces); + case "ciInstanceKlass" -> parseCiInstanceKlass(linePieces); + case "staticfield" -> parseStaticField(linePieces); + case "ciMethodData" -> parseCiMethodData(linePieces); + case "ciMethod" -> parseCiMethod(linePieces); + case "compile" -> parseCompile(linePieces); + default -> throw new RuntimeException("unknown command: " + command); + }; + linePieces.checkAtEnd(); + return cmd; + } + + static class LinePieces { + int pos = 0; + List pieces; + + private LinePieces(List pieces) { + this.pieces = List.copyOf(pieces); + } + + @Override + public String toString() { + var before = pieces.subList(0, pos); + var after = pieces.subList(pos, pieces.size()); + return before + ">>" + after; + } + + static public LinePieces make(List pieces, String commandName) { + var line = new LinePieces(pieces); + line.getKeywork(commandName); + return line; + } + + void checkBounds(int nb) { + if (pos < 0) + throw new IndexOutOfBoundsException("negative position: " + pos); + if (pos + nb - 1 >= pieces.size()) + throw new IndexOutOfBoundsException("size: " + pieces.size() + "; pos: " + pos + "; nb: " + nb); + } + + void getKeywork(String keyword) { + checkBounds(1); + String s = getString(); + if (!keyword.equals(s)) { + throw new RuntimeException("expected keyword: " + keyword + "; got: " + s); + } + } + + public String getString() { + checkBounds(1); + String s = pieces.get(pos); + pos++; + return s; + } + + public List getStrings(int n) { + checkBounds(n); + List sub = pieces.subList(pos, pos + n); + pos += n; + return sub; + } + + public List getLeftoverStrings() { + return getStrings(pieces.size() - pos); + } + + public int getInt() { + String s = getString(); + return Integer.parseInt(s); + } + + public List getInts(int n) { + List s = getStrings(n); + return s.stream().map(Integer::parseInt).toList(); + } + + public Optional getIntIfTwoIntsAvailable() { + if (pos + 1 >= pieces.size()) { + return Optional.empty(); + } + String s0 = pieces.get(pos); + String s1 = pieces.get(pos + 1); + try { + Integer.parseInt(s0); + Integer.parseInt(s1); + } catch (NumberFormatException _) { + return Optional.empty(); + } + return Optional.of(getInt()); + } + + public boolean getBool() { + int s = getInt(); + return switch (s) { + case 0 -> false; + case 1 -> true; + default -> throw new RuntimeException("unexpected bool: " + s); + }; + } + + public boolean getBoolKeyword(String falseKw, String trueKw) { + String s = getString(); + if (s.equals(falseKw)) return false; + if (s.equals(trueKw)) return true; + throw new RuntimeException("unexepcted boolean keyword; got " + s + "; expected " + falseKw + " (for false) or " + trueKw + " (for true)"); + } + + public boolean atEnd() { + return pos == pieces.size(); + } + + public void checkAtEnd() { + if (!atEnd()) { + throw new RuntimeException("not at end; size: " + pieces.size() + "; pos: " + pos + "; pieces: " + this); + } + } + } + + static VersionCommand parseVersion(LinePieces pieces) { + int version = pieces.getInt(); + return new VersionCommand(version); + } + + static JvmtiExportCommand parseJvmtiExport(LinePieces pieces) { + String field = pieces.getString(); + int value = pieces.getInt(); + return new JvmtiExportCommand(field, value); + } + + static InstanceKlassCommand parseInstanceKlass(LinePieces pieces) { + String name = pieces.getString(); + return switch (name) { + case "@bci" -> parseInstanceKlassBci(pieces); + case "@cpi" -> parseInstanceKlassCpi(pieces); + default -> new InstanceKlassCommandName(name); + }; + } + + static InstanceKlassCommandBci parseInstanceKlassBci(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int bci = pieces.getInt(); + List location = new ArrayList<>(); + var nextS = pieces.getString(); + while (!nextS.equals(";")) { + location.add(nextS); + nextS = pieces.getString(); + } + return new InstanceKlassCommandBci(klass, name, signature, bci, location); + } + + static InstanceKlassCommandCpi parseInstanceKlassCpi(LinePieces pieces) { + String klass = pieces.getString(); + int cpi = pieces.getInt(); + List location = pieces.getLeftoverStrings(); + return new InstanceKlassCommandCpi(klass, cpi, location); + } + + static CiInstanceKlassCommand parseCiInstanceKlass(LinePieces pieces) { + String name = pieces.getString(); + boolean isLinked = pieces.getBool(); + boolean isInitialized = pieces.getBool(); + int length = pieces.getInt(); + List tag = pieces.getInts(length - 1); + return new CiInstanceKlassCommand(name, isLinked, isInitialized, length, tag); + } + + static boolean isPrimitiveType(char c) { + return "IBCSZJFD".contains(String.valueOf(c)); + } + + static StaticFieldCommand parseStaticField(LinePieces pieces) { + String klass = pieces.getString(); + String fieldName = pieces.getString(); + String signature = pieces.getString(); + if (isPrimitiveType(signature.charAt(0))) { + String val = pieces.getString(); + return new StaticFieldCommandPrimitive(klass, fieldName, signature, val); + } + if (signature.charAt(0) == '[') { + if (isPrimitiveType(signature.charAt(1))) { + int length = pieces.getInt(); + return new StaticFieldCommandPrimitiveArray(klass, fieldName, signature, length); + } else { + int length = pieces.getInt(); + if (length == -1) { + return new StaticFieldCommandNullArray(klass, fieldName, signature); + } + boolean isFlat = pieces.getBoolKeyword("ref", "flat"); + boolean nullFree = pieces.getBoolKeyword("nullable", "null-free"); + if (isFlat) { + boolean nonAtomic = pieces.getBoolKeyword("atomic", "non-atomic"); + String actualKlass = pieces.getString(); + return new StaticFieldCommandFlatArray(klass, fieldName, signature, length, nullFree, nonAtomic, actualKlass); + } else { + String actualKlass = pieces.getString(); + return new StaticFieldCommandRefArray(klass, fieldName, signature, length, nullFree, actualKlass); + } + } + } + if (signature.equals("Ljava/lang/String;")) { + String value = pieces.getString(); + return new StaticFieldCommandString(klass, fieldName, value); + } + List actualKlassOrValues = pieces.getLeftoverStrings(); + return new StaticFieldCommandInstance(klass, fieldName, signature, actualKlassOrValues); + } + + // oops ( ?)* methods ( )* + static CiMethodDataCommand parseCiMethodData(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int state = pieces.getInt(); + int invocationCounter = pieces.getInt(); + + pieces.getKeywork("orig"); + int origLength = pieces.getInt(); + List orig = pieces.getInts(origLength); + + pieces.getKeywork("data"); + int datalength = pieces.getInt(); + List data = pieces.getStrings(datalength); + + pieces.getKeywork("oops"); + int oopsLength = pieces.getInt(); + List oops = new ArrayList<>(oopsLength); + + for (int i = 0; i < oopsLength; i++) { + int offset = pieces.getInt(); + String klass_ = pieces.getString(); + Optional properties = pieces.getIntIfTwoIntsAvailable(); + oops.add( + properties + .map(prop -> (CiMethodDataCommandOop)new CiMethodDataCommandOopArray(offset, klass_, prop)) + .orElse(new CiMethodDataCommandOopInstance(offset, klass_)) + ); + } + + pieces.getKeywork("methods"); + int methodsLength = pieces.getInt(); + List methods = new ArrayList<>(methodsLength); + + for (int i = 0; i < methodsLength; i++) { + int offset = pieces.getInt(); + String klass_ = pieces.getString(); + String name_ = pieces.getString(); + String signature_ = pieces.getString(); + methods.add(new CiMethodDataCommandMethod(offset, klass_, name_, signature_)); + } + + return new CiMethodDataCommand(klass, name, signature, state, invocationCounter, orig, data, oops, methods); + } + + static CiMethodCommand parseCiMethod(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int invocationCounter = pieces.getInt(); + int backedgeCounter = pieces.getInt(); + int interpreterInvocationCount = pieces.getInt(); + int interpreterThrowoutCount = pieces.getInt(); + int instructionsSize = pieces.getInt(); + return new CiMethodCommand(klass, name, signature, invocationCounter, backedgeCounter, interpreterInvocationCount, interpreterThrowoutCount, instructionsSize); + } + + static CompileCommand parseCompile(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int entryBci = pieces.getInt(); + int compLevel = pieces.getInt(); + pieces.getKeywork("inline"); + int count = pieces.getInt(); + + List inlines = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + int depth = pieces.getInt(); + int bci = pieces.getInt(); + boolean inlineLate = pieces.getBool(); + String klass_ = pieces.getString(); + String name_ = pieces.getString(); + String signature_ = pieces.getString(); + inlines.add(new CompileCommandInline(depth, bci, inlineLate, klass_, name_, signature_)); + } + + return new CompileCommand(klass, name, signature, entryBci, compLevel, inlines); + } + + List checkSanity() { + record Method(String klass, String name, String signature) {} + record Field(String klass, String name) {} + List insanities = new ArrayList<>(); + + int seenVersionCommands = 0; + Set seenCiMethod = new HashSet<>(); + Set seenCiMethodData = new HashSet<>(); + Set seenCompile = new HashSet<>(); + Set seenKlasses = new HashSet<>(); + Map seenFields = new HashMap<>(); + for (Command c : commands) { + switch (c) { + case CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List tag) -> seenKlasses.add(name); + case StaticFieldCommand cmd -> { + String klass = cmd.klass(); + String fieldName = cmd.fieldName(); + if (!seenKlasses.contains(klass)) { + insanities.add("Static field command " + cmd + " seen before the corresponding ciInstanceKlass command."); + } + var field = new Field(klass, fieldName); + if (seenFields.containsKey(field)) { + insanities.add("Already seen the static field " + klass + "::" + fieldName + " with signature " + seenFields.get(field) + ". This time, it had signature " + cmd.signature() + "."); + } else { + seenFields.put(field, cmd.signature()); + } + } + case CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines) -> { + var method = new Method(klass, name, signature); + seenCompile.add(method); + if (!seenCiMethod.contains(method)) { + insanities.add("Found \"compile\" command without a \"ciMethod\" command for the same method."); + } + if (!seenCiMethodData.contains(method)) { + insanities.add("Found \"compile\" command without a \"ciMethodData\" command for the same method."); + } + } + case CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) -> + seenCiMethod.add(new Method(klass, name, signature)); + case CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List orig, List data, List oops, List methods) -> + seenCiMethodData.add(new Method(klass, name, signature)); + case VersionCommand _ -> + seenVersionCommands++; + case InstanceKlassCommand _, + JvmtiExportCommand _ -> { + } + } + } + + if (seenCompile.isEmpty()) { + insanities.add("No \"compile\" command found."); + } + + if (seenVersionCommands == 0) { + insanities.add("No \"version\" command found."); + } else if (seenVersionCommands > 1) { + insanities.add("Found too many \"version\" commands: " + seenVersionCommands); + } + checked = true; + return insanities; + } + + // Use it only after checkSanity. + void index() { + Asserts.assertTrue(checked); + staticFieldCommands = new HashMap<>(); + + for (Command c : commands) { + switch (c) { + case StaticFieldCommand cmd -> { + String klass = cmd.klass(); + String fieldName = cmd.fieldName(); + staticFieldCommands.put(new StaticField(klass, fieldName), cmd); + } + case CiInstanceKlassCommand _, + CiMethodCommand _, + CiMethodDataCommand _, + CompileCommand _, + InstanceKlassCommand _, + JvmtiExportCommand _, + VersionCommand _ -> {} + } + + } + } + + static Optional getVersion(ParsedReplayFile parsed) { + return parsed.commands.stream().map(cmd -> switch (cmd) { case VersionCommand(int version) -> version; default -> null; }).filter(Objects::nonNull).findAny(); + } + static void compareVersion(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + Optional lhsVersion = getVersion(lhs); + Optional rhsVersion = getVersion(rhs); + + if (lhsVersion.isPresent() && rhsVersion.isPresent() && !lhsVersion.get().equals(rhsVersion.get())) { + differences.add("Versions mismatch: lhs=" + lhsVersion.get() + "; rhs=" + rhsVersion.get()); + } + } + + static HashSet extractSet(ParsedReplayFile parsed, BiConsumer, Command> f) { + return parsed.commands.stream().collect( + HashSet::new, + f, + HashSet::addAll + ); + } + static void diffSets(String name, HashSet lhs, HashSet rhs, List differences) { + lhs.forEach((v) -> { + if (!rhs.contains(v)) { + differences.add(name + " mismatch: element=" + v + " exists only in lhs"); + } + } + ); + rhs.forEach((v) -> { + if (!lhs.contains(v)) { + differences.add(name + " mismatch: element=" + v + " exists only in rhs"); + } + } + ); + } + static HashMap extractMap(ParsedReplayFile parsed, BiConsumer, Command> f) { + return parsed.commands.stream().collect( + HashMap::new, + f, + HashMap::putAll + ); + } + static void diffMaps(String name, HashMap lhs, HashMap rhs, BiPredicate eqValue, List differences) { + lhs.forEach((key, lValue) -> { + if (!rhs.containsKey(key)) { + differences.add(name + " mismatch: key=" + key + " exists only in lhs"); + } else { + U rValue = rhs.get(key); + if (!eqValue.test(lValue, rValue)) { + differences.add(name + " mismatch: for key=" + key + "; value in lhs=" + lValue + "; value in rhs=" + rValue); + } + } + } + ); + rhs.forEach((key, _) -> { + if (!lhs.containsKey(key)) { + differences.add(name + " mismatch: key=" + key + " exists only in rhs"); + } + } + ); + } + + static void compareJvmtiExport(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof JvmtiExportCommand(String field, int value)) { + acc.put(field, value); + } + }; + HashMap lhsJvmti = extractMap(lhs, folder); + HashMap rhsJvmti = extractMap(rhs, folder); + diffMaps("JvmtiExport", lhsJvmti, rhsJvmti, Integer::equals, differences); + } + + static void compareInstanceKlassNames(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandName(String name)) { + acc.add(name); + } + }; + HashSet lhsKlasses = extractSet(lhs, folder); + HashSet rhsKlasses = extractSet(rhs, folder); + diffSets("InstanceKlass", lhsKlasses, rhsKlasses, differences); + } + static void compareInstanceKlassCpi(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, int cpi) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandCpi(String klass, int cpi, List location)) { + acc.put(new Key(klass, cpi), location); + } + }; + var lhsKlasses = extractMap(lhs, folder); + var rhsKlasses = extractMap(rhs, folder); + diffMaps("InstanceKlass", lhsKlasses, rhsKlasses, List::equals, differences); + } + static void compareInstanceKlassBci(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature, int bci) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandBci(String klass, String name, String signature, int bci, List location)) { + acc.put(new Key(klass, name, signature, bci), location); + } + }; + var lhsKlasses = extractMap(lhs, folder); + var rhsKlasses = extractMap(rhs, folder); + diffMaps("InstanceKlass", lhsKlasses, rhsKlasses, List::equals, differences); + } + static void compareInstanceKlasses(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + compareInstanceKlassNames(lhs, rhs, differences); + compareInstanceKlassCpi(lhs, rhs, differences); + compareInstanceKlassBci(lhs, rhs, differences); + } + + static void compareCiInstanceKlasses(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Element(String name, boolean isLinked, boolean isInitialized, int length) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List _)) { + acc.add(new Element(name, isLinked, isInitialized, length)); + } + }; + var lhsCiLlasses = extractSet(lhs, folder); + var rhsCiLlasses = extractSet(rhs, folder); + diffSets("CiInstanceKlass", lhsCiLlasses, rhsCiLlasses, differences); + } + + static void compareStaticFieldCommandPrimitive(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandPrimitive(String klass, String fieldName, String signature, String value)) { + acc.put(new Key(klass, fieldName, signature), value); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, String::equals, differences); + } + static void compareStaticFieldCommandPrimitiveArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandPrimitiveArray(String klass, String fieldName, String signature, int length)) { + acc.put(new Key(klass, fieldName, signature), length); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Integer::equals, differences); + } + static void compareStaticFieldCommandRefArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + record Value(int length, boolean nullFree, String actualKlass) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandRefArray(String klass, String fieldName, String signature, int length, boolean nullFree, String actualKlass)) { + acc.put(new Key(klass, fieldName, signature), new Value(length, nullFree, actualKlass)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + static void compareStaticFieldCommandFlatArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + record Value(int length, boolean nullFree, boolean nonAtomic, String actualKlass) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandFlatArray(String klass, String fieldName, String signature, int length, boolean nullFree, boolean nonAtomic, String actualKlass)) { + acc.put(new Key(klass, fieldName, signature), new Value(length, nullFree, nonAtomic, actualKlass)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + static void compareStaticFieldCommandNullArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Element(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandNullArray(String klass, String fieldName, String signature)) { + acc.add(new Element(klass, fieldName, signature)); + } + }; + var lhsStaticFields = extractSet(lhs, folder); + var rhsStaticFields = extractSet(rhs, folder); + diffSets("CiInstanceKlass", lhsStaticFields, rhsStaticFields, differences); + } + static void compareStaticFieldCommandString(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandString(String klass, String fieldName, String value)) { + acc.put(new Key(klass, fieldName), value); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, String::equals, differences); + } + static void compareStaticFieldCommandInstance(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandInstance(String klass, String fieldName, String signature, List actualKlassOrValues)) { + acc.put(new Key(klass, fieldName, signature), actualKlassOrValues); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, List::equals, differences); + } + static void compareStaticFieldCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + compareStaticFieldCommandPrimitive(lhs, rhs, differences); + compareStaticFieldCommandPrimitiveArray(lhs, rhs, differences); + compareStaticFieldCommandRefArray(lhs, rhs, differences); + compareStaticFieldCommandFlatArray(lhs, rhs, differences); + compareStaticFieldCommandNullArray(lhs, rhs, differences); + compareStaticFieldCommandString(lhs, rhs, differences); + compareStaticFieldCommandInstance(lhs, rhs, differences); + } + + static void compareCiMethodDataCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int state, int invocationCounter) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List _, List _, List _, List _)) { + acc.put(new Key(klass, name, signature), new Value(state, invocationCounter)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static void compareCiMethodCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize)) { + acc.put(new Key(klass, name, signature), new Value(invocationCounter, backedgeCounter, interpreterInvocationCount, interpreterThrowoutCount, instructionsSize)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static void compareCompileCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int entryBci, int compLevel, List inlines) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines)) { + acc.put(new Key(klass, name, signature), new Value(entryBci, compLevel, inlines)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static List findDifferences(ParsedReplayFile lhs, ParsedReplayFile rhs) { + List differences = new ArrayList<>(); + + compareVersion(lhs, rhs, differences); + compareJvmtiExport(lhs, rhs, differences); + compareInstanceKlasses(lhs, rhs, differences); + compareCiInstanceKlasses(lhs, rhs, differences); + compareStaticFieldCommand(lhs, rhs, differences); + compareCiMethodDataCommand(lhs, rhs, differences); + compareCiMethodCommand(lhs, rhs, differences); + compareCompileCommand(lhs, rhs, differences); + + return differences; + } + + Optional findStaticFieldCommand(String klass, String fieldName) { + Asserts.assertNotNull(staticFieldCommands); // Must be already indexed + var f = new StaticField(klass, fieldName); + if (!staticFieldCommands.containsKey(f)) { + return Optional.empty(); + } + return Optional.ofNullable(staticFieldCommands.get(f)); + } + } } diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java index 30f8aff7d17..63fc84fb668 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java @@ -30,8 +30,8 @@ * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+IgnoreUnrecognizedVMOptions * -Xbootclasspath/a:. -XX:+WhiteBoxAPI * -Xbatch -XX:-TieredCompilation -XX:+AlwaysIncrementalInline - * -XX:CompileCommand=compileonly,compiler.ciReplay.TestDumpReplay::* - * compiler.ciReplay.TestDumpReplay + * -XX:CompileCommand=compileonly,${test.main.class}::* + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java index 04ad56f4d24..ca4d253d1b0 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java @@ -32,7 +32,7 @@ * @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 -XX:+TieredCompilation - * compiler.ciReplay.TestDumpReplayCommandLine + * ${test.main.class} */ package compiler.ciReplay; @@ -40,15 +40,7 @@ package compiler.ciReplay; import jdk.test.lib.Asserts; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; -import java.util.StringTokenizer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class TestDumpReplayCommandLine extends DumpReplayBase { diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java b/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java index 43daab7a9b0..c5a398d6dba 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java @@ -31,7 +31,7 @@ * @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 + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java b/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java index 52a07abb5fd..9d1e888c028 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java @@ -31,7 +31,7 @@ * @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.TestInlining + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java index 2687315f05b..824c80a0388 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java @@ -31,7 +31,7 @@ * @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.TestInliningProtectionDomain + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java b/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java index 45a2e647c80..3ed9bd988be 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java @@ -29,9 +29,11 @@ * @requires vm.compMode != "Xint" * @modules java.base/jdk.internal.misc * java.management - * @run driver TestInvalidReplayFile + * @run driver ${test.main.class} */ +package compiler.ciReplay; + import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java b/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java index 33180a8c759..6d23a9404a8 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java @@ -31,7 +31,7 @@ * @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.TestLambdas + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java b/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java index 0459cf0afd1..ad2901bd2e5 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java @@ -31,7 +31,7 @@ * @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.TestNoClassFile + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java b/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java index 47a78ad5e44..cd179c14e69 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java @@ -31,7 +31,7 @@ * @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.TestNullStaticField + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java b/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java new file mode 100644 index 00000000000..ef95056c91f --- /dev/null +++ b/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2026, 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 8375548 + * @enablePreview + * @library / /test/lib + * @summary Testing the additions and fixes of Replay file v4 + * @requires vm.flagless & vm.flightRecorder != true & vm.compMode != "Xint" & vm.compMode != "Xcomp" & + * vm.debug == true & vm.compiler2.enabled + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @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 -XX:+TieredCompilation + * ${test.main.class} + */ + +package compiler.ciReplay; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import compiler.ciReplay.ReplayFile.ParsedReplayFile; +import compiler.ciReplay.ReplayFile.ParsedReplayFile.*; + +public class TestReplayV4 extends DumpReplayBase { + private final String[] defaultReplayRunFlags; + + public static void main(String[] args) { + new TestReplayV4().runTest("-XX:CompileCommand=dontinline,*::*", + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + TIERED_DISABLED_VM_OPTION); + } + + private TestReplayV4() { + defaultReplayRunFlags = defaultReplayRunFlags(); + } + + + private String[] defaultReplayRunFlags() { + List vmFlags = new ArrayList<>(); + Collections.addAll(vmFlags, + "-XX:+ReplayIgnoreInitErrors", + "-XX:CompileCommand=dontinline,*::*", + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + TIERED_DISABLED_VM_OPTION + ); + return vmFlags.toArray(new String[0]); + } + + @Override + public void testAction() { + reDumpAndCompare(); + } + + private String makeMessageFromList(String header, List lines) { + var message = new StringBuilder(header); + message.append(":\n"); + for (String diff : lines) { + message.append(" - ").append(diff).append("\n"); + } + return message.toString(); + } + + private void reDumpAndCompare() { + ParsedReplayFile firstParsedReplay; + ParsedReplayFile secondParsedReplay; + try { + String[] reDumpingFlags = Arrays.copyOf(defaultReplayRunFlags, defaultReplayRunFlags.length + 2); + reDumpingFlags[defaultReplayRunFlags.length] = "-XX:CompileCommand=option," + "*::*" + ",bool,DumpReplay,true"; + reDumpingFlags[defaultReplayRunFlags.length+1] = "-XX:CompileCommand=PrintCompilation,*::*"; + Asserts.assertEQ(getReplayFiles().size(), 1); + File firstReplay = getReplayFiles().getFirst(); + positiveTest(reDumpingFlags); + List replayFilesPostRun; + try (Stream files = Files.list(Paths.get("."))) { + replayFilesPostRun = files.map(Path::toFile).filter(f -> f.getName().startsWith(DUMP_REPLAY_PATTERN)).toList(); + } + Asserts.assertEQ(replayFilesPostRun.size(), 2); + Asserts.assertTrue(replayFilesPostRun.contains(firstReplay)); + var secondReplayOpt = replayFilesPostRun.stream().filter(file -> !file.equals(firstReplay)).findAny(); + Asserts.assertTrue(secondReplayOpt.isPresent()); + var secondReplay = secondReplayOpt.get(); + System.out.println("Replay read by the second run: "+firstReplay+"; replay produced: "+secondReplay); + + firstParsedReplay = ParsedReplayFile.parse(firstReplay); + secondParsedReplay = ParsedReplayFile.parse(secondReplay); + } catch (Throwable t) { + System.out.println(t); + System.out.println(t.getMessage()); + throw new Error("Can't find replay: " + t, t); + } + + // First, let's make sure replay files are not crazy. + var firstInsanities = firstParsedReplay.checkSanity(); + Asserts.assertTrue(firstInsanities.isEmpty(), makeMessageFromList("Insane first replay file", firstInsanities)); + var secondInsanities = secondParsedReplay.checkSanity(); + Asserts.assertTrue(secondInsanities.isEmpty(), makeMessageFromList("Insane second replay file", secondInsanities)); + + // For lookup later. This is allowed only after sanity checking. + firstParsedReplay.index(); + secondParsedReplay.index(); + + // Now, we make sure they have equivalent enough content. + var differences = ParsedReplayFile.findDifferences(firstParsedReplay, secondParsedReplay); + Asserts.assertTrue(differences.isEmpty(), makeMessageFromList("Differences", differences)); + + // Finally, we check a few facts about the second replay file. + var oArrNullCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNull"); + Asserts.assertTrue(oArrNullCmdOpt.isPresent()); + var oArrNullCmd = oArrNullCmdOpt.get(); + Asserts.assertTrue(oArrNullCmd instanceof StaticFieldCommandNullArray); + + var oArrRefArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrRefArray"); + Asserts.assertTrue(oArrRefArrayCmdOpt.isPresent()); + var oArrRefArrayCmdUntyped = oArrRefArrayCmdOpt.get(); + Asserts.assertTrue(oArrRefArrayCmdUntyped instanceof StaticFieldCommandRefArray); + var oArrRefArrayCmd = (StaticFieldCommandRefArray)oArrRefArrayCmdUntyped; + Asserts.assertFalse(oArrRefArrayCmd.nullFree()); + Asserts.assertEquals(oArrRefArrayCmd.length(), 2); + + var oArrNullableAtomicArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNullableAtomicArray"); + Asserts.assertTrue(oArrNullableAtomicArrayCmdOpt.isPresent()); + var oArrNullableAtomicArrayCmdUntyped = oArrNullableAtomicArrayCmdOpt.get(); + Asserts.assertTrue(oArrNullableAtomicArrayCmdUntyped instanceof StaticFieldCommandFlatArray); + var oArrNullableAtomicArrayCmd = (StaticFieldCommandFlatArray)oArrNullableAtomicArrayCmdUntyped; + Asserts.assertFalse(oArrNullableAtomicArrayCmd.nullFree()); + Asserts.assertFalse(oArrNullableAtomicArrayCmd.nonAtomic()); + Asserts.assertEquals(oArrNullableAtomicArrayCmd.length(), 2); + + var oArrNullRestrictedAtomicArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNullRestrictedAtomicArray"); + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmdOpt.isPresent()); + var oArrNullRestrictedAtomicArrayCmdUntyped = oArrNullRestrictedAtomicArrayCmdOpt.get(); + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmdUntyped instanceof StaticFieldCommandFlatArray); + var oArrNullRestrictedAtomicArrayCmd = (StaticFieldCommandFlatArray)oArrNullRestrictedAtomicArrayCmdUntyped; + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmd.nullFree()); + Asserts.assertFalse(oArrNullRestrictedAtomicArrayCmd.nonAtomic()); + Asserts.assertEquals(oArrNullRestrictedAtomicArrayCmd.length(), 2); + } + + @Override + public String getTestClass() { + return Test.class.getName(); + } + + + private static class Test { + static final Base[] oArrDefault = new Base[2]; + static final Base[] oArrNullableAtomicArray = (Base[]) ValueClass.newNullableAtomicArray(Derived.class, 2); + static final Base[] oArrNullRestrictedAtomicArray = (Base[]) ValueClass.newNullRestrictedAtomicArray(Derived.class, 2, new Derived(1, 0)); + static final Base[] oArrNullRestrictedNonAtomicArray = (Base[]) ValueClass.newNullRestrictedNonAtomicArray(Derived.class, 2, new Derived(2, 0)); + static final Base[] oArrRefArray = (Base[]) ValueClass.newReferenceArray(Derived.class, 2); + static final Base[] oArrNull = null; + + static Base o1, o2, o3, o4; + static final Base a = new Derived(10, 15); + static final Base a_base_null = null; + static final Derived a_derived_null = null; + @NullRestricted + static final Base a_base_null_free = new Derived(10, 15); + @NullRestricted + static final Derived a_derived_null_free = new Derived(10, 15); + + public static void main(String[] args) { + oArrDefault[0] = new Derived(3, 5); + oArrNullableAtomicArray[0] = new Derived(4, 6); + oArrNullRestrictedAtomicArray[0] = new Derived(5, 7); + oArrNullRestrictedNonAtomicArray[0] = new Derived(6, 8); + oArrRefArray[0] = new Derived(7, 9); + for (int i = 0; i < 10000; i++) { + test(); + } + } + + static void test() { + o1 = oArrDefault[0]; + oArrDefault[1] = a; + o2 = oArrNullableAtomicArray[0]; + oArrNullableAtomicArray[1] = a; + o3 = oArrNullRestrictedAtomicArray[0]; + oArrNullRestrictedAtomicArray[1] = a; + o4 = oArrNullRestrictedNonAtomicArray[0]; + oArrNullRestrictedNonAtomicArray[1] = a; + } + + static abstract value class Base { + short x; + byte y; + public Base(int x, int y) { + this.x = (short)x; + this.y = (byte)y; + } + } + + static value class Derived extends Base { + public Derived(int x, int y) { + super(x, y); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java b/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java index 9f05969798b..3700c1e4035 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java @@ -31,7 +31,7 @@ * @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.TestUnresolvedClasses + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java b/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java index 65ef1312d68..90d484065e5 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java @@ -35,7 +35,7 @@ * @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 -XX:+TieredCompilation - * compiler.ciReplay.TestValueClassArrays + * ${test.main.class} */ package compiler.ciReplay;