From 3e584f11bb2275c968a7d23276b1ebbb7e7e8ab5 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Sat, 18 Oct 2025 23:33:33 -0700 Subject: [PATCH 01/23] mdo replay --- src/hotspot/share/ci/ciClassList.hpp | 1 + src/hotspot/share/ci/ciEnv.hpp | 2 + src/hotspot/share/ci/ciMethodData.hpp | 1 + src/hotspot/share/prims/jni.cpp | 4 + src/hotspot/share/runtime/flags/allFlags.hpp | 9 + src/hotspot/share/runtime/java.cpp | 32 ++ src/hotspot/share/runtime/threads.cpp | 3 + src/hotspot/share/services/mdoReplayDump.cpp | 221 +++++++++++ src/hotspot/share/services/mdoReplayDump.hpp | 19 + src/hotspot/share/services/mdoReplayLoad.cpp | 346 ++++++++++++++++++ src/hotspot/share/services/mdoReplayLoad.hpp | 16 + .../share/services/mdoReplay_globals.hpp | 30 ++ 12 files changed, 684 insertions(+) create mode 100644 src/hotspot/share/services/mdoReplayDump.cpp create mode 100644 src/hotspot/share/services/mdoReplayDump.hpp create mode 100644 src/hotspot/share/services/mdoReplayLoad.cpp create mode 100644 src/hotspot/share/services/mdoReplayLoad.hpp create mode 100644 src/hotspot/share/services/mdoReplay_globals.hpp diff --git a/src/hotspot/share/ci/ciClassList.hpp b/src/hotspot/share/ci/ciClassList.hpp index bce1e52e80b..8102fc2d2d1 100644 --- a/src/hotspot/share/ci/ciClassList.hpp +++ b/src/hotspot/share/ci/ciClassList.hpp @@ -81,6 +81,7 @@ friend class ciObjectFactory; \ #define CI_PACKAGE_ACCESS_TO \ friend class ciObjectFactory; \ friend class VMStructs; \ +friend class MDOReplayDump; \ friend class ciCallSite; \ friend class ciConstantPoolCache; \ friend class ciField; \ diff --git a/src/hotspot/share/ci/ciEnv.hpp b/src/hotspot/share/ci/ciEnv.hpp index a22975c7453..a13de957e52 100644 --- a/src/hotspot/share/ci/ciEnv.hpp +++ b/src/hotspot/share/ci/ciEnv.hpp @@ -50,6 +50,8 @@ class ciEnv : StackObj { friend class Dependencies; // for get_object, during logging friend class RecordLocation; friend class PrepareExtraDataClosure; + friend class MDOReplayDump; // allow helper to access private getters + friend class VM_DumpMDOReplay; // nested op helper private: Arena* _arena; // Alias for _ciEnv_arena except in init_shared_objects() diff --git a/src/hotspot/share/ci/ciMethodData.hpp b/src/hotspot/share/ci/ciMethodData.hpp index a43d011b77e..99429e22ba5 100644 --- a/src/hotspot/share/ci/ciMethodData.hpp +++ b/src/hotspot/share/ci/ciMethodData.hpp @@ -370,6 +370,7 @@ class ciSpeculativeTrapData : public SpeculativeTrapData { class ciMethodData : public ciMetadata { CI_PACKAGE_ACCESS friend class ciReplay; + friend class MDOReplayDump; private: // Size in bytes diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 228244bbd05..503f525170d 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -93,6 +93,7 @@ #include "utilities/events.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +#include "services/mdoReplayLoad.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmciCompiler.hpp" #endif @@ -3619,6 +3620,9 @@ static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) { JFR_ONLY(Jfr::on_thread_start(thread);) if (ReplayCompiles) ciReplay::replay(thread); + if (LoadMDOAtStartup && MDOReplayLoadFile != nullptr) { + MDOReplayLoad::load(thread); + } #ifdef ASSERT // Some platforms (like Win*) need a wrapper around these test diff --git a/src/hotspot/share/runtime/flags/allFlags.hpp b/src/hotspot/share/runtime/flags/allFlags.hpp index edeb1cd4024..650e443e0c1 100644 --- a/src/hotspot/share/runtime/flags/allFlags.hpp +++ b/src/hotspot/share/runtime/flags/allFlags.hpp @@ -31,6 +31,7 @@ #include "gc/shared/tlab_globals.hpp" #include "runtime/flags/debug_globals.hpp" #include "runtime/globals.hpp" +#include "services/mdoReplay_globals.hpp" // Put LP64/ARCH/JVMCI/COMPILER1/COMPILER2 at the top, // as they are processed by jvmFlag.cpp in that order. @@ -89,6 +90,14 @@ range, \ constraint) \ \ + MDOREPLAY_FLAGS( \ + develop, \ + develop_pd, \ + product, \ + product_pd, \ + range, \ + constraint) \ + \ CDS_FLAGS( \ develop, \ develop_pd, \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 497420c8502..58f1ec1dc56 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -84,6 +84,8 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +#include "nmt/memTag.hpp" +#include "services/mdoReplayDump.hpp" #ifdef COMPILER1 #include "c1/c1_Compiler.hpp" #include "c1/c1_Runtime1.hpp" @@ -102,6 +104,13 @@ #endif GrowableArray* collected_profiled_methods; +static GrowableArray* collected_all_methods; + +static void collect_all_methods(Method* m) { + if (m != nullptr && m->method_data() != nullptr) { + collected_all_methods->push(m); + } +} static int compare_methods(Method** a, Method** b) { // compiled_invocation_count() returns int64_t, forcing the entire expression @@ -295,6 +304,29 @@ void print_statistics() { print_method_profiling_data(); + // Profiles-only replay dumper (stub): log intent at exit + if (DumpMDOAtExit && MDOReplayDumpFile != nullptr) { + log_info(compilation)("MDO replay: will dump profiles to %s", MDOReplayDumpFile); + fileStream fs(MDOReplayDumpFile, "w"); + if (fs.is_open()) { + // Emit a minimal header + fs.print_cr("# mdo-replay (MethodData only)"); + // Run at a safepoint to avoid concurrent MDO mutations during dump + class VM_MDOReplayDump : public VM_Operation { + fileStream* _out; + public: + VM_MDOReplayDump(fileStream* out) : _out(out) {} + virtual VMOp_Type type() const { return VMOp_GC_HeapInspection; } + virtual void doit() { + MDOReplayDump::dump_all(_out); + } + } op(&fs); + VMThread::execute(&op); + } else { + log_info(compilation)("MDO replay: failed to open %s", MDOReplayDumpFile); + } + } + if (TimeOopMap) { GenerateOopMap::print_time(); } diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 7251411a167..f91253f63cf 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -869,6 +869,9 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { if (UsePerfData) PerfDataManager::create_misc_perfdata(); if (CheckJNICalls) JniPeriodicChecker::engage(); + // Profiles-only replay loader (stub): log intent at startup + // MDO replay logging moved to JNI_CreateJavaVM_inner. + call_postVMInitHook(THREAD); // The Java side of PostVMInitHook.run must deal with all // exceptions and provide means of diagnosis. diff --git a/src/hotspot/share/services/mdoReplayDump.cpp b/src/hotspot/share/services/mdoReplayDump.cpp new file mode 100644 index 00000000000..20b54199125 --- /dev/null +++ b/src/hotspot/share/services/mdoReplayDump.cpp @@ -0,0 +1,221 @@ +#include "classfile/systemDictionary.hpp" +#include "services/mdoReplay_globals.hpp" +#include "oops/methodData.hpp" +#include "oops/method.hpp" +#include "services/mdoReplayDump.hpp" +#include "utilities/ostream.hpp" +#include "utilities/copy.hpp" +#include "logging/log.hpp" + +// Local collector shared with java.cpp style +static GrowableArray* g_methods; +static void collect_with_mdo(Method* m) { + if (m != nullptr && m->method_data() != nullptr) { + g_methods->push(m); + } +} + +static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { + // Header: MethodData + InstanceKlass* holder = m->method_holder(); + const char* kname = holder->name()->as_quoted_ascii(); + const char* mname = m->name()->as_quoted_ascii(); + const char* sig = m->signature()->as_quoted_ascii(); + const int state = mdo->is_mature() ? 2 /*mature*/ : 1 /*immature*/; + int invc = mdo->invocation_count(); + if (invc == 0 && mdo->backedge_count() > 0) invc = 1; + out->print("MethodData %s %s %s %d %d", kname, mname, sig, state, invc); + + // orig header bytes: copy MethodData::CompilerCounters + size_t cc_offset = in_bytes(MethodData::trap_history_offset()) - in_bytes(MethodData::CompilerCounters::trap_history_offset()); + const unsigned char* orig = (const unsigned char*)((address)mdo + cc_offset); + int orig_len = (int)sizeof(MethodData::CompilerCounters); + out->print(" orig %d", orig_len); + for (int i = 0; i < orig_len; i++) { + out->print(" %d", orig[i]); + } + + // Dump raw data words: data + extra_data + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); + out->print(" data %d", elements); + intptr_t* base = (intptr_t*)mdo->data_base(); + for (int i = 0; i < elements; i++) { + out->print(" 0x%zx", base[i]); + } + + // Collect class pointer offsets (oops) and method pointer offsets + GrowableArray class_offsets(16); + GrowableArray class_klasses(16); + GrowableArray method_offsets(4); + GrowableArray method_targets(4); + + // Iterate main data records + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + // Receiver types (virtual calls) + if (pd->is_VirtualCallData()) { + VirtualCallData* vcd = pd->as_VirtualCallData(); + for (uint row = 0; row < vcd->row_limit(); row++) { + Klass* k = vcd->receiver(row); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(pd->dp() + in_bytes(VirtualCallData::receiver_offset(row))); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + // Call argument/return types for invokedynamic/invokestatic/etc + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int i = 0; i < ctd->number_of_arguments(); i++) { + int off_bytes = in_bytes(ctd->argument_type_offset(i)); + intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + if (ctd->has_return()) { + int off_bytes = in_bytes(ctd->return_type_offset()); + intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + // Virtual call argument/return types + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int i = 0; i < vctd->number_of_arguments(); i++) { + int off_bytes = in_bytes(vctd->argument_type_offset(i)); + intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + if (vctd->has_return()) { + int off_bytes = in_bytes(vctd->return_type_offset()); + intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + } + + // Parameters type data (method entry) + if (mdo->parameters_type_data() != nullptr) { + ParametersTypeData* p = mdo->parameters_type_data(); + address p_dp = p->dp(); + for (int i = 0; i < p->number_of_parameters(); i++) { + int off_bytes = in_bytes(ParametersTypeData::type_offset(i)); + intptr_t val = *(intptr_t*)(p_dp + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + if (k != nullptr) { + int di_bytes = mdo->dp_to_di(p_dp + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } + } + } + } + + // Extra data: speculative trap data carries Method* + // Should not reach here. No extra data in MDO replay. + // { + // DataLayout* dp = mdo->extra_data_base(); + // DataLayout* end = mdo->args_data_limit(); + // for (; dp < end; dp = MethodData::next_extra(dp)) { + // switch (dp->tag()) { + // case DataLayout::no_tag: + // case DataLayout::arg_info_data_tag: + // break; + // case DataLayout::bit_data_tag: + // break; + // case DataLayout::speculative_trap_data_tag: { + // SpeculativeTrapData* s = new SpeculativeTrapData(dp); + // Method* sm = s->method(); + // if (sm != nullptr) { + // int di_bytes = mdo->dp_to_di(((address)dp) + in_bytes(SpeculativeTrapData::method_offset())); + // method_offsets.push(di_bytes / (int)sizeof(intptr_t)); + // method_targets.push(sm); + // } + // break; + // } + // default: + // break; + // } + // } + // } + + // Emit classes + out->print(" oops %d", class_offsets.length()); + for (int i = 0; i < class_offsets.length(); i++) { + Klass* k = class_klasses.at(i); + const char* cname = k->name()->as_quoted_ascii(); + out->print(" %d %s", class_offsets.at(i), cname); + } + + // Emit methods + out->print(" methods %d", method_offsets.length()); + for (int i = 0; i < method_offsets.length(); i++) { + Method* tm = method_targets.at(i); + InstanceKlass* th = tm->method_holder(); + out->print(" %d %s %s %s", method_offsets.at(i), th->name()->as_quoted_ascii(), tm->name()->as_quoted_ascii(), tm->signature()->as_quoted_ascii()); + } + + out->cr(); +} + +static const char* dump_path() { return MDOReplayDumpFile; } + +void MDOReplayDump::dump_all(fileStream* out) { + ResourceMark rm; + log_info(compilation)("MDO replay: dumping MDOs"); + GrowableArray methods(1024); + g_methods = &methods; + SystemDictionary::methods_do(collect_with_mdo); + g_methods = nullptr; + for (int i = 0; i < methods.length(); i++) { + Method* m = methods.at(i); + MethodData* mdo = m->method_data(); + if (mdo == nullptr) continue; + if (!mdo->is_mature()) continue; // dump only stable profiles + MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); + dump_one_mdo(out, m, mdo); + } + log_info(compilation)("MDO replay: dumped %d MDOs", methods.length()); +} + + diff --git a/src/hotspot/share/services/mdoReplayDump.hpp b/src/hotspot/share/services/mdoReplayDump.hpp new file mode 100644 index 00000000000..642f02f8a26 --- /dev/null +++ b/src/hotspot/share/services/mdoReplayDump.hpp @@ -0,0 +1,19 @@ +/* + * Minimal MDO replay dumper helper. + */ +#ifndef SHARE_SERVICES_MDOREPLAYDUMP_HPP +#define SHARE_SERVICES_MDOREPLAYDUMP_HPP + +#include "utilities/globalDefinitions.hpp" + +class fileStream; + +class MDOReplayDump { + public: + // Dump ciMethodData-style records for all methods with MDOs + static void dump_all(fileStream* out); +}; + +#endif // SHARE_SERVICES_MDOREPLAYDUMP_HPP + + diff --git a/src/hotspot/share/services/mdoReplayLoad.cpp b/src/hotspot/share/services/mdoReplayLoad.cpp new file mode 100644 index 00000000000..2921757ab99 --- /dev/null +++ b/src/hotspot/share/services/mdoReplayLoad.cpp @@ -0,0 +1,346 @@ +#include "classfile/systemDictionary.hpp" +#include "classfile/symbolTable.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "oops/methodData.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/os.hpp" +#include "services/mdoReplayLoad.hpp" +#include "logging/log.hpp" +#include "services/mdoReplay_globals.hpp" +#include "utilities/ostream.hpp" +#include "utilities/copy.hpp" +#include "memory/metaspace.hpp" + +// Very simple text parser matching the dump format in mdoReplayDump.cpp. +// Grammar (one line per MDO): +// MethodData orig data oops ( )... methods ( )... + +static char* read_word(FILE* f, char* buf, int buflen) { + int c; + do { c = fgetc(f); if (c == EOF) return nullptr; } while (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + int i = 0; buf[i++] = (char)c; + while (i < buflen - 1) { c = fgetc(f); if (c == EOF || c == ' ' || c == '\t' || c == '\n' || c == '\r') break; buf[i++] = (char)c; } + buf[i] = '\0'; + return buf; +} + +static bool read_int(FILE* f, int& out) { + char tmp[64]; if (read_word(f, tmp, sizeof(tmp)) == nullptr) return false; out = atoi(tmp); return true; +} + +static bool read_hex_word(FILE* f, uintptr_t& out) { + char tmp[64]; if (read_word(f, tmp, sizeof(tmp)) == nullptr) return false; out = (uintptr_t)strtoull(tmp, nullptr, 16); return true; +} + +static InstanceKlass* resolve_klass(const char* qname, TRAPS) { + // qname is quoted ascii; strip quotes if present + const char* s = qname; + size_t len = strlen(s); + if (len >= 2 && s[0] == '"' && s[len-1] == '"') { s++; len -= 2; } + Symbol* sym = SymbolTable::new_symbol(s, (int)len); + // Use the Java system loader (same as ciReplay) instead of bootstrap-only + Handle loader(THREAD, SystemDictionary::java_system_loader()); + Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } + if (k != nullptr && k->is_instance_klass()) return InstanceKlass::cast(k); + return nullptr; +} + +static Method* resolve_method(InstanceKlass* ik, const char* mname_q, const char* sig_q) { + const char* mn = mname_q; size_t ln = strlen(mn); if (ln >= 2 && mn[0]=='"' && mn[ln-1]=='"') { mn++; ln-=2; } + const char* sg = sig_q; size_t ls = strlen(sg); if (ls >= 2 && sg[0]=='"' && sg[ls-1]=='"') { sg++; ls-=2; } + return ik->find_method(SymbolTable::new_symbol(mn, (int)ln), SymbolTable::new_symbol(sg, (int)ls)); +} + +void MDOReplayLoad::load(JavaThread* THREAD) { + if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; + FILE* f = os::fopen(MDOReplayLoadFile, "r"); + if (f == nullptr) { return; } + ResourceMark rm; + log_info(compilation)("MDO replay: loading from %s", MDOReplayLoadFile); + int records_read = 0; + int records_installed = 0; + int size_mismatch = 0; + char word[4096]; + while (read_word(f, word, sizeof(word)) != nullptr) { + if (strcmp(word, "#") == 0) { // comment line: skip to EOL + int c; while ((c = fgetc(f)) != EOF && c != '\n') {} + continue; + } + if (strcmp(word, "MethodData") != 0) { + // skip line + int c; while ((c = fgetc(f)) != EOF && c != '\n') {} + continue; + } + records_read++; + char* kname = read_word(f, word, sizeof(word)); if (kname == nullptr) break; + char mname[4096]; if (read_word(f, mname, sizeof(mname)) == nullptr) break; + char msig[4096]; if (read_word(f, msig, sizeof(msig)) == nullptr) break; + int state = 0, invc = 0; if (!read_int(f, state) || !read_int(f, invc)) break; + // expect 'orig' + char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "orig") != 0) break; + int orig_len = 0; if (!read_int(f, orig_len)) break; + for (int i = 0; i < orig_len; i++) { int b = 0; if (!read_int(f, b)) break; /*ignored on load*/ } + // data + if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "data") != 0) break; + int nwords = 0; if (!read_int(f, nwords)) break; + GrowableArray words(nwords); + for (int i = 0; i < nwords; i++) { uintptr_t w = 0; if (!read_hex_word(f, w)) { nwords = i; break; } words.push(w); } + // oops + if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "oops") != 0) break; + int nclasses = 0; if (!read_int(f, nclasses)) break; + GrowableArray class_offs(nclasses); + GrowableArray class_vals(nclasses); + for (int i = 0; i < nclasses; i++) { + int off = 0; if (!read_int(f, off)) break; + char cname[4096]; if (read_word(f, cname, sizeof(cname)) == nullptr) break; + InstanceKlass* ck = resolve_klass(cname, THREAD); + class_offs.push(off); + class_vals.push(ck); // may be null; will be patched as null + } + // methods + if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "methods") != 0) break; + int nmethods = 0; if (!read_int(f, nmethods)) break; + GrowableArray method_offs(nmethods); + GrowableArray method_vals(nmethods); + for (int i = 0; i < nmethods; i++) { + int off = 0; if (!read_int(f, off)) break; + char mkname[4096], mmname[4096], mmsig[4096]; + if (read_word(f, mkname, sizeof(mkname)) == nullptr) break; + if (read_word(f, mmname, sizeof(mmname)) == nullptr) break; + if (read_word(f, mmsig, sizeof(mmsig)) == nullptr) break; + InstanceKlass* mik = resolve_klass(mkname, THREAD); + Method* mm = (mik == nullptr) ? nullptr : resolve_method(mik, mmname, mmsig); + method_offs.push(off); + method_vals.push(mm); + } + + // Resolve target Method and ensure MDO exists + InstanceKlass* holder = resolve_klass(kname, THREAD); + if (holder == nullptr) continue; + Method* target = resolve_method(holder, mname, msig); + if (target == nullptr) continue; + // Ensure the holder is linked before creating or touching profiling data + holder->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; continue; } + if (target->method_data() == nullptr) { + methodHandle mh(THREAD, target); + target->build_profiling_method_data(mh, CHECK); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; continue; } + } + MethodData* mdo = target->method_data(); + if (mdo == nullptr) continue; + + // Reset escape analysis info to avoid inconsistent state + mdo->clear_escape_info(); + + // Size check: must match + int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); + if (nwords != total_cells) { size_mismatch++; continue; } + + // Copy only up to args_data_limit: primary data + extra_data + params header region + // We will zero the trap/arg-info extra slice immediately after. + { +#ifdef _LP64 + Copy::conjoint_jlongs_atomic((jlong*)words.adr_at(0), (jlong*)mdo->data_base(), nwords); +#else + Copy::conjoint_jints_atomic((jint*)words.adr_at(0), (jint*)mdo->data_base(), nwords); +#endif + } + + // Zero-out the extra-data trap/arg-info slice to avoid inconsistent tags + { + DataLayout* extra_base = mdo->extra_data_base(); + DataLayout* args_limit = mdo->args_data_limit(); + if (args_limit > extra_base) { + size_t bytes = ((address)args_limit) - ((address)extra_base); + memset((void*)extra_base, 0, bytes); + } + + // Synthesize a minimal ArgInfoData at the end with all zeros so CI can update + // Find the start of ArgInfoData record: it is the last entry before args_data_limit + // We place a header right before args_data_limit with tag=arg_info_data_tag and array_len=method parameter count + int nparams = target->size_of_parameters(); + if (nparams > 0) { + // ArgInfoData occupies header + (len cell + nparams entries) cells + const int cell_size = (int)sizeof(intptr_t); + const int header_bytes = DataLayout::header_size_in_bytes(); + const size_t total_bytes = (size_t)header_bytes + (size_t)(nparams + 1) * (size_t)cell_size; + if (((address)args_limit) >= ((address)extra_base) + total_bytes) { + address arg_info_start = ((address)args_limit) - total_bytes; + // Zero region and place header + memset((void*)arg_info_start, 0, total_bytes); + DataLayout* aid = (DataLayout*)arg_info_start; + aid->set_header(0); + *(u1*)((address)aid + in_bytes(DataLayout::tag_offset())) = (u1)DataLayout::arg_info_data_tag; + // Set array length in first cell + aid->set_cell_at(0, (intptr_t)nparams); + } + } + } + + // Sanitize: clear all type-entry Klass* pointers to avoid stale addresses; + // we'll re-patch known ones below. Preserve status bits. + { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int i = 0; i < ctd->number_of_arguments(); i++) { + int off_b = in_bytes(ctd->argument_type_offset(i)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int i = 0; i < vctd->number_of_arguments(); i++) { + int off_b = in_bytes(vctd->argument_type_offset(i)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + ParametersTypeData* p = mdo->parameters_type_data(); + if (p != nullptr) { + address p_dp = p->dp(); + for (int i = 0; i < p->number_of_parameters(); i++) { + int off_b = in_bytes(ParametersTypeData::type_offset(i)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + + // Patch class pointers (TypeEntries): null on failure, preserving status bits + int patched_classes = 0; + // Safety-first: skip repatching TypeEntries Klass* to avoid stale metadata crashes. + // Profiles still contribute counters without receiver types. + + // Patch method pointers in extra data: null on failure + int patched_methods = 0; + for (int i = 0; i < method_offs.length(); i++) { + int off = method_offs.at(i); + intptr_t* slot = ((intptr_t*)mdo->data_base()) + off; + Method* mv = method_vals.at(i); + *(Method**)slot = mv; // may be nullptr + patched_methods++; + } + + // Final scrub: ensure all Klass* in type entries are live; null out otherwise + { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int i = 0; i < ctd->number_of_arguments(); i++) { + int off_b = in_bytes(ctd->argument_type_offset(i)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int i = 0; i < vctd->number_of_arguments(); i++) { + int off_b = in_bytes(vctd->argument_type_offset(i)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + } + ParametersTypeData* p = mdo->parameters_type_data(); + if (p != nullptr) { + address p_dp = p->dp(); + for (int i = 0; i < p->number_of_parameters(); i++) { + int off_b = in_bytes(ParametersTypeData::type_offset(i)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr) { + if (!Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + } + records_installed++; + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: installed %s %s %s (cells=%d, oops=%d, methods=%d)", + kname, mname, msig, nwords, patched_classes, patched_methods); + } + } + fclose(f); + log_info(compilation)("MDO replay: loaded %d records (%d installed, %d size mismatch)", + records_read, records_installed, size_mismatch); +} + + diff --git a/src/hotspot/share/services/mdoReplayLoad.hpp b/src/hotspot/share/services/mdoReplayLoad.hpp new file mode 100644 index 00000000000..1a21046e00e --- /dev/null +++ b/src/hotspot/share/services/mdoReplayLoad.hpp @@ -0,0 +1,16 @@ +/* + * Load MethodData profiles from a replay file and install into live MDOs. + */ +#ifndef SHARE_SERVICES_MDOREPLAYLOAD_HPP +#define SHARE_SERVICES_MDOREPLAYLOAD_HPP + +class JavaThread; + +class MDOReplayLoad { + public: + static void load(JavaThread* thread); +}; + +#endif // SHARE_SERVICES_MDOREPLAYLOAD_HPP + + diff --git a/src/hotspot/share/services/mdoReplay_globals.hpp b/src/hotspot/share/services/mdoReplay_globals.hpp new file mode 100644 index 00000000000..e07153ec39c --- /dev/null +++ b/src/hotspot/share/services/mdoReplay_globals.hpp @@ -0,0 +1,30 @@ +#ifndef SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP +#define SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP + +#include "runtime/globals_shared.hpp" + +// Defines MDO replay/export flags. Only flags here; included via allFlags.hpp. +#define MDOREPLAY_FLAGS(develop, \ + develop_pd, \ + product, \ + product_pd, \ + range, \ + constraint) \ + \ + product(ccstr, MDOReplayDumpFile, nullptr, DIAGNOSTIC, \ + "File for exporting profiles") \ + \ + product(ccstr, MDOReplayLoadFile, nullptr, DIAGNOSTIC, \ + "File for importing profiles") \ + \ + product(bool, DumpMDOAtExit, false, DIAGNOSTIC, \ + "Dump MDOs and counters to MDOReplayDumpFile at VM exit") \ + \ + product(bool, LoadMDOAtStartup, false, DIAGNOSTIC, \ + "Load MDOs and counters from MDOReplayLoadFile at VM startup") \ + +DECLARE_FLAGS(MDOREPLAY_FLAGS) + +#endif // SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP + + From 1a031c02dbe589c657cf2134f5e84a1b1f0cfa74 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Mon, 20 Oct 2025 21:39:58 -0700 Subject: [PATCH 02/23] qol: move parsing into functions --- src/hotspot/share/services/mdoReplayDump.cpp | 86 ++++++------- src/hotspot/share/services/mdoReplayLoad.cpp | 125 ++++++++++++------- 2 files changed, 121 insertions(+), 90 deletions(-) diff --git a/src/hotspot/share/services/mdoReplayDump.cpp b/src/hotspot/share/services/mdoReplayDump.cpp index 20b54199125..79e96b37e32 100644 --- a/src/hotspot/share/services/mdoReplayDump.cpp +++ b/src/hotspot/share/services/mdoReplayDump.cpp @@ -15,7 +15,7 @@ static void collect_with_mdo(Method* m) { } } -static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { +static void dump_header(fileStream* out, Method* m, MethodData* mdo) { // Header: MethodData InstanceKlass* holder = m->method_holder(); const char* kname = holder->name()->as_quoted_ascii(); @@ -25,6 +25,9 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { int invc = mdo->invocation_count(); if (invc == 0 && mdo->backedge_count() > 0) invc = 1; out->print("MethodData %s %s %s %d %d", kname, mname, sig, state, invc); +} + +static void dump_orig(fileStream* out, MethodData* mdo) { // orig header bytes: copy MethodData::CompilerCounters size_t cc_offset = in_bytes(MethodData::trap_history_offset()) - in_bytes(MethodData::CompilerCounters::trap_history_offset()); @@ -34,7 +37,9 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { for (int i = 0; i < orig_len; i++) { out->print(" %d", orig[i]); } +} +static void dump_cells(fileStream* out, MethodData* mdo) { // Dump raw data words: data + extra_data int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); out->print(" data %d", elements); @@ -42,16 +47,16 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { for (int i = 0; i < elements; i++) { out->print(" 0x%zx", base[i]); } +} - // Collect class pointer offsets (oops) and method pointer offsets - GrowableArray class_offsets(16); - GrowableArray class_klasses(16); - GrowableArray method_offsets(4); - GrowableArray method_targets(4); + - // Iterate main data records +static void collect_oops_and_methods(MethodData* mdo, + GrowableArray& class_offsets, + GrowableArray& class_klasses, + GrowableArray& method_offsets, + GrowableArray& method_targets) { for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - // Receiver types (virtual calls) if (pd->is_VirtualCallData()) { VirtualCallData* vcd = pd->as_VirtualCallData(); for (uint row = 0; row < vcd->row_limit(); row++) { @@ -59,6 +64,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(pd->dp() + in_bytes(VirtualCallData::receiver_offset(row))); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -66,7 +72,6 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { } } } - // Call argument/return types for invokedynamic/invokestatic/etc if (pd->is_CallTypeData()) { CallTypeData* ctd = (CallTypeData*)pd; if (ctd->has_arguments()) { @@ -77,6 +82,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -91,6 +97,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -98,7 +105,6 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { } } } - // Virtual call argument/return types if (pd->is_VirtualCallTypeData()) { VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); if (vctd->has_arguments()) { @@ -109,6 +115,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -123,6 +130,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -131,8 +139,6 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { } } } - - // Parameters type data (method entry) if (mdo->parameters_type_data() != nullptr) { ParametersTypeData* p = mdo->parameters_type_data(); address p_dp = p->dp(); @@ -143,6 +149,7 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { if (k != nullptr) { int di_bytes = mdo->dp_to_di(p_dp + off_bytes); int off_cells = di_bytes / (int)sizeof(intptr_t); + int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if (0 <= off_cells && off_cells < elements) { class_offsets.push(off_cells); class_klasses.push(k); @@ -150,54 +157,41 @@ static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { } } } +} - // Extra data: speculative trap data carries Method* - // Should not reach here. No extra data in MDO replay. - // { - // DataLayout* dp = mdo->extra_data_base(); - // DataLayout* end = mdo->args_data_limit(); - // for (; dp < end; dp = MethodData::next_extra(dp)) { - // switch (dp->tag()) { - // case DataLayout::no_tag: - // case DataLayout::arg_info_data_tag: - // break; - // case DataLayout::bit_data_tag: - // break; - // case DataLayout::speculative_trap_data_tag: { - // SpeculativeTrapData* s = new SpeculativeTrapData(dp); - // Method* sm = s->method(); - // if (sm != nullptr) { - // int di_bytes = mdo->dp_to_di(((address)dp) + in_bytes(SpeculativeTrapData::method_offset())); - // method_offsets.push(di_bytes / (int)sizeof(intptr_t)); - // method_targets.push(sm); - // } - // break; - // } - // default: - // break; - // } - // } - // } - - // Emit classes +static void write_oops_and_methods(fileStream* out, + GrowableArray& class_offsets, + GrowableArray& class_klasses, + GrowableArray& method_offsets, + GrowableArray& method_targets) { out->print(" oops %d", class_offsets.length()); for (int i = 0; i < class_offsets.length(); i++) { Klass* k = class_klasses.at(i); const char* cname = k->name()->as_quoted_ascii(); out->print(" %d %s", class_offsets.at(i), cname); } - - // Emit methods out->print(" methods %d", method_offsets.length()); for (int i = 0; i < method_offsets.length(); i++) { Method* tm = method_targets.at(i); InstanceKlass* th = tm->method_holder(); out->print(" %d %s %s %s", method_offsets.at(i), th->name()->as_quoted_ascii(), tm->name()->as_quoted_ascii(), tm->signature()->as_quoted_ascii()); } - out->cr(); } +static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { + dump_header(out, m, mdo); + dump_orig(out, mdo); + dump_cells(out, mdo); + GrowableArray class_offsets(16); + GrowableArray class_klasses(16); + GrowableArray method_offsets(4); + GrowableArray method_targets(4); + collect_oops_and_methods(mdo, class_offsets, class_klasses, method_offsets, method_targets); + // Note: we intentionally skip dumping extra_data trap records to avoid tag mismatches on load + write_oops_and_methods(out, class_offsets, class_klasses, method_offsets, method_targets); +} + static const char* dump_path() { return MDOReplayDumpFile; } void MDOReplayDump::dump_all(fileStream* out) { @@ -207,6 +201,7 @@ void MDOReplayDump::dump_all(fileStream* out) { g_methods = &methods; SystemDictionary::methods_do(collect_with_mdo); g_methods = nullptr; + int emitted = 0; for (int i = 0; i < methods.length(); i++) { Method* m = methods.at(i); MethodData* mdo = m->method_data(); @@ -214,8 +209,9 @@ void MDOReplayDump::dump_all(fileStream* out) { if (!mdo->is_mature()) continue; // dump only stable profiles MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); dump_one_mdo(out, m, mdo); + emitted++; } - log_info(compilation)("MDO replay: dumped %d MDOs", methods.length()); + log_info(compilation)("MDO replay: dumped %d MDOs", emitted); } diff --git a/src/hotspot/share/services/mdoReplayLoad.cpp b/src/hotspot/share/services/mdoReplayLoad.cpp index 2921757ab99..2bbc04936e4 100644 --- a/src/hotspot/share/services/mdoReplayLoad.cpp +++ b/src/hotspot/share/services/mdoReplayLoad.cpp @@ -54,6 +54,71 @@ static Method* resolve_method(InstanceKlass* ik, const char* mname_q, const char return ik->find_method(SymbolTable::new_symbol(mn, (int)ln), SymbolTable::new_symbol(sg, (int)ls)); } +// ---------------- Parsing helpers ---------------- +static bool parse_header(FILE* f, + char* kname, size_t kcap, + char* mname, size_t mncap, + char* msig, size_t mscap, + int& state_out, int& invc_out) { + char tmp[4096]; + char* kw = read_word(f, tmp, sizeof(tmp)); if (kw == nullptr) return false; strncpy(kname, kw, kcap); + char* mw = read_word(f, tmp, sizeof(tmp)); if (mw == nullptr) return false; strncpy(mname, mw, mncap); + char* sw = read_word(f, tmp, sizeof(tmp)); if (sw == nullptr) return false; strncpy(msig, sw, mscap); + if (!read_int(f, state_out)) return false; + if (!read_int(f, invc_out)) return false; + return true; +} + +static bool parse_orig(FILE* f) { + char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "orig") != 0) return false; + int orig_len = 0; if (!read_int(f, orig_len)) return false; + for (int i = 0; i < orig_len; i++) { int b = 0; if (!read_int(f, b)) return false; } + return true; +} + +static bool parse_data_words(FILE* f, GrowableArray& words) { + char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "data") != 0) return false; + int nwords = 0; if (!read_int(f, nwords)) return false; + for (int i = 0; i < nwords; i++) { uintptr_t w = 0; if (!read_hex_word(f, w)) return false; words.push(w); } + return true; +} + +static bool parse_oops(FILE* f, + GrowableArray& class_offs, + GrowableArray& class_vals, + JavaThread* THREAD) { + char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "oops") != 0) return false; + int nclasses = 0; if (!read_int(f, nclasses)) return false; + for (int i = 0; i < nclasses; i++) { + int off = 0; if (!read_int(f, off)) return false; + char cname[4096]; if (read_word(f, cname, sizeof(cname)) == nullptr) return false; + InstanceKlass* ck = resolve_klass(cname, THREAD); + class_offs.push(off); + class_vals.push(ck); + } + return true; +} + +static bool parse_methods(FILE* f, + GrowableArray& method_offs, + GrowableArray& method_vals, + JavaThread* THREAD) { + char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "methods") != 0) return false; + int nmethods = 0; if (!read_int(f, nmethods)) return false; + for (int i = 0; i < nmethods; i++) { + int off = 0; if (!read_int(f, off)) return false; + char mkname[4096], mmname[4096], mmsig[4096]; + if (read_word(f, mkname, sizeof(mkname)) == nullptr) return false; + if (read_word(f, mmname, sizeof(mmname)) == nullptr) return false; + if (read_word(f, mmsig, sizeof(mmsig)) == nullptr) return false; + InstanceKlass* mik = resolve_klass(mkname, THREAD); + Method* mm = (mik == nullptr) ? nullptr : resolve_method(mik, mmname, mmsig); + method_offs.push(off); + method_vals.push(mm); + } + return true; +} + void MDOReplayLoad::load(JavaThread* THREAD) { if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; FILE* f = os::fopen(MDOReplayLoadFile, "r"); @@ -75,47 +140,17 @@ void MDOReplayLoad::load(JavaThread* THREAD) { continue; } records_read++; - char* kname = read_word(f, word, sizeof(word)); if (kname == nullptr) break; - char mname[4096]; if (read_word(f, mname, sizeof(mname)) == nullptr) break; - char msig[4096]; if (read_word(f, msig, sizeof(msig)) == nullptr) break; - int state = 0, invc = 0; if (!read_int(f, state) || !read_int(f, invc)) break; - // expect 'orig' - char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "orig") != 0) break; - int orig_len = 0; if (!read_int(f, orig_len)) break; - for (int i = 0; i < orig_len; i++) { int b = 0; if (!read_int(f, b)) break; /*ignored on load*/ } - // data - if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "data") != 0) break; - int nwords = 0; if (!read_int(f, nwords)) break; - GrowableArray words(nwords); - for (int i = 0; i < nwords; i++) { uintptr_t w = 0; if (!read_hex_word(f, w)) { nwords = i; break; } words.push(w); } - // oops - if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "oops") != 0) break; - int nclasses = 0; if (!read_int(f, nclasses)) break; - GrowableArray class_offs(nclasses); - GrowableArray class_vals(nclasses); - for (int i = 0; i < nclasses; i++) { - int off = 0; if (!read_int(f, off)) break; - char cname[4096]; if (read_word(f, cname, sizeof(cname)) == nullptr) break; - InstanceKlass* ck = resolve_klass(cname, THREAD); - class_offs.push(off); - class_vals.push(ck); // may be null; will be patched as null - } - // methods - if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "methods") != 0) break; - int nmethods = 0; if (!read_int(f, nmethods)) break; - GrowableArray method_offs(nmethods); - GrowableArray method_vals(nmethods); - for (int i = 0; i < nmethods; i++) { - int off = 0; if (!read_int(f, off)) break; - char mkname[4096], mmname[4096], mmsig[4096]; - if (read_word(f, mkname, sizeof(mkname)) == nullptr) break; - if (read_word(f, mmname, sizeof(mmname)) == nullptr) break; - if (read_word(f, mmsig, sizeof(mmsig)) == nullptr) break; - InstanceKlass* mik = resolve_klass(mkname, THREAD); - Method* mm = (mik == nullptr) ? nullptr : resolve_method(mik, mmname, mmsig); - method_offs.push(off); - method_vals.push(mm); - } + char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0; + if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc)) break; + if (!parse_orig(f)) break; + GrowableArray words(16); + if (!parse_data_words(f, words)) break; + GrowableArray class_offs(8); + GrowableArray class_vals(8); + if (!parse_oops(f, class_offs, class_vals, THREAD)) break; + GrowableArray method_offs(4); + GrowableArray method_vals(4); + if (!parse_methods(f, method_offs, method_vals, THREAD)) break; // Resolve target Method and ensure MDO exists InstanceKlass* holder = resolve_klass(kname, THREAD); @@ -138,15 +173,15 @@ void MDOReplayLoad::load(JavaThread* THREAD) { // Size check: must match int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (nwords != total_cells) { size_mismatch++; continue; } + if ((int)words.length() != total_cells) { size_mismatch++; continue; } // Copy only up to args_data_limit: primary data + extra_data + params header region // We will zero the trap/arg-info extra slice immediately after. { #ifdef _LP64 - Copy::conjoint_jlongs_atomic((jlong*)words.adr_at(0), (jlong*)mdo->data_base(), nwords); + Copy::conjoint_jlongs_atomic((jlong*)words.adr_at(0), (jlong*)mdo->data_base(), words.length()); #else - Copy::conjoint_jints_atomic((jint*)words.adr_at(0), (jint*)mdo->data_base(), nwords); + Copy::conjoint_jints_atomic((jint*)words.adr_at(0), (jint*)mdo->data_base(), words.length()); #endif } @@ -335,7 +370,7 @@ void MDOReplayLoad::load(JavaThread* THREAD) { records_installed++; if (log_is_enabled(Debug, compilation)) { log_debug(compilation)("MDO replay: installed %s %s %s (cells=%d, oops=%d, methods=%d)", - kname, mname, msig, nwords, patched_classes, patched_methods); + kname, mname, msig, (int)words.length(), patched_classes, patched_methods); } } fclose(f); From 4d15405ce2118afa316343e36557c02523f3c60f Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Tue, 21 Oct 2025 01:42:48 -0700 Subject: [PATCH 03/23] more qol refactoring --- src/hotspot/share/services/mdoReplayDump.cpp | 110 +++--- src/hotspot/share/services/mdoReplayLoad.cpp | 348 +++++++++---------- 2 files changed, 207 insertions(+), 251 deletions(-) diff --git a/src/hotspot/share/services/mdoReplayDump.cpp b/src/hotspot/share/services/mdoReplayDump.cpp index 79e96b37e32..8ffa337e175 100644 --- a/src/hotspot/share/services/mdoReplayDump.cpp +++ b/src/hotspot/share/services/mdoReplayDump.cpp @@ -49,6 +49,34 @@ static void dump_cells(fileStream* out, MethodData* mdo) { } } +// Small helpers to avoid duplication when collecting Klass* cells +static void add_klass_cell_if_in_bounds(MethodData* mdo, + address base_dp, + int off_bytes, + Klass* k, + int elements, + GrowableArray& class_offsets, + GrowableArray& class_klasses) { + if (k == nullptr) return; + int di_bytes = mdo->dp_to_di(base_dp + off_bytes); + int off_cells = di_bytes / (int)sizeof(intptr_t); + if (0 <= off_cells && off_cells < elements) { + class_offsets.push(off_cells); + class_klasses.push(k); + } +} + +static void add_typeentry_if_present(MethodData* mdo, + address base_dp, + int off_bytes, + int elements, + GrowableArray& class_offsets, + GrowableArray& class_klasses) { + intptr_t val = *(intptr_t*)(base_dp + off_bytes); + Klass* k = TypeEntries::valid_klass(val); + add_klass_cell_if_in_bounds(mdo, base_dp, off_bytes, k, elements, class_offsets, class_klasses); +} + static void collect_oops_and_methods(MethodData* mdo, @@ -56,86 +84,40 @@ static void collect_oops_and_methods(MethodData* mdo, GrowableArray& class_klasses, GrowableArray& method_offsets, GrowableArray& method_targets) { + const int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData()) { VirtualCallData* vcd = pd->as_VirtualCallData(); for (uint row = 0; row < vcd->row_limit(); row++) { Klass* k = vcd->receiver(row); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(pd->dp() + in_bytes(VirtualCallData::receiver_offset(row))); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_klass_cell_if_in_bounds(mdo, pd->dp(), in_bytes(VirtualCallData::receiver_offset(row)), k, + elements, class_offsets, class_klasses); } } if (pd->is_CallTypeData()) { CallTypeData* ctd = (CallTypeData*)pd; if (ctd->has_arguments()) { for (int i = 0; i < ctd->number_of_arguments(); i++) { - int off_bytes = in_bytes(ctd->argument_type_offset(i)); - intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_typeentry_if_present(mdo, pd->dp(), in_bytes(ctd->argument_type_offset(i)), + elements, class_offsets, class_klasses); } } if (ctd->has_return()) { - int off_bytes = in_bytes(ctd->return_type_offset()); - intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_typeentry_if_present(mdo, pd->dp(), in_bytes(ctd->return_type_offset()), + elements, class_offsets, class_klasses); } } if (pd->is_VirtualCallTypeData()) { VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); if (vctd->has_arguments()) { for (int i = 0; i < vctd->number_of_arguments(); i++) { - int off_bytes = in_bytes(vctd->argument_type_offset(i)); - intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_typeentry_if_present(mdo, pd->dp(), in_bytes(vctd->argument_type_offset(i)), + elements, class_offsets, class_klasses); } } if (vctd->has_return()) { - int off_bytes = in_bytes(vctd->return_type_offset()); - intptr_t val = *(intptr_t*)(pd->dp() + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(pd->dp() + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_typeentry_if_present(mdo, pd->dp(), in_bytes(vctd->return_type_offset()), + elements, class_offsets, class_klasses); } } } @@ -143,18 +125,8 @@ static void collect_oops_and_methods(MethodData* mdo, ParametersTypeData* p = mdo->parameters_type_data(); address p_dp = p->dp(); for (int i = 0; i < p->number_of_parameters(); i++) { - int off_bytes = in_bytes(ParametersTypeData::type_offset(i)); - intptr_t val = *(intptr_t*)(p_dp + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - if (k != nullptr) { - int di_bytes = mdo->dp_to_di(p_dp + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } - } + add_typeentry_if_present(mdo, p_dp, in_bytes(ParametersTypeData::type_offset(i)), + elements, class_offsets, class_klasses); } } } diff --git a/src/hotspot/share/services/mdoReplayLoad.cpp b/src/hotspot/share/services/mdoReplayLoad.cpp index 2bbc04936e4..bcb06f4bbbd 100644 --- a/src/hotspot/share/services/mdoReplayLoad.cpp +++ b/src/hotspot/share/services/mdoReplayLoad.cpp @@ -119,64 +119,47 @@ static bool parse_methods(FILE* f, return true; } -void MDOReplayLoad::load(JavaThread* THREAD) { - if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; - FILE* f = os::fopen(MDOReplayLoadFile, "r"); - if (f == nullptr) { return; } - ResourceMark rm; - log_info(compilation)("MDO replay: loading from %s", MDOReplayLoadFile); - int records_read = 0; - int records_installed = 0; - int size_mismatch = 0; - char word[4096]; - while (read_word(f, word, sizeof(word)) != nullptr) { - if (strcmp(word, "#") == 0) { // comment line: skip to EOL - int c; while ((c = fgetc(f)) != EOF && c != '\n') {} - continue; - } - if (strcmp(word, "MethodData") != 0) { - // skip line - int c; while ((c = fgetc(f)) != EOF && c != '\n') {} - continue; - } - records_read++; - char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0; - if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc)) break; - if (!parse_orig(f)) break; - GrowableArray words(16); - if (!parse_data_words(f, words)) break; - GrowableArray class_offs(8); - GrowableArray class_vals(8); - if (!parse_oops(f, class_offs, class_vals, THREAD)) break; - GrowableArray method_offs(4); - GrowableArray method_vals(4); - if (!parse_methods(f, method_offs, method_vals, THREAD)) break; +// Load a single MethodData record; return false only on parse failure (break), true otherwise +static bool load_one_mdo(FILE* f, + JavaThread* THREAD, + int& size_mismatch, + int& records_installed) { + char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0; + if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc)) return false; + if (!parse_orig(f)) return false; + GrowableArray words(16); + if (!parse_data_words(f, words)) return false; + GrowableArray class_offs(8); + GrowableArray class_vals(8); + if (!parse_oops(f, class_offs, class_vals, THREAD)) return false; + GrowableArray method_offs(4); + GrowableArray method_vals(4); + if (!parse_methods(f, method_offs, method_vals, THREAD)) return false; - // Resolve target Method and ensure MDO exists - InstanceKlass* holder = resolve_klass(kname, THREAD); - if (holder == nullptr) continue; - Method* target = resolve_method(holder, mname, msig); - if (target == nullptr) continue; + // Resolve target Method and ensure MDO exists + InstanceKlass* holder = resolve_klass(kname, THREAD); + if (holder == nullptr) return true; + Method* target = resolve_method(holder, mname, msig); + if (target == nullptr) return true; // Ensure the holder is linked before creating or touching profiling data holder->link_class(THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; continue; } - if (target->method_data() == nullptr) { - methodHandle mh(THREAD, target); - target->build_profiling_method_data(mh, CHECK); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; continue; } - } - MethodData* mdo = target->method_data(); - if (mdo == nullptr) continue; + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return true; } + if (target->method_data() == nullptr) { + methodHandle mh(THREAD, target); + target->build_profiling_method_data(mh, CHECK_(true)); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return true; } + } + MethodData* mdo = target->method_data(); + if (mdo == nullptr) return true; - // Reset escape analysis info to avoid inconsistent state - mdo->clear_escape_info(); + // Reset escape analysis info to avoid inconsistent state + mdo->clear_escape_info(); - // Size check: must match - int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if ((int)words.length() != total_cells) { size_mismatch++; continue; } + // Size check: must match + int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); + if ((int)words.length() != total_cells) { size_mismatch++; return true; } - // Copy only up to args_data_limit: primary data + extra_data + params header region - // We will zero the trap/arg-info extra slice immediately after. + // Copy full cells (data + extra) { #ifdef _LP64 Copy::conjoint_jlongs_atomic((jlong*)words.adr_at(0), (jlong*)mdo->data_base(), words.length()); @@ -185,7 +168,7 @@ void MDOReplayLoad::load(JavaThread* THREAD) { #endif } - // Zero-out the extra-data trap/arg-info slice to avoid inconsistent tags + // Zero-out the extra-data trap/arg-info slice and synthesize minimal ArgInfoData { DataLayout* extra_base = mdo->extra_data_base(); DataLayout* args_limit = mdo->args_data_limit(); @@ -193,185 +176,186 @@ void MDOReplayLoad::load(JavaThread* THREAD) { size_t bytes = ((address)args_limit) - ((address)extra_base); memset((void*)extra_base, 0, bytes); } - - // Synthesize a minimal ArgInfoData at the end with all zeros so CI can update - // Find the start of ArgInfoData record: it is the last entry before args_data_limit - // We place a header right before args_data_limit with tag=arg_info_data_tag and array_len=method parameter count int nparams = target->size_of_parameters(); if (nparams > 0) { - // ArgInfoData occupies header + (len cell + nparams entries) cells const int cell_size = (int)sizeof(intptr_t); const int header_bytes = DataLayout::header_size_in_bytes(); const size_t total_bytes = (size_t)header_bytes + (size_t)(nparams + 1) * (size_t)cell_size; if (((address)args_limit) >= ((address)extra_base) + total_bytes) { address arg_info_start = ((address)args_limit) - total_bytes; - // Zero region and place header memset((void*)arg_info_start, 0, total_bytes); DataLayout* aid = (DataLayout*)arg_info_start; aid->set_header(0); *(u1*)((address)aid + in_bytes(DataLayout::tag_offset())) = (u1)DataLayout::arg_info_data_tag; - // Set array length in first cell aid->set_cell_at(0, (intptr_t)nparams); } } } - // Sanitize: clear all type-entry Klass* pointers to avoid stale addresses; - // we'll re-patch known ones below. Preserve status bits. - { - for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { - ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); - for (uint row = 0; row < rtd->row_limit(); row++) { - int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } + // Sanitize: clear all type-entry Klass* pointers to avoid stale addresses; preserve status bits + { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } - if (pd->is_CallTypeData()) { - CallTypeData* ctd = (CallTypeData*)pd; - if (ctd->has_arguments()) { - for (int i = 0; i < ctd->number_of_arguments(); i++) { - int off_b = in_bytes(ctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (ctd->has_return()) { - int off_b = in_bytes(ctd->return_type_offset()); + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int i = 0; i < ctd->number_of_arguments(); i++) { + int off_b = in_bytes(ctd->argument_type_offset(i)); intptr_t* cell = (intptr_t*)(pd->dp() + off_b); *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int i = 0; i < vctd->number_of_arguments(); i++) { - int off_b = in_bytes(vctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (vctd->has_return()) { - int off_b = in_bytes(vctd->return_type_offset()); + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int i = 0; i < vctd->number_of_arguments(); i++) { + int off_b = in_bytes(vctd->argument_type_offset(i)); intptr_t* cell = (intptr_t*)(pd->dp() + off_b); *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } - } - ParametersTypeData* p = mdo->parameters_type_data(); - if (p != nullptr) { - address p_dp = p->dp(); - for (int i = 0; i < p->number_of_parameters(); i++) { - int off_b = in_bytes(ParametersTypeData::type_offset(i)); - intptr_t* cell = (intptr_t*)(p_dp + off_b); + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } } - - // Patch class pointers (TypeEntries): null on failure, preserving status bits - int patched_classes = 0; - // Safety-first: skip repatching TypeEntries Klass* to avoid stale metadata crashes. - // Profiles still contribute counters without receiver types. - - // Patch method pointers in extra data: null on failure - int patched_methods = 0; - for (int i = 0; i < method_offs.length(); i++) { - int off = method_offs.at(i); - intptr_t* slot = ((intptr_t*)mdo->data_base()) + off; - Method* mv = method_vals.at(i); - *(Method**)slot = mv; // may be nullptr - patched_methods++; + ParametersTypeData* p = mdo->parameters_type_data(); + if (p != nullptr) { + address p_dp = p->dp(); + for (int i = 0; i < p->number_of_parameters(); i++) { + int off_b = in_bytes(ParametersTypeData::type_offset(i)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } } + } - // Final scrub: ensure all Klass* in type entries are live; null out otherwise - { - for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { - ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); - for (uint row = 0; row < rtd->row_limit(); row++) { - int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } + // Patch method pointers in extra data (if any): may be nullptr + int patched_methods = 0; + for (int i = 0; i < method_offs.length(); i++) { + int off = method_offs.at(i); + intptr_t* slot = ((intptr_t*)mdo->data_base()) + off; + Method* mv = method_vals.at(i); + *(Method**)slot = mv; + patched_methods++; + } + + // Final scrub: ensure all Klass* in type entries are live; null out otherwise + { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } - if (pd->is_CallTypeData()) { - CallTypeData* ctd = (CallTypeData*)pd; - if (ctd->has_arguments()) { - for (int i = 0; i < ctd->number_of_arguments(); i++) { - int off_b = in_bytes(ctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - } - if (ctd->has_return()) { - int off_b = in_bytes(ctd->return_type_offset()); + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int i = 0; i < ctd->number_of_arguments(); i++) { + int off_b = in_bytes(ctd->argument_type_offset(i)); intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int i = 0; i < vctd->number_of_arguments(); i++) { - int off_b = in_bytes(vctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } - if (vctd->has_return()) { - int off_b = in_bytes(vctd->return_type_offset()); + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int i = 0; i < vctd->number_of_arguments(); i++) { + int off_b = in_bytes(vctd->argument_type_offset(i)); intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } } - } - ParametersTypeData* p = mdo->parameters_type_data(); - if (p != nullptr) { - address p_dp = p->dp(); - for (int i = 0; i < p->number_of_parameters(); i++) { - int off_b = in_bytes(ParametersTypeData::type_offset(i)); - intptr_t* cell = (intptr_t*)(p_dp + off_b); + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr) { - if (!Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); } } } } - records_installed++; - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: installed %s %s %s (cells=%d, oops=%d, methods=%d)", - kname, mname, msig, (int)words.length(), patched_classes, patched_methods); + ParametersTypeData* p2 = mdo->parameters_type_data(); + if (p2 != nullptr) { + address p_dp = p2->dp(); + for (int i = 0; i < p2->number_of_parameters(); i++) { + int off_b = in_bytes(ParametersTypeData::type_offset(i)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + Klass* k = (Klass*)TypeEntries::klass_part(*cell); + if (k != nullptr && !Metaspace::contains((address)k)) { + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + } + + records_installed++; + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: installed %s %s %s (cells=%d, oops=%d, methods=%d)", + kname, mname, msig, (int)words.length(), 0, patched_methods); + } + return true; +} + +void MDOReplayLoad::load(JavaThread* THREAD) { + if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; + FILE* f = os::fopen(MDOReplayLoadFile, "r"); + if (f == nullptr) { return; } + ResourceMark rm; + log_info(compilation)("MDO replay: loading from %s", MDOReplayLoadFile); + int records_read = 0; + int records_installed = 0; + int size_mismatch = 0; + char word[4096]; + while (read_word(f, word, sizeof(word)) != nullptr) { + if (strcmp(word, "#") == 0) { // comment line: skip to EOL + int c; while ((c = fgetc(f)) != EOF && c != '\n') {} + continue; + } + if (strcmp(word, "MethodData") != 0) { + // skip line + int c; while ((c = fgetc(f)) != EOF && c != '\n') {} + continue; } + records_read++; + if (!load_one_mdo(f, THREAD, size_mismatch, records_installed)) break; } fclose(f); log_info(compilation)("MDO replay: loaded %d records (%d installed, %d size mismatch)", From b3e4143c6577cb780e8129f34b1e74f95c729206 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Tue, 21 Oct 2025 15:48:02 -0700 Subject: [PATCH 04/23] restore header, backedge, debug --- src/hotspot/share/services/mdoReplayDump.cpp | 3 +- src/hotspot/share/services/mdoReplayLoad.cpp | 53 +++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/services/mdoReplayDump.cpp b/src/hotspot/share/services/mdoReplayDump.cpp index 8ffa337e175..0ec7a614a16 100644 --- a/src/hotspot/share/services/mdoReplayDump.cpp +++ b/src/hotspot/share/services/mdoReplayDump.cpp @@ -24,7 +24,8 @@ static void dump_header(fileStream* out, Method* m, MethodData* mdo) { const int state = mdo->is_mature() ? 2 /*mature*/ : 1 /*immature*/; int invc = mdo->invocation_count(); if (invc == 0 && mdo->backedge_count() > 0) invc = 1; - out->print("MethodData %s %s %s %d %d", kname, mname, sig, state, invc); + int backc = mdo->backedge_count(); + out->print("MethodData %s %s %s %d %d %d", kname, mname, sig, state, invc, backc); } static void dump_orig(fileStream* out, MethodData* mdo) { diff --git a/src/hotspot/share/services/mdoReplayLoad.cpp b/src/hotspot/share/services/mdoReplayLoad.cpp index bcb06f4bbbd..14f6b56284b 100644 --- a/src/hotspot/share/services/mdoReplayLoad.cpp +++ b/src/hotspot/share/services/mdoReplayLoad.cpp @@ -41,9 +41,22 @@ static InstanceKlass* resolve_klass(const char* qname, TRAPS) { if (len >= 2 && s[0] == '"' && s[len-1] == '"') { s++; len -= 2; } Symbol* sym = SymbolTable::new_symbol(s, (int)len); // Use the Java system loader (same as ciReplay) instead of bootstrap-only - Handle loader(THREAD, SystemDictionary::java_system_loader()); + oop sys_loader_oop = SystemDictionary::java_system_loader(); + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: resolve_klass name=%s system_loader=%p", s, (void*)sys_loader_oop); + } + Handle loader(THREAD, sys_loader_oop); Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } + if (HAS_PENDING_EXCEPTION) { + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: resolve_klass failed for %s (exception), clearing and skipping", s); + } + CLEAR_PENDING_EXCEPTION; + return nullptr; + } + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: resolve_klass %s -> %p", s, (void*)k); + } if (k != nullptr && k->is_instance_klass()) return InstanceKlass::cast(k); return nullptr; } @@ -59,20 +72,23 @@ static bool parse_header(FILE* f, char* kname, size_t kcap, char* mname, size_t mncap, char* msig, size_t mscap, - int& state_out, int& invc_out) { + int& state_out, int& invc_out, int& backc_out) { char tmp[4096]; char* kw = read_word(f, tmp, sizeof(tmp)); if (kw == nullptr) return false; strncpy(kname, kw, kcap); char* mw = read_word(f, tmp, sizeof(tmp)); if (mw == nullptr) return false; strncpy(mname, mw, mncap); char* sw = read_word(f, tmp, sizeof(tmp)); if (sw == nullptr) return false; strncpy(msig, sw, mscap); if (!read_int(f, state_out)) return false; if (!read_int(f, invc_out)) return false; + if (!read_int(f, backc_out)) return false; return true; } -static bool parse_orig(FILE* f) { +static bool parse_orig(FILE* f, GrowableArray& out_bytes) { char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "orig") != 0) return false; int orig_len = 0; if (!read_int(f, orig_len)) return false; - for (int i = 0; i < orig_len; i++) { int b = 0; if (!read_int(f, b)) return false; } + for (int i = 0; i < orig_len; i++) { + int b = 0; if (!read_int(f, b)) return false; out_bytes.push((unsigned char)(b & 0xFF)); + } return true; } @@ -124,9 +140,10 @@ static bool load_one_mdo(FILE* f, JavaThread* THREAD, int& size_mismatch, int& records_installed) { - char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0; - if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc)) return false; - if (!parse_orig(f)) return false; + char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0, backc = 0; + if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc, backc)) return false; + GrowableArray orig_bytes(64); + if (!parse_orig(f, orig_bytes)) return false; GrowableArray words(16); if (!parse_data_words(f, words)) return false; GrowableArray class_offs(8); @@ -155,6 +172,23 @@ static bool load_one_mdo(FILE* f, // Reset escape analysis info to avoid inconsistent state mdo->clear_escape_info(); + // Restore invocation/backedge counters into MethodData and MethodCounters + { + // Set MDO counters (InvocationCounter objects) via atomic helpers + mdo->invocation_counter()->set((unsigned)invc); + mdo->backedge_counter()->set((unsigned)backc); + } + + // Restore CompilerCounters header bytes ("orig") + if (orig_bytes.length() > 0) { + size_t cc_offset = in_bytes(MethodData::trap_history_offset()) - in_bytes(MethodData::CompilerCounters::trap_history_offset()); + unsigned char* dst = (unsigned char*)((address)mdo + cc_offset); + size_t copy_len = orig_bytes.length(); + if (copy_len > sizeof(MethodData::CompilerCounters)) copy_len = sizeof(MethodData::CompilerCounters); + // Copy bytes back into the MDO's CompilerCounters + Copy::conjoint_jbytes((const char*)orig_bytes.adr_at(0), (char*)dst, (jlong)copy_len); + } + // Size check: must match int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); if ((int)words.length() != total_cells) { size_mismatch++; return true; } @@ -340,6 +374,9 @@ void MDOReplayLoad::load(JavaThread* THREAD) { if (f == nullptr) { return; } ResourceMark rm; log_info(compilation)("MDO replay: loading from %s", MDOReplayLoadFile); + if (log_is_enabled(Debug, compilation)) { + log_debug(compilation)("MDO replay: system_loader at load entry: %p", (void*)SystemDictionary::java_system_loader()); + } int records_read = 0; int records_installed = 0; int size_mismatch = 0; From 8569021403768cbe982c48c124f17c93682f411d Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 6 Nov 2025 01:31:41 -0800 Subject: [PATCH 05/23] profileCheckpoint --- src/hotspot/share/prims/jni.cpp | 4 +- src/hotspot/share/runtime/flags/allFlags.hpp | 4 +- src/hotspot/share/runtime/java.cpp | 12 +- src/hotspot/share/services/mdoReplayDump.cpp | 190 ----- src/hotspot/share/services/mdoReplayDump.hpp | 19 - src/hotspot/share/services/mdoReplayLoad.cpp | 402 ----------- src/hotspot/share/services/mdoReplayLoad.hpp | 16 - .../share/services/mdoReplay_globals.hpp | 30 - .../share/services/profileCheckpoint.cpp | 648 ++++++++++++++++++ .../share/services/profileCheckpoint.hpp | 112 +++ .../services/profileCheckpoint_globals.hpp | 36 + 11 files changed, 806 insertions(+), 667 deletions(-) delete mode 100644 src/hotspot/share/services/mdoReplayDump.cpp delete mode 100644 src/hotspot/share/services/mdoReplayDump.hpp delete mode 100644 src/hotspot/share/services/mdoReplayLoad.cpp delete mode 100644 src/hotspot/share/services/mdoReplayLoad.hpp delete mode 100644 src/hotspot/share/services/mdoReplay_globals.hpp create mode 100644 src/hotspot/share/services/profileCheckpoint.cpp create mode 100644 src/hotspot/share/services/profileCheckpoint.hpp create mode 100644 src/hotspot/share/services/profileCheckpoint_globals.hpp diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 503f525170d..02aaa0286d5 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -93,7 +93,7 @@ #include "utilities/events.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" -#include "services/mdoReplayLoad.hpp" +#include "services/profileCheckpoint.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmciCompiler.hpp" #endif @@ -3621,7 +3621,7 @@ static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) { if (ReplayCompiles) ciReplay::replay(thread); if (LoadMDOAtStartup && MDOReplayLoadFile != nullptr) { - MDOReplayLoad::load(thread); + ProfileCheckpoint::load(thread); } #ifdef ASSERT diff --git a/src/hotspot/share/runtime/flags/allFlags.hpp b/src/hotspot/share/runtime/flags/allFlags.hpp index 650e443e0c1..1b18384ace9 100644 --- a/src/hotspot/share/runtime/flags/allFlags.hpp +++ b/src/hotspot/share/runtime/flags/allFlags.hpp @@ -31,7 +31,7 @@ #include "gc/shared/tlab_globals.hpp" #include "runtime/flags/debug_globals.hpp" #include "runtime/globals.hpp" -#include "services/mdoReplay_globals.hpp" +#include "services/profileCheckpoint_globals.hpp" // Put LP64/ARCH/JVMCI/COMPILER1/COMPILER2 at the top, // as they are processed by jvmFlag.cpp in that order. @@ -90,7 +90,7 @@ range, \ constraint) \ \ - MDOREPLAY_FLAGS( \ + PROFILECHECKPOINT_FLAGS( \ develop, \ develop_pd, \ product, \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 58f1ec1dc56..5df4f4eefe2 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -85,7 +85,7 @@ #include "utilities/macros.hpp" #include "utilities/vmError.hpp" #include "nmt/memTag.hpp" -#include "services/mdoReplayDump.hpp" +#include "services/profileCheckpoint.hpp" #ifdef COMPILER1 #include "c1/c1_Compiler.hpp" #include "c1/c1_Runtime1.hpp" @@ -304,13 +304,11 @@ void print_statistics() { print_method_profiling_data(); - // Profiles-only replay dumper (stub): log intent at exit + // Binary MDO replay dumper: write MDOX file at exit if (DumpMDOAtExit && MDOReplayDumpFile != nullptr) { log_info(compilation)("MDO replay: will dump profiles to %s", MDOReplayDumpFile); - fileStream fs(MDOReplayDumpFile, "w"); + fileStream fs(MDOReplayDumpFile, "wb"); if (fs.is_open()) { - // Emit a minimal header - fs.print_cr("# mdo-replay (MethodData only)"); // Run at a safepoint to avoid concurrent MDO mutations during dump class VM_MDOReplayDump : public VM_Operation { fileStream* _out; @@ -318,7 +316,9 @@ void print_statistics() { VM_MDOReplayDump(fileStream* out) : _out(out) {} virtual VMOp_Type type() const { return VMOp_GC_HeapInspection; } virtual void doit() { - MDOReplayDump::dump_all(_out); + ProfileCheckpoint::Writer* dummy = nullptr; // include dependency anchor + (void)dummy; + ProfileCheckpoint::dump_to_stream(_out); } } op(&fs); VMThread::execute(&op); diff --git a/src/hotspot/share/services/mdoReplayDump.cpp b/src/hotspot/share/services/mdoReplayDump.cpp deleted file mode 100644 index 0ec7a614a16..00000000000 --- a/src/hotspot/share/services/mdoReplayDump.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "classfile/systemDictionary.hpp" -#include "services/mdoReplay_globals.hpp" -#include "oops/methodData.hpp" -#include "oops/method.hpp" -#include "services/mdoReplayDump.hpp" -#include "utilities/ostream.hpp" -#include "utilities/copy.hpp" -#include "logging/log.hpp" - -// Local collector shared with java.cpp style -static GrowableArray* g_methods; -static void collect_with_mdo(Method* m) { - if (m != nullptr && m->method_data() != nullptr) { - g_methods->push(m); - } -} - -static void dump_header(fileStream* out, Method* m, MethodData* mdo) { - // Header: MethodData - InstanceKlass* holder = m->method_holder(); - const char* kname = holder->name()->as_quoted_ascii(); - const char* mname = m->name()->as_quoted_ascii(); - const char* sig = m->signature()->as_quoted_ascii(); - const int state = mdo->is_mature() ? 2 /*mature*/ : 1 /*immature*/; - int invc = mdo->invocation_count(); - if (invc == 0 && mdo->backedge_count() > 0) invc = 1; - int backc = mdo->backedge_count(); - out->print("MethodData %s %s %s %d %d %d", kname, mname, sig, state, invc, backc); -} - -static void dump_orig(fileStream* out, MethodData* mdo) { - - // orig header bytes: copy MethodData::CompilerCounters - size_t cc_offset = in_bytes(MethodData::trap_history_offset()) - in_bytes(MethodData::CompilerCounters::trap_history_offset()); - const unsigned char* orig = (const unsigned char*)((address)mdo + cc_offset); - int orig_len = (int)sizeof(MethodData::CompilerCounters); - out->print(" orig %d", orig_len); - for (int i = 0; i < orig_len; i++) { - out->print(" %d", orig[i]); - } -} - -static void dump_cells(fileStream* out, MethodData* mdo) { - // Dump raw data words: data + extra_data - int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - out->print(" data %d", elements); - intptr_t* base = (intptr_t*)mdo->data_base(); - for (int i = 0; i < elements; i++) { - out->print(" 0x%zx", base[i]); - } -} - -// Small helpers to avoid duplication when collecting Klass* cells -static void add_klass_cell_if_in_bounds(MethodData* mdo, - address base_dp, - int off_bytes, - Klass* k, - int elements, - GrowableArray& class_offsets, - GrowableArray& class_klasses) { - if (k == nullptr) return; - int di_bytes = mdo->dp_to_di(base_dp + off_bytes); - int off_cells = di_bytes / (int)sizeof(intptr_t); - if (0 <= off_cells && off_cells < elements) { - class_offsets.push(off_cells); - class_klasses.push(k); - } -} - -static void add_typeentry_if_present(MethodData* mdo, - address base_dp, - int off_bytes, - int elements, - GrowableArray& class_offsets, - GrowableArray& class_klasses) { - intptr_t val = *(intptr_t*)(base_dp + off_bytes); - Klass* k = TypeEntries::valid_klass(val); - add_klass_cell_if_in_bounds(mdo, base_dp, off_bytes, k, elements, class_offsets, class_klasses); -} - - - -static void collect_oops_and_methods(MethodData* mdo, - GrowableArray& class_offsets, - GrowableArray& class_klasses, - GrowableArray& method_offsets, - GrowableArray& method_targets) { - const int elements = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - if (pd->is_VirtualCallData()) { - VirtualCallData* vcd = pd->as_VirtualCallData(); - for (uint row = 0; row < vcd->row_limit(); row++) { - Klass* k = vcd->receiver(row); - add_klass_cell_if_in_bounds(mdo, pd->dp(), in_bytes(VirtualCallData::receiver_offset(row)), k, - elements, class_offsets, class_klasses); - } - } - if (pd->is_CallTypeData()) { - CallTypeData* ctd = (CallTypeData*)pd; - if (ctd->has_arguments()) { - for (int i = 0; i < ctd->number_of_arguments(); i++) { - add_typeentry_if_present(mdo, pd->dp(), in_bytes(ctd->argument_type_offset(i)), - elements, class_offsets, class_klasses); - } - } - if (ctd->has_return()) { - add_typeentry_if_present(mdo, pd->dp(), in_bytes(ctd->return_type_offset()), - elements, class_offsets, class_klasses); - } - } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int i = 0; i < vctd->number_of_arguments(); i++) { - add_typeentry_if_present(mdo, pd->dp(), in_bytes(vctd->argument_type_offset(i)), - elements, class_offsets, class_klasses); - } - } - if (vctd->has_return()) { - add_typeentry_if_present(mdo, pd->dp(), in_bytes(vctd->return_type_offset()), - elements, class_offsets, class_klasses); - } - } - } - if (mdo->parameters_type_data() != nullptr) { - ParametersTypeData* p = mdo->parameters_type_data(); - address p_dp = p->dp(); - for (int i = 0; i < p->number_of_parameters(); i++) { - add_typeentry_if_present(mdo, p_dp, in_bytes(ParametersTypeData::type_offset(i)), - elements, class_offsets, class_klasses); - } - } -} - -static void write_oops_and_methods(fileStream* out, - GrowableArray& class_offsets, - GrowableArray& class_klasses, - GrowableArray& method_offsets, - GrowableArray& method_targets) { - out->print(" oops %d", class_offsets.length()); - for (int i = 0; i < class_offsets.length(); i++) { - Klass* k = class_klasses.at(i); - const char* cname = k->name()->as_quoted_ascii(); - out->print(" %d %s", class_offsets.at(i), cname); - } - out->print(" methods %d", method_offsets.length()); - for (int i = 0; i < method_offsets.length(); i++) { - Method* tm = method_targets.at(i); - InstanceKlass* th = tm->method_holder(); - out->print(" %d %s %s %s", method_offsets.at(i), th->name()->as_quoted_ascii(), tm->name()->as_quoted_ascii(), tm->signature()->as_quoted_ascii()); - } - out->cr(); -} - -static void dump_one_mdo(fileStream* out, Method* m, MethodData* mdo) { - dump_header(out, m, mdo); - dump_orig(out, mdo); - dump_cells(out, mdo); - GrowableArray class_offsets(16); - GrowableArray class_klasses(16); - GrowableArray method_offsets(4); - GrowableArray method_targets(4); - collect_oops_and_methods(mdo, class_offsets, class_klasses, method_offsets, method_targets); - // Note: we intentionally skip dumping extra_data trap records to avoid tag mismatches on load - write_oops_and_methods(out, class_offsets, class_klasses, method_offsets, method_targets); -} - -static const char* dump_path() { return MDOReplayDumpFile; } - -void MDOReplayDump::dump_all(fileStream* out) { - ResourceMark rm; - log_info(compilation)("MDO replay: dumping MDOs"); - GrowableArray methods(1024); - g_methods = &methods; - SystemDictionary::methods_do(collect_with_mdo); - g_methods = nullptr; - int emitted = 0; - for (int i = 0; i < methods.length(); i++) { - Method* m = methods.at(i); - MethodData* mdo = m->method_data(); - if (mdo == nullptr) continue; - if (!mdo->is_mature()) continue; // dump only stable profiles - MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); - dump_one_mdo(out, m, mdo); - emitted++; - } - log_info(compilation)("MDO replay: dumped %d MDOs", emitted); -} - - diff --git a/src/hotspot/share/services/mdoReplayDump.hpp b/src/hotspot/share/services/mdoReplayDump.hpp deleted file mode 100644 index 642f02f8a26..00000000000 --- a/src/hotspot/share/services/mdoReplayDump.hpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Minimal MDO replay dumper helper. - */ -#ifndef SHARE_SERVICES_MDOREPLAYDUMP_HPP -#define SHARE_SERVICES_MDOREPLAYDUMP_HPP - -#include "utilities/globalDefinitions.hpp" - -class fileStream; - -class MDOReplayDump { - public: - // Dump ciMethodData-style records for all methods with MDOs - static void dump_all(fileStream* out); -}; - -#endif // SHARE_SERVICES_MDOREPLAYDUMP_HPP - - diff --git a/src/hotspot/share/services/mdoReplayLoad.cpp b/src/hotspot/share/services/mdoReplayLoad.cpp deleted file mode 100644 index 14f6b56284b..00000000000 --- a/src/hotspot/share/services/mdoReplayLoad.cpp +++ /dev/null @@ -1,402 +0,0 @@ -#include "classfile/systemDictionary.hpp" -#include "classfile/symbolTable.hpp" -#include "oops/instanceKlass.hpp" -#include "oops/method.hpp" -#include "oops/methodData.hpp" -#include "runtime/javaThread.hpp" -#include "runtime/handles.inline.hpp" -#include "runtime/os.hpp" -#include "services/mdoReplayLoad.hpp" -#include "logging/log.hpp" -#include "services/mdoReplay_globals.hpp" -#include "utilities/ostream.hpp" -#include "utilities/copy.hpp" -#include "memory/metaspace.hpp" - -// Very simple text parser matching the dump format in mdoReplayDump.cpp. -// Grammar (one line per MDO): -// MethodData orig data oops ( )... methods ( )... - -static char* read_word(FILE* f, char* buf, int buflen) { - int c; - do { c = fgetc(f); if (c == EOF) return nullptr; } while (c == ' ' || c == '\t' || c == '\n' || c == '\r'); - int i = 0; buf[i++] = (char)c; - while (i < buflen - 1) { c = fgetc(f); if (c == EOF || c == ' ' || c == '\t' || c == '\n' || c == '\r') break; buf[i++] = (char)c; } - buf[i] = '\0'; - return buf; -} - -static bool read_int(FILE* f, int& out) { - char tmp[64]; if (read_word(f, tmp, sizeof(tmp)) == nullptr) return false; out = atoi(tmp); return true; -} - -static bool read_hex_word(FILE* f, uintptr_t& out) { - char tmp[64]; if (read_word(f, tmp, sizeof(tmp)) == nullptr) return false; out = (uintptr_t)strtoull(tmp, nullptr, 16); return true; -} - -static InstanceKlass* resolve_klass(const char* qname, TRAPS) { - // qname is quoted ascii; strip quotes if present - const char* s = qname; - size_t len = strlen(s); - if (len >= 2 && s[0] == '"' && s[len-1] == '"') { s++; len -= 2; } - Symbol* sym = SymbolTable::new_symbol(s, (int)len); - // Use the Java system loader (same as ciReplay) instead of bootstrap-only - oop sys_loader_oop = SystemDictionary::java_system_loader(); - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: resolve_klass name=%s system_loader=%p", s, (void*)sys_loader_oop); - } - Handle loader(THREAD, sys_loader_oop); - Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); - if (HAS_PENDING_EXCEPTION) { - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: resolve_klass failed for %s (exception), clearing and skipping", s); - } - CLEAR_PENDING_EXCEPTION; - return nullptr; - } - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: resolve_klass %s -> %p", s, (void*)k); - } - if (k != nullptr && k->is_instance_klass()) return InstanceKlass::cast(k); - return nullptr; -} - -static Method* resolve_method(InstanceKlass* ik, const char* mname_q, const char* sig_q) { - const char* mn = mname_q; size_t ln = strlen(mn); if (ln >= 2 && mn[0]=='"' && mn[ln-1]=='"') { mn++; ln-=2; } - const char* sg = sig_q; size_t ls = strlen(sg); if (ls >= 2 && sg[0]=='"' && sg[ls-1]=='"') { sg++; ls-=2; } - return ik->find_method(SymbolTable::new_symbol(mn, (int)ln), SymbolTable::new_symbol(sg, (int)ls)); -} - -// ---------------- Parsing helpers ---------------- -static bool parse_header(FILE* f, - char* kname, size_t kcap, - char* mname, size_t mncap, - char* msig, size_t mscap, - int& state_out, int& invc_out, int& backc_out) { - char tmp[4096]; - char* kw = read_word(f, tmp, sizeof(tmp)); if (kw == nullptr) return false; strncpy(kname, kw, kcap); - char* mw = read_word(f, tmp, sizeof(tmp)); if (mw == nullptr) return false; strncpy(mname, mw, mncap); - char* sw = read_word(f, tmp, sizeof(tmp)); if (sw == nullptr) return false; strncpy(msig, sw, mscap); - if (!read_int(f, state_out)) return false; - if (!read_int(f, invc_out)) return false; - if (!read_int(f, backc_out)) return false; - return true; -} - -static bool parse_orig(FILE* f, GrowableArray& out_bytes) { - char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "orig") != 0) return false; - int orig_len = 0; if (!read_int(f, orig_len)) return false; - for (int i = 0; i < orig_len; i++) { - int b = 0; if (!read_int(f, b)) return false; out_bytes.push((unsigned char)(b & 0xFF)); - } - return true; -} - -static bool parse_data_words(FILE* f, GrowableArray& words) { - char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "data") != 0) return false; - int nwords = 0; if (!read_int(f, nwords)) return false; - for (int i = 0; i < nwords; i++) { uintptr_t w = 0; if (!read_hex_word(f, w)) return false; words.push(w); } - return true; -} - -static bool parse_oops(FILE* f, - GrowableArray& class_offs, - GrowableArray& class_vals, - JavaThread* THREAD) { - char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "oops") != 0) return false; - int nclasses = 0; if (!read_int(f, nclasses)) return false; - for (int i = 0; i < nclasses; i++) { - int off = 0; if (!read_int(f, off)) return false; - char cname[4096]; if (read_word(f, cname, sizeof(cname)) == nullptr) return false; - InstanceKlass* ck = resolve_klass(cname, THREAD); - class_offs.push(off); - class_vals.push(ck); - } - return true; -} - -static bool parse_methods(FILE* f, - GrowableArray& method_offs, - GrowableArray& method_vals, - JavaThread* THREAD) { - char tag[32]; if (read_word(f, tag, sizeof(tag)) == nullptr || strcmp(tag, "methods") != 0) return false; - int nmethods = 0; if (!read_int(f, nmethods)) return false; - for (int i = 0; i < nmethods; i++) { - int off = 0; if (!read_int(f, off)) return false; - char mkname[4096], mmname[4096], mmsig[4096]; - if (read_word(f, mkname, sizeof(mkname)) == nullptr) return false; - if (read_word(f, mmname, sizeof(mmname)) == nullptr) return false; - if (read_word(f, mmsig, sizeof(mmsig)) == nullptr) return false; - InstanceKlass* mik = resolve_klass(mkname, THREAD); - Method* mm = (mik == nullptr) ? nullptr : resolve_method(mik, mmname, mmsig); - method_offs.push(off); - method_vals.push(mm); - } - return true; -} - -// Load a single MethodData record; return false only on parse failure (break), true otherwise -static bool load_one_mdo(FILE* f, - JavaThread* THREAD, - int& size_mismatch, - int& records_installed) { - char kname[4096], mname[4096], msig[4096]; int state = 0, invc = 0, backc = 0; - if (!parse_header(f, kname, sizeof(kname), mname, sizeof(mname), msig, sizeof(msig), state, invc, backc)) return false; - GrowableArray orig_bytes(64); - if (!parse_orig(f, orig_bytes)) return false; - GrowableArray words(16); - if (!parse_data_words(f, words)) return false; - GrowableArray class_offs(8); - GrowableArray class_vals(8); - if (!parse_oops(f, class_offs, class_vals, THREAD)) return false; - GrowableArray method_offs(4); - GrowableArray method_vals(4); - if (!parse_methods(f, method_offs, method_vals, THREAD)) return false; - - // Resolve target Method and ensure MDO exists - InstanceKlass* holder = resolve_klass(kname, THREAD); - if (holder == nullptr) return true; - Method* target = resolve_method(holder, mname, msig); - if (target == nullptr) return true; - // Ensure the holder is linked before creating or touching profiling data - holder->link_class(THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return true; } - if (target->method_data() == nullptr) { - methodHandle mh(THREAD, target); - target->build_profiling_method_data(mh, CHECK_(true)); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return true; } - } - MethodData* mdo = target->method_data(); - if (mdo == nullptr) return true; - - // Reset escape analysis info to avoid inconsistent state - mdo->clear_escape_info(); - - // Restore invocation/backedge counters into MethodData and MethodCounters - { - // Set MDO counters (InvocationCounter objects) via atomic helpers - mdo->invocation_counter()->set((unsigned)invc); - mdo->backedge_counter()->set((unsigned)backc); - } - - // Restore CompilerCounters header bytes ("orig") - if (orig_bytes.length() > 0) { - size_t cc_offset = in_bytes(MethodData::trap_history_offset()) - in_bytes(MethodData::CompilerCounters::trap_history_offset()); - unsigned char* dst = (unsigned char*)((address)mdo + cc_offset); - size_t copy_len = orig_bytes.length(); - if (copy_len > sizeof(MethodData::CompilerCounters)) copy_len = sizeof(MethodData::CompilerCounters); - // Copy bytes back into the MDO's CompilerCounters - Copy::conjoint_jbytes((const char*)orig_bytes.adr_at(0), (char*)dst, (jlong)copy_len); - } - - // Size check: must match - int total_cells = (mdo->data_size() + mdo->extra_data_size()) / (int)sizeof(intptr_t); - if ((int)words.length() != total_cells) { size_mismatch++; return true; } - - // Copy full cells (data + extra) - { -#ifdef _LP64 - Copy::conjoint_jlongs_atomic((jlong*)words.adr_at(0), (jlong*)mdo->data_base(), words.length()); -#else - Copy::conjoint_jints_atomic((jint*)words.adr_at(0), (jint*)mdo->data_base(), words.length()); -#endif - } - - // Zero-out the extra-data trap/arg-info slice and synthesize minimal ArgInfoData - { - DataLayout* extra_base = mdo->extra_data_base(); - DataLayout* args_limit = mdo->args_data_limit(); - if (args_limit > extra_base) { - size_t bytes = ((address)args_limit) - ((address)extra_base); - memset((void*)extra_base, 0, bytes); - } - int nparams = target->size_of_parameters(); - if (nparams > 0) { - const int cell_size = (int)sizeof(intptr_t); - const int header_bytes = DataLayout::header_size_in_bytes(); - const size_t total_bytes = (size_t)header_bytes + (size_t)(nparams + 1) * (size_t)cell_size; - if (((address)args_limit) >= ((address)extra_base) + total_bytes) { - address arg_info_start = ((address)args_limit) - total_bytes; - memset((void*)arg_info_start, 0, total_bytes); - DataLayout* aid = (DataLayout*)arg_info_start; - aid->set_header(0); - *(u1*)((address)aid + in_bytes(DataLayout::tag_offset())) = (u1)DataLayout::arg_info_data_tag; - aid->set_cell_at(0, (intptr_t)nparams); - } - } - } - - // Sanitize: clear all type-entry Klass* pointers to avoid stale addresses; preserve status bits - { - for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { - ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); - for (uint row = 0; row < rtd->row_limit(); row++) { - int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (pd->is_CallTypeData()) { - CallTypeData* ctd = (CallTypeData*)pd; - if (ctd->has_arguments()) { - for (int i = 0; i < ctd->number_of_arguments(); i++) { - int off_b = in_bytes(ctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (ctd->has_return()) { - int off_b = in_bytes(ctd->return_type_offset()); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int i = 0; i < vctd->number_of_arguments(); i++) { - int off_b = in_bytes(vctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - if (vctd->has_return()) { - int off_b = in_bytes(vctd->return_type_offset()); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - ParametersTypeData* p = mdo->parameters_type_data(); - if (p != nullptr) { - address p_dp = p->dp(); - for (int i = 0; i < p->number_of_parameters(); i++) { - int off_b = in_bytes(ParametersTypeData::type_offset(i)); - intptr_t* cell = (intptr_t*)(p_dp + off_b); - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - - // Patch method pointers in extra data (if any): may be nullptr - int patched_methods = 0; - for (int i = 0; i < method_offs.length(); i++) { - int off = method_offs.at(i); - intptr_t* slot = ((intptr_t*)mdo->data_base()) + off; - Method* mv = method_vals.at(i); - *(Method**)slot = mv; - patched_methods++; - } - - // Final scrub: ensure all Klass* in type entries are live; null out otherwise - { - for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { - if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { - ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); - for (uint row = 0; row < rtd->row_limit(); row++) { - int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - if (pd->is_CallTypeData()) { - CallTypeData* ctd = (CallTypeData*)pd; - if (ctd->has_arguments()) { - for (int i = 0; i < ctd->number_of_arguments(); i++) { - int off_b = in_bytes(ctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - if (ctd->has_return()) { - int off_b = in_bytes(ctd->return_type_offset()); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int i = 0; i < vctd->number_of_arguments(); i++) { - int off_b = in_bytes(vctd->argument_type_offset(i)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - if (vctd->has_return()) { - int off_b = in_bytes(vctd->return_type_offset()); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - } - ParametersTypeData* p2 = mdo->parameters_type_data(); - if (p2 != nullptr) { - address p_dp = p2->dp(); - for (int i = 0; i < p2->number_of_parameters(); i++) { - int off_b = in_bytes(ParametersTypeData::type_offset(i)); - intptr_t* cell = (intptr_t*)(p_dp + off_b); - Klass* k = (Klass*)TypeEntries::klass_part(*cell); - if (k != nullptr && !Metaspace::contains((address)k)) { - *cell = TypeEntries::with_status((Klass*)nullptr, *cell); - } - } - } - } - - records_installed++; - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: installed %s %s %s (cells=%d, oops=%d, methods=%d)", - kname, mname, msig, (int)words.length(), 0, patched_methods); - } - return true; -} - -void MDOReplayLoad::load(JavaThread* THREAD) { - if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; - FILE* f = os::fopen(MDOReplayLoadFile, "r"); - if (f == nullptr) { return; } - ResourceMark rm; - log_info(compilation)("MDO replay: loading from %s", MDOReplayLoadFile); - if (log_is_enabled(Debug, compilation)) { - log_debug(compilation)("MDO replay: system_loader at load entry: %p", (void*)SystemDictionary::java_system_loader()); - } - int records_read = 0; - int records_installed = 0; - int size_mismatch = 0; - char word[4096]; - while (read_word(f, word, sizeof(word)) != nullptr) { - if (strcmp(word, "#") == 0) { // comment line: skip to EOL - int c; while ((c = fgetc(f)) != EOF && c != '\n') {} - continue; - } - if (strcmp(word, "MethodData") != 0) { - // skip line - int c; while ((c = fgetc(f)) != EOF && c != '\n') {} - continue; - } - records_read++; - if (!load_one_mdo(f, THREAD, size_mismatch, records_installed)) break; - } - fclose(f); - log_info(compilation)("MDO replay: loaded %d records (%d installed, %d size mismatch)", - records_read, records_installed, size_mismatch); -} - - diff --git a/src/hotspot/share/services/mdoReplayLoad.hpp b/src/hotspot/share/services/mdoReplayLoad.hpp deleted file mode 100644 index 1a21046e00e..00000000000 --- a/src/hotspot/share/services/mdoReplayLoad.hpp +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Load MethodData profiles from a replay file and install into live MDOs. - */ -#ifndef SHARE_SERVICES_MDOREPLAYLOAD_HPP -#define SHARE_SERVICES_MDOREPLAYLOAD_HPP - -class JavaThread; - -class MDOReplayLoad { - public: - static void load(JavaThread* thread); -}; - -#endif // SHARE_SERVICES_MDOREPLAYLOAD_HPP - - diff --git a/src/hotspot/share/services/mdoReplay_globals.hpp b/src/hotspot/share/services/mdoReplay_globals.hpp deleted file mode 100644 index e07153ec39c..00000000000 --- a/src/hotspot/share/services/mdoReplay_globals.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP -#define SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP - -#include "runtime/globals_shared.hpp" - -// Defines MDO replay/export flags. Only flags here; included via allFlags.hpp. -#define MDOREPLAY_FLAGS(develop, \ - develop_pd, \ - product, \ - product_pd, \ - range, \ - constraint) \ - \ - product(ccstr, MDOReplayDumpFile, nullptr, DIAGNOSTIC, \ - "File for exporting profiles") \ - \ - product(ccstr, MDOReplayLoadFile, nullptr, DIAGNOSTIC, \ - "File for importing profiles") \ - \ - product(bool, DumpMDOAtExit, false, DIAGNOSTIC, \ - "Dump MDOs and counters to MDOReplayDumpFile at VM exit") \ - \ - product(bool, LoadMDOAtStartup, false, DIAGNOSTIC, \ - "Load MDOs and counters from MDOReplayLoadFile at VM startup") \ - -DECLARE_FLAGS(MDOREPLAY_FLAGS) - -#endif // SHARE_SERVICES_MDOREPLAY_GLOBALS_HPP - - diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp new file mode 100644 index 00000000000..a01375df04c --- /dev/null +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -0,0 +1,648 @@ +#include "services/profileCheckpoint.hpp" +#include "utilities/ostream.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/symbolTable.hpp" +#include "services/profileCheckpoint_globals.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/methodData.hpp" +#include "oops/method.hpp" +#include "oops/methodCounters.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "logging/log.hpp" +#include "utilities/copy.hpp" +#include "runtime/os.hpp" +#include "runtime/globals.hpp" +#include +#include + +// ---- helpers (extracted for readability) ---- + +static bool copy_mdo_payload(MethodData* dst_mdo, const char* src_bytes, u4 src_size) { + if (dst_mdo == nullptr) return false; + if ((u4)dst_mdo->size_in_bytes() != src_size) return false; + address dst_base = dst_mdo->data_base(); + const size_t data_sz = (size_t)dst_mdo->data_size(); + const size_t data_off = (size_t)(dst_base - (address)dst_mdo); + const char* src = src_bytes + data_off; + Copy::conjoint_jbytes(src, (char*)dst_base, (jlong)data_sz); + return true; +} + +static void sanitize_type_entries(MethodData* mdo) { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int ai = 0; ai < ctd->number_of_arguments(); ai++) { + int off_b = in_bytes(ctd->argument_type_offset(ai)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int ai = 0; ai < vctd->number_of_arguments(); ai++) { + int off_b = in_bytes(vctd->argument_type_offset(ai)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } + } + if (ParametersTypeData* p = mdo->parameters_type_data()) { + address p_dp = p->dp(); + for (int pi = 0; pi < p->number_of_parameters(); pi++) { + int off_b = in_bytes(ParametersTypeData::type_offset(pi)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + *cell = TypeEntries::with_status((Klass*)nullptr, *cell); + } + } +} + +struct FixupText { + u4 offset_in_mdo; + ProfileCheckpoint::FixupKind kind; + const char* name; // klass internal name +}; + +static void collect_type_fixups_text(MethodData* mdo, GrowableArray& out_fixups) { + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { + if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { + ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); + for (uint row = 0; row < rtd->row_limit(); row++) { + int off_b = in_bytes(ReceiverTypeData::receiver_offset(row)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } + if (pd->is_CallTypeData()) { + CallTypeData* ctd = (CallTypeData*)pd; + if (ctd->has_arguments()) { + for (int ai = 0; ai < ctd->number_of_arguments(); ai++) { + int off_b = in_bytes(ctd->argument_type_offset(ai)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } + if (ctd->has_return()) { + int off_b = in_bytes(ctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int ai = 0; ai < vctd->number_of_arguments(); ai++) { + int off_b = in_bytes(vctd->argument_type_offset(ai)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } + } + if (ParametersTypeData* p = mdo->parameters_type_data()) { + address p_dp = p->dp(); + for (int pi = 0; pi < p->number_of_parameters(); pi++) { + int off_b = in_bytes(ParametersTypeData::type_offset(pi)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + FixupText fx; fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + out_fixups.append(fx); + } + } + } +} + +static void apply_fixups(MethodData* mdo, + const ProfileCheckpoint::Fixup* fixups, + u4 fixup_count, + GrowableArray& symtab, + ProfileCheckpoint::LoaderId loader, + TRAPS) { + Handle loader_h; + switch (loader) { + case ProfileCheckpoint::LoaderId::BOOT: loader_h = Handle(); break; + case ProfileCheckpoint::LoaderId::PLATFORM: loader_h = Handle(THREAD, SystemDictionary::java_platform_loader()); break; + case ProfileCheckpoint::LoaderId::APP: loader_h = Handle(THREAD, SystemDictionary::java_system_loader()); break; + default: loader_h = Handle(); break; + } + for (u4 fi = 0; fi < fixup_count; fi++) { + const ProfileCheckpoint::Fixup& fx = fixups[fi]; + if (fx.kind != ProfileCheckpoint::FixupKind::KLASS) { + log_debug(compilation)("MDO checkpoint: unsupported fixup kind=%d at off=%u sym_id=%u", + (int)fx.kind, fx.offset_in_mdo, fx.target.id); + continue; + } + if ((int)fx.target.id < 0 || (int)fx.target.id >= symtab.length()) { + log_debug(compilation)("MDO checkpoint: invalid sym_id=%u (symtab_len=%d) at off=%u", + fx.target.id, symtab.length(), fx.offset_in_mdo); + continue; + } + const char* cname = symtab.at((int)fx.target.id); + Symbol* sym = SymbolTable::new_symbol(cname); + Klass* k = SystemDictionary::resolve_or_fail(sym, loader_h, true, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; k = nullptr; } + if (k != nullptr && k->is_instance_klass()) { + address cell_addr = (address)mdo + fx.offset_in_mdo; + intptr_t* cell = (intptr_t*)cell_addr; + *cell = TypeEntries::with_status(InstanceKlass::cast(k), *cell); + } else { + log_debug(compilation)("MDO checkpoint: fixup unresolved %s (loader=%d) at off=%u", + cname, (int)loader, fx.offset_in_mdo); + } + } +} + +static bool write_exact(fileStream* out, const void* p, size_t n) { + if (n == 0) return true; + out->write((const char*)p, n); + return true; +} + +static bool read_exact(FILE* in, void* p, size_t n) { + return ::fread(p, 1, n, in) == n; +} + +static bool write_u4(fileStream* out, u4 v) { + return write_exact(out, &v, sizeof(v)); +} + +static bool read_u4(FILE* in, u4& v) { + return read_exact(in, &v, sizeof(v)); +} + +static bool write_str(fileStream* out, const char* s) { + u4 len = (u4)strlen(s); + if (!write_u4(out, len)) return false; + return write_exact(out, s, len); +} + +static char* read_str(FILE* in) { + u4 len = 0; + if (!read_u4(in, len)) return nullptr; + char* buf = (char*)os::malloc(len + 1, mtInternal); + if (buf == nullptr) return nullptr; + if (len > 0 && !read_exact(in, buf, len)) { os::free(buf); return nullptr; } + buf[len] = '\0'; + return buf; +} + +static bool write_u2(fileStream* out, u2 v) { return write_exact(out, &v, sizeof(v)); } +static bool read_u2(FILE* in, u2& v) { return read_exact(in, &v, sizeof(v)); } + +bool ProfileCheckpoint::Header::write(fileStream* out, const Header& h) { + if (!write_exact(out, h.magic, sizeof(h.magic))) return false; + if (!write_u4(out, h.version)) return false; + if (!write_u2(out, h.pointer_size)) return false; + if (!write_u2(out, h.endianness)) return false; + if (!write_exact(out, &h.layout, sizeof(h.layout))) return false; + if (!write_u4(out, h.sym_count)) return false; + if (!write_u4(out, h.rec_count)) return false; + return true; +} + +bool ProfileCheckpoint::Header::read(FILE* in, Header& h) { + if (!read_exact(in, h.magic, sizeof(h.magic))) return false; + if (h.magic[0] != 'M' || h.magic[1] != 'D' || h.magic[2] != 'O' || h.magic[3] != 'X') return false; + if (!read_u4(in, h.version)) return false; + if (h.version != 3u) return false; + if (!read_u2(in, h.pointer_size)) return false; + if (!read_u2(in, h.endianness)) return false; + if (!read_exact(in, &h.layout, sizeof(h.layout))) return false; + if (!read_u4(in, h.sym_count)) return false; + if (!read_u4(in, h.rec_count)) return false; + return true; +} + +static u2 detect_endianness() { + union { u4 v; u1 b[4]; } u; u.v = 1; + return (u.b[0] == 1) ? (u2)0 : (u2)1; +} + +void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count) { + h.magic[0] = 'M'; h.magic[1] = 'D'; h.magic[2] = 'O'; h.magic[3] = 'X'; + h.version = 3u; + h.pointer_size = (u2)sizeof(void*); + h.endianness = detect_endianness(); + h.layout.type_profile_level = (uint32_t)TypeProfileLevel; + h.layout.type_profile_args_limit = (int32_t)TypeProfileArgsLimit; + h.layout.type_profile_parms_limit = (int32_t)TypeProfileParmsLimit; + h.layout.type_profile_width = (int64_t)TypeProfileWidth; + h.layout.profile_traps = (uint8_t)(ProfileTraps ? 1 : 0); + h.layout.type_profile_casts = (uint8_t)(TypeProfileCasts ? 1 : 0); + h.layout.spec_trap_limit_extra_entries = (int32_t)SpecTrapLimitExtraEntries; + h.sym_count = sym_count; + h.rec_count = rec_count; +} + +bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes) { + if (!write_u4(out, r.key.klass.id)) return false; + if (!write_u4(out, r.key.name.id)) return false; + if (!write_u4(out, r.key.sig.id)) return false; + u1 loader = (u1)r.key.loader; + if (!write_exact(out, &loader, sizeof(loader))) return false; + if (!write_u4(out, r.mdo_size)) return false; + if (!write_u4(out, r.fixup_count)) return false; + for (u4 i = 0; i < r.fixup_count; i++) { + if (!write_u4(out, fixups[i].offset_in_mdo)) return false; + u1 kind = (u1)fixups[i].kind; + if (!write_exact(out, &kind, sizeof(kind))) return false; + if (!write_u4(out, fixups[i].target.id)) return false; + } + if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; + if (!write_u4(out, r.mc_size)) return false; + if (r.mc_size > 0) { + if (!write_exact(out, mc_bytes, r.mc_size)) return false; + } + return true; +} + +bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) { + if (!read_u4(in, r.key.klass.id)) return false; + if (!read_u4(in, r.key.name.id)) return false; + if (!read_u4(in, r.key.sig.id)) return false; + u1 loader = 0; + if (!read_exact(in, &loader, sizeof(loader))) return false; + r.key.loader = (LoaderId)loader; + if (!read_u4(in, r.mdo_size)) return false; + if (!read_u4(in, r.fixup_count)) return false; + fixups = nullptr; + if (r.fixup_count > 0) { + fixups = (Fixup*)os::malloc(sizeof(Fixup) * r.fixup_count, mtInternal); + if (fixups == nullptr) return false; + for (u4 i = 0; i < r.fixup_count; i++) { + u1 kind = 0; + if (!read_u4(in, fixups[i].offset_in_mdo)) { os::free(fixups); return false; } + if (!read_exact(in, &kind, sizeof(kind))) { os::free(fixups); return false; } + fixups[i].kind = (FixupKind)kind; + if (!read_u4(in, fixups[i].target.id)) { os::free(fixups); return false; } + } + } + mdo_bytes = nullptr; + if (r.mdo_size > 0) { + mdo_bytes = (char*)os::malloc(r.mdo_size, mtInternal); + if (mdo_bytes == nullptr) { if (fixups) os::free(fixups); return false; } + if (!read_exact(in, mdo_bytes, r.mdo_size)) { os::free(mdo_bytes); if (fixups) os::free(fixups); return false; } + } + if (!read_u4(in, r.mc_size)) return false; + mc_bytes = nullptr; + if (r.mc_size > 0) { + mc_bytes = (char*)os::malloc(r.mc_size, mtInternal); + if (mc_bytes == nullptr) { if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); return false; } + if (!read_exact(in, mc_bytes, r.mc_size)) { os::free(mc_bytes); if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); return false; } + } + return true; +} + +// --- Restore support (minimal v1): resolve by system loader, link, alloc MDO, memcpy, sanitize --- + +static InstanceKlass* resolve_klass_utf8(const char* name, TRAPS) { + Symbol* sym = SymbolTable::new_symbol(name); + oop sys_loader_oop = SystemDictionary::java_system_loader(); + Handle loader(THREAD, sys_loader_oop); + Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } + return (k != nullptr && k->is_instance_klass()) ? InstanceKlass::cast(k) : nullptr; +} + +static Method* resolve_method_utf8(InstanceKlass* ik, const char* mname, const char* msig) { + if (ik == nullptr) return nullptr; + Symbol* mn = SymbolTable::new_symbol(mname); + Symbol* sg = SymbolTable::new_symbol(msig); + return ik->find_method(mn, sg); +} + +void ProfileCheckpoint::load(JavaThread* THREAD) { + if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; + FILE* f = os::fopen(MDOReplayLoadFile, "rb"); + if (f == nullptr) { return; } + ResourceMark rm; + log_info(compilation)("MDO checkpoint: loading (binary) from %s", MDOReplayLoadFile); + + ProfileCheckpoint::Header hdr; + if (!ProfileCheckpoint::Header::read(f, hdr)) { fclose(f); return; } + u4 sym_count = hdr.sym_count; + u4 rec_total = hdr.rec_count; + // Read symtab + GrowableArray symtab((int)sym_count); + for (u4 si = 0; si < sym_count; si++) { + char* s = read_str(f); + if (s == nullptr) { fclose(f); return; } + symtab.append(s); + } + int records_read = 0; + int records_installed = 0; + int size_mismatch = 0; + + for (u4 i = 0; i < rec_total; i++) { + ProfileCheckpoint::Record rec; + ProfileCheckpoint::Fixup* fixups = nullptr; + char* mdo_bytes = nullptr; + char* mc_bytes = nullptr; + if (!ProfileCheckpoint::Record::read(f, rec, fixups, mdo_bytes, mc_bytes)) { log_debug(compilation)("MDO checkpoint: record read failed at %u", i); break; } + const char* kname = symtab.at((int)rec.key.klass.id); + const char* mname = symtab.at((int)rec.key.name.id); + const char* msig = symtab.at((int)rec.key.sig.id); + records_read++; + if (rec.mdo_size == 0) { log_debug(compilation)("MDO checkpoint: empty MDO payload for %s %s %s", kname, mname, msig); if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); continue; } + + InstanceKlass* holder = resolve_klass_utf8(kname, THREAD); + if (holder != nullptr) { + Method* target = resolve_method_utf8(holder, mname, msig); + if (target != nullptr) { + holder->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + if (target->method_data() == nullptr) { + methodHandle mh(THREAD, target); + target->build_profiling_method_data(mh, CHECK); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + MethodData* mdo = target->method_data(); + if (mdo != nullptr) { + if (copy_mdo_payload(mdo, mdo_bytes, rec.mdo_size)) { + sanitize_type_entries(mdo); + if (fixups != nullptr && rec.fixup_count > 0) { + apply_fixups(mdo, fixups, rec.fixup_count, symtab, rec.key.loader, THREAD); + } + + records_installed++; + // Restore MethodCounters snapshot if present and counters object exists + if (rec.mc_size > 0) { + MethodCounters* mc = target->method_counters(); + if (mc != nullptr) { + const size_t ic_sz = sizeof(InvocationCounter); + if (rec.mc_size >= ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint) + 2 * sizeof(u1)) { + char* p = mc_bytes; + // invocation counter + Copy::conjoint_jbytes(p, (char*)mc->invocation_counter(), (jlong)ic_sz); p += ic_sz; + // backedge counter + Copy::conjoint_jbytes(p, (char*)mc->backedge_counter(), (jlong)ic_sz); p += ic_sz; + // prev_time + jlong prev_time = *(jlong*)p; p += sizeof(jlong); + mc->set_prev_time(prev_time); + // rate + float rate = *(float*)p; p += sizeof(float); + mc->set_rate(rate); + // prev_event_count + jint pec = *(jint*)p; p += sizeof(jint); + mc->set_prev_event_count(pec); + // highest levels + int hc = (int)(u1)(*p++); + int hosr = (int)(u1)(*p++); + mc->set_highest_comp_level(hc); + mc->set_highest_osr_comp_level(hosr); + } + } + } + if (PrintMDOAfterLoad) { + tty->print_cr("[AfterLoad] %s %s %s", kname, mname, msig); + tty->print_cr("[MethodData]"); + mdo->print_data_on(tty); + tty->print_cr("[MethodCounters]"); + MethodCounters* mc = target->method_counters(); + if (mc != nullptr) { + mc->print_data_on(tty); + } else { + tty->print_cr(" (none)"); + } + } + } else { + log_debug(compilation)("MDO checkpoint: copy payload failed for %s %s %s (size=%u)", kname, mname, msig, rec.mdo_size); + size_mismatch++; + } + } else { + log_debug(compilation)("MDO checkpoint: MethodData allocation/build failed for %s %s %s", kname, mname, msig); + } + } else { + log_debug(compilation)("MDO checkpoint: resolve method failed for %s %s %s", kname, mname, msig); + } + } else { + log_debug(compilation)("MDO checkpoint: resolve class failed for %s", kname); + } + if (fixups != nullptr) os::free(fixups); + if (mdo_bytes != nullptr) os::free(mdo_bytes); + if (mc_bytes != nullptr) os::free(mc_bytes); + } + fclose(f); + log_info(compilation)("MDO checkpoint: loaded %d records (%d installed, %d size mismatch)", + records_read, records_installed, size_mismatch); +} + +void ProfileCheckpoint::dump_to_stream(fileStream* out) { + ResourceMark rm; + GrowableArray methods(1024); + // We can't pass lambdas directly; reuse pattern from old dumper + static GrowableArray* g_methods; + g_methods = &methods; + auto collect_with_mdo = [](Method* m) { + if (m != nullptr && m->method_data() != nullptr) { + g_methods->push(m); + } + }; + SystemDictionary::methods_do(collect_with_mdo); + + // Symtab builder utilities + GrowableArray symtab(256); + auto add_unique = [&](const char* s){ + for (int i = 0; i < symtab.length(); i++) { + if (strcmp(symtab.at(i), s) == 0) return; + } + symtab.append(s); + }; + auto lookup_id = [&](const char* s)->u4 { + for (int i = 0; i < symtab.length(); i++) { + if (strcmp(symtab.at(i), s) == 0) return (u4)i; + } + // Should not happen once symtab is frozen + assert(false, "Symbol not found in symtab"); + return (u4)UINT_MAX; + }; + + struct RecNames { const char* kname; const char* mname; const char* sig; u4 mdo_size; const void* mdo_ptr; }; + GrowableArray recs(1024); + + for (int i = 0; i < methods.length(); i++) { + Method* m = methods.at(i); + MethodData* mdo = m->method_data(); + if (mdo == nullptr) continue; + MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); + InstanceKlass* holder = m->method_holder(); + const char* kname = holder->name()->as_utf8(); + const char* mname = m->name()->as_utf8(); + const char* sig = m->signature()->as_utf8(); + RecNames r; + r.kname = kname; + r.mname = mname; + r.sig = sig; + r.mdo_size = (u4)mdo->size_in_bytes(); + r.mdo_ptr = (const void*)mdo; + recs.append(r); + } + + // Pre-scan all methods to collect fixups as raw strings + GrowableArray< GrowableArray* > fixups_per_rec(recs.length()); + for (int ri = 0; ri < recs.length(); ri++) { + Method* m = methods.at(ri); + MethodData* mdo = m->method_data(); + GrowableArray* fx = new GrowableArray(16); + collect_type_fixups_text(mdo, *fx); + fixups_per_rec.append(fx); + } + + // Build/freeze symtab from all names: method keys + fixup names + for (int ri = 0; ri < recs.length(); ri++) { + add_unique(recs.at(ri).kname); + add_unique(recs.at(ri).mname); + add_unique(recs.at(ri).sig); + GrowableArray* fx = fixups_per_rec.at(ri); + for (int i = 0; i < fx->length(); i++) add_unique(fx->at(i).name); + } + + // Now write finalized header and symtab + ProfileCheckpoint::Writer writer(out); + writer.write_header((u4)symtab.length(), (u4)recs.length()); + + for (int si = 0; si < symtab.length(); si++) { + const char* s = symtab.at(si); + u4 len = (u4)strlen(s); + write_u4(out, len); + write_exact(out, s, len); + } + + // Write records using pre-built fixups + u4 emitted = 0; + for (int ri = 0; ri < recs.length(); ri++) { + const RecNames& rn = recs.at(ri); + Method* m = methods.at(ri); + MethodData* mdo = m->method_data(); + ProfileCheckpoint::Record rec; + oop cl = m->method_holder()->class_loader(); + if (cl == nullptr) { + rec.key.loader = LoaderId::BOOT; + } else if (SystemDictionary::is_platform_class_loader(cl)) { + rec.key.loader = LoaderId::PLATFORM; + } else { + rec.key.loader = LoaderId::APP; + } + rec.key.klass.id = lookup_id(rn.kname); + rec.key.name.id = lookup_id(rn.mname); + rec.key.sig.id = lookup_id(rn.sig); + rec.key.bytecode_crc32 = 0; + rec.mdo_size = rn.mdo_size; + GrowableArray* fx_text = fixups_per_rec.at(ri); + GrowableArray fx_ids(fx_text->length()); + for (int i = 0; i < fx_text->length(); i++) { + Fixup fxi; fxi.offset_in_mdo = fx_text->at(i).offset_in_mdo; fxi.kind = fx_text->at(i).kind; fxi.target.id = lookup_id(fx_text->at(i).name); fx_ids.append(fxi); + } + rec.fixup_count = (u4)fx_ids.length(); + // Build MethodCounters snapshot (optional) + const void* mc_bytes = nullptr; + GrowableArray mc_buf(0); + MethodCounters* mc = m->method_counters(); + if (mc != nullptr) { + const size_t ic_sz = sizeof(InvocationCounter); + const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint) + 2 * sizeof(u1); + for (size_t fill = 0; fill < mc_sz; fill++) mc_buf.append((char)0); + char* p = mc_buf.adr_at(0); + // invocation counter + Copy::conjoint_jbytes((char*)mc->invocation_counter(), p, (jlong)ic_sz); + p += ic_sz; + // backedge counter + Copy::conjoint_jbytes((char*)mc->backedge_counter(), p, (jlong)ic_sz); + p += ic_sz; + // prev_time + *(jlong*)p = mc->prev_time(); p += sizeof(jlong); + // rate + *(float*)p = mc->rate(); p += sizeof(float); + // prev_event_count + *(jint*)p = mc->prev_event_count(); p += sizeof(jint); + // highest levels + *p++ = (u1)mc->highest_comp_level(); + *p++ = (u1)mc->highest_osr_comp_level(); + rec.mc_size = (u4)mc_sz; + mc_bytes = mc_buf.adr_at(0); + } else { + rec.mc_size = 0; + } + ProfileCheckpoint::Record::write(out, rec, rn.mdo_ptr, rec.fixup_count ? fx_ids.adr_at(0) : nullptr, mc_bytes); + if (PrintMDOAtDump) { + const char* kname = rn.kname; + const char* mname = rn.mname; + const char* sig = rn.sig; + tty->print_cr("[Dump] %s %s %s", kname, mname, sig); + tty->print_cr("[MethodData]"); + mdo->print_data_on(tty); + tty->print_cr("[MethodCounters]"); + MethodCounters* mc = m->method_counters(); + if (mc != nullptr) { + mc->print_data_on(tty); + } else { + tty->print_cr(" (none)"); + } + } + emitted++; + } + log_info(compilation)("MDO checkpoint: dumped %u MDOs (sym=%d)", emitted, symtab.length()); +} + + diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp new file mode 100644 index 00000000000..f6ae35ac829 --- /dev/null +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -0,0 +1,112 @@ +#ifndef SHARE_SERVICES_PROFILECHECKPOINT_HPP +#define SHARE_SERVICES_PROFILECHECKPOINT_HPP + +#include "utilities/globalDefinitions.hpp" +#include + +class fileStream; + +class ProfileCheckpoint { +public: + // Binary format v3 (File = [HEADER][SYMTAB][RECORDS]) + // HEADER: + // magic[4] = 'M','D','O','X' + // version : u32 (==3) + // pointer_size: u16 (e.g., 8) + // endianness : u16 (0=little, 1=big) + // layout flags: see LayoutFlags + // sym_count : u32 + // rec_count : u32 + // + // SYMTAB: repeated sym_count times + // [u32 len][len bytes utf8] + // + // RECORD: repeated rec_count times + // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, + // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] + + enum class LoaderId : u1 { BOOT, PLATFORM, APP, UNDEFINED }; + enum class FixupKind : u1 { KLASS, METHOD }; + + struct SymbolId { uint32_t id; }; + + struct Fixup { + uint32_t offset_in_mdo; + FixupKind kind; + SymbolId target; + }; + + struct ByteRange { uint64_t off; uint32_t size; }; + + struct LayoutFlags { + uint32_t type_profile_level; + int32_t type_profile_args_limit; + int32_t type_profile_parms_limit; + int64_t type_profile_width; + uint8_t profile_traps; + uint8_t type_profile_casts; + int32_t spec_trap_limit_extra_entries; + }; + + struct MethodKey { + LoaderId loader; + SymbolId klass; // "a/b/C" + SymbolId name; // "foo" + SymbolId sig; // "(I)Ljava/lang/String;" + uint32_t bytecode_crc32; + }; + + struct Descriptor { + MethodKey key; + ByteRange mdo_payload {0,0}; + ByteRange mdo_fixups {0,0}; + ByteRange mc_payload {0,0}; + ByteRange mc_fixups {0,0}; + }; + + struct Header { + char magic[4]; + u4 version; + u2 pointer_size; + u2 endianness; + LayoutFlags layout; + u4 sym_count; + u4 rec_count; + + static void init(Header& h, u4 sym_count, u4 rec_count); + static bool write(fileStream* out, const Header& h); + static bool read(FILE* in, Header& h); + }; + + struct Record { + MethodKey key; + u4 mdo_size; + u4 fixup_count; + u4 mc_size; // bytes of MethodCounters snapshot (may be 0) + + static bool write(fileStream* out, const Record& r, const void* mdo_bytes, + const Fixup* fixups, const void* mc_bytes); + static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes); + }; + + class Writer { + fileStream* _out; + public: + explicit Writer(fileStream* out) : _out(out) {} + bool write_header(u4 sym_count, u4 rec_count) { Header h; Header::init(h, sym_count, rec_count); return Header::write(_out, h); } + bool write_record(const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes) { return Record::write(_out, r, mdo_bytes, fixups, mc_bytes); } + }; + + class Reader { + FILE* _in; + public: + explicit Reader(FILE* in) : _in(in) {} + bool read_header(Header& h) { return Header::read(_in, h); } + bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) { return Record::read(_in, r, fixups, mdo_bytes, mc_bytes); } + }; + + static void load(class JavaThread* THREAD); + static void dump_to_stream(class fileStream* out); +}; + +#endif // SHARE_SERVICES_PROFILECHECKPOINT_HPP diff --git a/src/hotspot/share/services/profileCheckpoint_globals.hpp b/src/hotspot/share/services/profileCheckpoint_globals.hpp new file mode 100644 index 00000000000..f7271af8a49 --- /dev/null +++ b/src/hotspot/share/services/profileCheckpoint_globals.hpp @@ -0,0 +1,36 @@ +#ifndef SHARE_SERVICES_PROFILECHECKPOINT_GLOBALS_HPP +#define SHARE_SERVICES_PROFILECHECKPOINT_GLOBALS_HPP + +#include "runtime/globals_shared.hpp" + +// Defines profile checkpoint (binary MDO) flags. Flags reuse existing names. +#define PROFILECHECKPOINT_FLAGS(develop, \ + develop_pd, \ + product, \ + product_pd, \ + range, \ + constraint) \ + \ + product(ccstr, MDOReplayDumpFile, nullptr, DIAGNOSTIC, \ + "File for exporting profiles (binary)") \ + \ + product(ccstr, MDOReplayLoadFile, nullptr, DIAGNOSTIC, \ + "File for importing profiles (binary)") \ + \ + product(bool, DumpMDOAtExit, false, DIAGNOSTIC, \ + "Dump MDOs to MDOReplayDumpFile at VM exit (binary)") \ + \ + product(bool, LoadMDOAtStartup, false, DIAGNOSTIC, \ + "Load MDOs from MDOReplayLoadFile at VM startup (binary)") \ + \ + product(bool, PrintMDOAtDump, false, DIAGNOSTIC, \ + "Debug: print MethodData/MethodCounters for each dumped method") \ + \ + product(bool, PrintMDOAfterLoad, false, DIAGNOSTIC, \ + "Debug: print MethodData/MethodCounters after loading each method") + +DECLARE_FLAGS(PROFILECHECKPOINT_FLAGS) + +#endif // SHARE_SERVICES_PROFILECHECKPOINT_GLOBALS_HPP + + From d036a06356c342e53327a8a329916f6bcbe00d32 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 6 Nov 2025 02:08:23 -0800 Subject: [PATCH 06/23] fix methodcounters --- .../share/services/profileCheckpoint.cpp | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index a01375df04c..828fc029899 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -273,7 +273,7 @@ bool ProfileCheckpoint::Header::read(FILE* in, Header& h) { if (!read_u4(in, h.rec_count)) return false; return true; } - + static u2 detect_endianness() { union { u4 v; u1 b[4]; } u; u.v = 1; return (u.b[0] == 1) ? (u2)0 : (u2)1; @@ -429,9 +429,15 @@ void ProfileCheckpoint::load(JavaThread* THREAD) { // Restore MethodCounters snapshot if present and counters object exists if (rec.mc_size > 0) { MethodCounters* mc = target->method_counters(); + if (mc == nullptr) { + methodHandle mh(THREAD, target); + MethodCounters* ensured = Method::build_method_counters(THREAD, target); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + mc = ensured; + } if (mc != nullptr) { const size_t ic_sz = sizeof(InvocationCounter); - if (rec.mc_size >= ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint) + 2 * sizeof(u1)) { + if (rec.mc_size >= ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint)) { char* p = mc_bytes; // invocation counter Copy::conjoint_jbytes(p, (char*)mc->invocation_counter(), (jlong)ic_sz); p += ic_sz; @@ -446,11 +452,6 @@ void ProfileCheckpoint::load(JavaThread* THREAD) { // prev_event_count jint pec = *(jint*)p; p += sizeof(jint); mc->set_prev_event_count(pec); - // highest levels - int hc = (int)(u1)(*p++); - int hosr = (int)(u1)(*p++); - mc->set_highest_comp_level(hc); - mc->set_highest_osr_comp_level(hosr); } } } @@ -601,7 +602,7 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { MethodCounters* mc = m->method_counters(); if (mc != nullptr) { const size_t ic_sz = sizeof(InvocationCounter); - const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint) + 2 * sizeof(u1); + const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint); for (size_t fill = 0; fill < mc_sz; fill++) mc_buf.append((char)0); char* p = mc_buf.adr_at(0); // invocation counter @@ -616,9 +617,6 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { *(float*)p = mc->rate(); p += sizeof(float); // prev_event_count *(jint*)p = mc->prev_event_count(); p += sizeof(jint); - // highest levels - *p++ = (u1)mc->highest_comp_level(); - *p++ = (u1)mc->highest_osr_comp_level(); rec.mc_size = (u4)mc_sz; mc_bytes = mc_buf.adr_at(0); } else { From 1cc47f28224661bbba728b92dddc1e478d36aa4d Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 6 Nov 2025 23:11:22 -0800 Subject: [PATCH 07/23] refactoring... --- .../share/services/profileCheckpoint.cpp | 141 ++++++++++-------- .../share/services/profileCheckpoint.hpp | 23 +++ 2 files changed, 101 insertions(+), 63 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 828fc029899..559413eff12 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -372,6 +372,64 @@ static Method* resolve_method_utf8(InstanceKlass* ik, const char* mname, const c return ik->find_method(mn, sg); } +// Build RecMeta (holder/name/sig/mdo size/pointer) for a method with an MDO +static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { + ProfileCheckpoint::RecMeta r{}; + MethodData* mdo = m->method_data(); + // Caller guarantees mdo != nullptr + MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); + InstanceKlass* holder = m->method_holder(); + r.kname = holder->name()->as_utf8(); + r.mname = m->name()->as_utf8(); + r.sig = m->signature()->as_utf8(); + r.mdo_size = (u4)mdo->size_in_bytes(); + r.mdo_ptr = (const void*)mdo; + return r; +} + +// Get all methods with MDO data +GrowableArray get_methods() { + GrowableArray methods(1024); + static GrowableArray* g_methods; + g_methods = &methods; + auto collect_with_mdo = [](Method* m) { + if (m != nullptr && m->method_data() != nullptr) { + g_methods->push(m); + } + }; + SystemDictionary::methods_do(collect_with_mdo); + return methods; +} + +u4 ProfileCheckpoint::SymtabBuilder::intern(const char* s) { + assert(!_frozen, "frozen"); + for (int i = 0; i < _syms.length(); i++) { + if (strcmp(_syms.at(i), s) == 0) return (u4)i; + } + _syms.append(s); + return (u4)(_syms.length() - 1); +} + +u4 ProfileCheckpoint::SymtabBuilder::id_of(const char* s) const { + assert(_frozen, "must freeze before lookups"); + for (int i = 0; i < _syms.length(); i++) { + if (strcmp(_syms.at(i), s) == 0) return (u4)i; + } + assert(false, "symbol not found in symtab"); + return (u4)UINT_MAX; +} + +bool ProfileCheckpoint::SymtabBuilder::write(fileStream* out) const { + for (int i = 0; i < _syms.length(); i++) { + const char* s = _syms.at(i); + u4 len = (u4)strlen(s); + if (!write_u4(out, len)) return false; + if (!write_exact(out, s, len)) return false; + } + return true; +} + + void ProfileCheckpoint::load(JavaThread* THREAD) { if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; FILE* f = os::fopen(MDOReplayLoadFile, "rb"); @@ -491,53 +549,15 @@ void ProfileCheckpoint::load(JavaThread* THREAD) { void ProfileCheckpoint::dump_to_stream(fileStream* out) { ResourceMark rm; - GrowableArray methods(1024); - // We can't pass lambdas directly; reuse pattern from old dumper - static GrowableArray* g_methods; - g_methods = &methods; - auto collect_with_mdo = [](Method* m) { - if (m != nullptr && m->method_data() != nullptr) { - g_methods->push(m); - } - }; - SystemDictionary::methods_do(collect_with_mdo); - - // Symtab builder utilities - GrowableArray symtab(256); - auto add_unique = [&](const char* s){ - for (int i = 0; i < symtab.length(); i++) { - if (strcmp(symtab.at(i), s) == 0) return; - } - symtab.append(s); - }; - auto lookup_id = [&](const char* s)->u4 { - for (int i = 0; i < symtab.length(); i++) { - if (strcmp(symtab.at(i), s) == 0) return (u4)i; - } - // Should not happen once symtab is frozen - assert(false, "Symbol not found in symtab"); - return (u4)UINT_MAX; - }; - - struct RecNames { const char* kname; const char* mname; const char* sig; u4 mdo_size; const void* mdo_ptr; }; - GrowableArray recs(1024); + GrowableArray methods = get_methods(); + // Symtab builder and record metadata + ProfileCheckpoint::SymtabBuilder stb; + GrowableArray recs(1024); for (int i = 0; i < methods.length(); i++) { Method* m = methods.at(i); - MethodData* mdo = m->method_data(); - if (mdo == nullptr) continue; - MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); - InstanceKlass* holder = m->method_holder(); - const char* kname = holder->name()->as_utf8(); - const char* mname = m->name()->as_utf8(); - const char* sig = m->signature()->as_utf8(); - RecNames r; - r.kname = kname; - r.mname = mname; - r.sig = sig; - r.mdo_size = (u4)mdo->size_in_bytes(); - r.mdo_ptr = (const void*)mdo; - recs.append(r); + if (m->method_data() == nullptr) continue; + recs.append(make_rec_meta_for_method(m)); } // Pre-scan all methods to collect fixups as raw strings @@ -550,30 +570,25 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { fixups_per_rec.append(fx); } - // Build/freeze symtab from all names: method keys + fixup names + // Build symtab from all names: method keys + fixup names for (int ri = 0; ri < recs.length(); ri++) { - add_unique(recs.at(ri).kname); - add_unique(recs.at(ri).mname); - add_unique(recs.at(ri).sig); + stb.intern(recs.at(ri).kname); + stb.intern(recs.at(ri).mname); + stb.intern(recs.at(ri).sig); GrowableArray* fx = fixups_per_rec.at(ri); - for (int i = 0; i < fx->length(); i++) add_unique(fx->at(i).name); + for (int i = 0; i < fx->length(); i++) stb.intern(fx->at(i).name); } + stb.freeze(); // Now write finalized header and symtab ProfileCheckpoint::Writer writer(out); - writer.write_header((u4)symtab.length(), (u4)recs.length()); - - for (int si = 0; si < symtab.length(); si++) { - const char* s = symtab.at(si); - u4 len = (u4)strlen(s); - write_u4(out, len); - write_exact(out, s, len); - } + writer.write_header(stb.length(), (u4)recs.length()); + stb.write(out); // Write records using pre-built fixups u4 emitted = 0; for (int ri = 0; ri < recs.length(); ri++) { - const RecNames& rn = recs.at(ri); + const ProfileCheckpoint::RecMeta& rn = recs.at(ri); Method* m = methods.at(ri); MethodData* mdo = m->method_data(); ProfileCheckpoint::Record rec; @@ -585,15 +600,15 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { } else { rec.key.loader = LoaderId::APP; } - rec.key.klass.id = lookup_id(rn.kname); - rec.key.name.id = lookup_id(rn.mname); - rec.key.sig.id = lookup_id(rn.sig); + rec.key.klass.id = stb.id_of(rn.kname); + rec.key.name.id = stb.id_of(rn.mname); + rec.key.sig.id = stb.id_of(rn.sig); rec.key.bytecode_crc32 = 0; rec.mdo_size = rn.mdo_size; GrowableArray* fx_text = fixups_per_rec.at(ri); GrowableArray fx_ids(fx_text->length()); for (int i = 0; i < fx_text->length(); i++) { - Fixup fxi; fxi.offset_in_mdo = fx_text->at(i).offset_in_mdo; fxi.kind = fx_text->at(i).kind; fxi.target.id = lookup_id(fx_text->at(i).name); fx_ids.append(fxi); + Fixup fxi; fxi.offset_in_mdo = fx_text->at(i).offset_in_mdo; fxi.kind = fx_text->at(i).kind; fxi.target.id = stb.id_of(fx_text->at(i).name); fx_ids.append(fxi); } rec.fixup_count = (u4)fx_ids.length(); // Build MethodCounters snapshot (optional) @@ -640,7 +655,7 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { } emitted++; } - log_info(compilation)("MDO checkpoint: dumped %u MDOs (sym=%d)", emitted, symtab.length()); + log_info(compilation)("MDO checkpoint: dumped %u MDOs (sym=%u)", emitted, stb.length()); } diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index f6ae35ac829..04b052cfc4b 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -3,6 +3,7 @@ #include "utilities/globalDefinitions.hpp" #include +#include "utilities/growableArray.hpp" class fileStream; @@ -64,6 +65,15 @@ class ProfileCheckpoint { ByteRange mc_fixups {0,0}; }; + // Minimal metadata per-record used during dump before IDs are frozen + struct RecMeta { + const char* kname; + const char* mname; + const char* sig; + u4 mdo_size; + const void* mdo_ptr; + }; + struct Header { char magic[4]; u4 version; @@ -105,6 +115,19 @@ class ProfileCheckpoint { bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) { return Record::read(_in, r, fixups, mdo_bytes, mc_bytes); } }; + class SymtabBuilder { + private: + GrowableArray _syms; + bool _frozen; + public: + SymtabBuilder() : _syms(256), _frozen(false) {} + u4 intern(const char* s); + u4 id_of(const char* s) const; + void freeze() { _frozen = true; } + u4 length() const { return (u4)_syms.length(); } + bool write(fileStream* out) const; + }; + static void load(class JavaThread* THREAD); static void dump_to_stream(class fileStream* out); }; From 3c746f90ef114457ddc635f29f4dc39ac52dea0c Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Sun, 16 Nov 2025 03:14:35 -0800 Subject: [PATCH 08/23] big refactor + eager compile that doesnt work that well --- src/hotspot/share/runtime/java.cpp | 2 - .../share/services/profileCheckpoint.cpp | 528 ++++++++++++------ .../share/services/profileCheckpoint.hpp | 63 ++- .../services/profileCheckpoint_globals.hpp | 11 +- 4 files changed, 415 insertions(+), 189 deletions(-) diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 5df4f4eefe2..bab78986859 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -316,8 +316,6 @@ void print_statistics() { VM_MDOReplayDump(fileStream* out) : _out(out) {} virtual VMOp_Type type() const { return VMOp_GC_HeapInspection; } virtual void doit() { - ProfileCheckpoint::Writer* dummy = nullptr; // include dependency anchor - (void)dummy; ProfileCheckpoint::dump_to_stream(_out); } } op(&fs); diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 559413eff12..e4cb7dcf03f 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -13,6 +13,9 @@ #include "utilities/copy.hpp" #include "runtime/os.hpp" #include "runtime/globals.hpp" +#include "compiler/compilationPolicy.hpp" +#include "compiler/compileBroker.hpp" +#include "compiler/compilerDefinitions.hpp" #include #include @@ -80,13 +83,9 @@ static void sanitize_type_entries(MethodData* mdo) { } } -struct FixupText { - u4 offset_in_mdo; - ProfileCheckpoint::FixupKind kind; - const char* name; // klass internal name -}; - -static void collect_type_fixups_text(MethodData* mdo, GrowableArray& out_fixups) { +static void collect_type_fixups(MethodData* mdo, + ProfileCheckpoint::SymtabBuilder& stb, + GrowableArray& out_fixups) { for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); @@ -96,8 +95,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -111,8 +112,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -123,8 +126,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -138,8 +143,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -150,8 +157,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -165,8 +174,10 @@ static void collect_type_fixups_text(MethodData* mdo, GrowableArray& Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); - FixupText fx; fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.name = cname; + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); + fx.kind = ProfileCheckpoint::FixupKind::KLASS; + fx.target.id = stb.intern(cname); out_fixups.append(fx); } } @@ -231,12 +242,6 @@ static bool read_u4(FILE* in, u4& v) { return read_exact(in, &v, sizeof(v)); } -static bool write_str(fileStream* out, const char* s) { - u4 len = (u4)strlen(s); - if (!write_u4(out, len)) return false; - return write_exact(out, s, len); -} - static char* read_str(FILE* in) { u4 len = 0; if (!read_u4(in, len)) return nullptr; @@ -354,9 +359,25 @@ bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& return true; } -// --- Restore support (minimal v1): resolve by system loader, link, alloc MDO, memcpy, sanitize --- +ProfileCheckpoint::Loader::Loader(JavaThread* thread) + : _thread(thread), + _records_read(0), + _records_installed(0), + _size_mismatch(0) {} + +const char* ProfileCheckpoint::Loader::load_status_name(LoadStatus status) { + switch (status) { + case LoadStatus::Success: return "success"; + case LoadStatus::MissingPath: return "missing_path"; + case LoadStatus::FileOpenFailed: return "file_open_failed"; + case LoadStatus::HeaderInvalid: return "header_invalid"; + case LoadStatus::SymtabReadFailed: return "symtab_read_failed"; + case LoadStatus::RecordReadFailed: return "record_read_failed"; + default: return "unknown"; + } +} -static InstanceKlass* resolve_klass_utf8(const char* name, TRAPS) { +InstanceKlass* ProfileCheckpoint::Loader::resolve_klass_utf8(const char* name, TRAPS) { Symbol* sym = SymbolTable::new_symbol(name); oop sys_loader_oop = SystemDictionary::java_system_loader(); Handle loader(THREAD, sys_loader_oop); @@ -365,13 +386,174 @@ static InstanceKlass* resolve_klass_utf8(const char* name, TRAPS) { return (k != nullptr && k->is_instance_klass()) ? InstanceKlass::cast(k) : nullptr; } -static Method* resolve_method_utf8(InstanceKlass* ik, const char* mname, const char* msig) { +Method* ProfileCheckpoint::Loader::resolve_method_utf8(InstanceKlass* ik, const char* mname, const char* msig) { if (ik == nullptr) return nullptr; Symbol* mn = SymbolTable::new_symbol(mname); Symbol* sg = SymbolTable::new_symbol(msig); return ik->find_method(mn, sg); } +bool ProfileCheckpoint::Loader::install_record(const Record& rec, + Fixup* fixups, + char* mdo_bytes, + char* mc_bytes, + GrowableArray& symtab) { + JavaThread* THREAD = _thread; // For exception macros. + const char* kname = symtab.at((int)rec.key.klass.id); + const char* mname = symtab.at((int)rec.key.name.id); + const char* msig = symtab.at((int)rec.key.sig.id); + + InstanceKlass* holder = resolve_klass_utf8(kname, THREAD); + if (holder == nullptr) { + log_debug(compilation)("MDO checkpoint: resolve class failed for %s", kname); + return false; + } + Method* target = resolve_method_utf8(holder, mname, msig); + if (target == nullptr) { + log_debug(compilation)("MDO checkpoint: resolve method failed for %s %s %s", kname, mname, msig); + return false; + } + + holder->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + + if (target->method_data() == nullptr) { + methodHandle mh(THREAD, target); + target->build_profiling_method_data(mh, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + + MethodData* mdo = target->method_data(); + if (mdo == nullptr) { + log_debug(compilation)("MDO checkpoint: MethodData allocation/build failed for %s %s %s", kname, mname, msig); + return false; + } + + if (!copy_mdo_payload(mdo, mdo_bytes, rec.mdo_size)) { + log_debug(compilation)("MDO checkpoint: copy payload failed for %s %s %s (size=%u)", kname, mname, msig, rec.mdo_size); + _size_mismatch++; + return false; + } + + sanitize_type_entries(mdo); + if (fixups != nullptr && rec.fixup_count > 0) { + apply_fixups(mdo, fixups, rec.fixup_count, symtab, rec.key.loader, THREAD); + } + + _records_installed++; + + if (rec.mc_size > 0) { + MethodCounters* mc = target->method_counters(); + if (mc == nullptr) { + methodHandle mh(THREAD, target); + MethodCounters* ensured = Method::build_method_counters(THREAD, target); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + mc = ensured; + } + if (mc != nullptr) { + const size_t ic_sz = sizeof(InvocationCounter); + if (rec.mc_size >= ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint)) { + char* p = mc_bytes; + Copy::conjoint_jbytes(p, (char*)mc->invocation_counter(), (jlong)ic_sz); p += ic_sz; + Copy::conjoint_jbytes(p, (char*)mc->backedge_counter(), (jlong)ic_sz); p += ic_sz; + jlong prev_time = *(jlong*)p; p += sizeof(jlong); + mc->set_prev_time(prev_time); + float rate = *(float*)p; p += sizeof(float); + mc->set_rate(rate); + jint pec = *(jint*)p; p += sizeof(jint); + mc->set_prev_event_count(pec); + } + } + } + + if (PrintMDOAfterLoad) { + tty->print_cr("[AfterLoad] %s %s %s", kname, mname, msig); + tty->print_cr("[MethodData]"); + mdo->print_data_on(tty); + tty->print_cr("[MethodCounters]"); + MethodCounters* mc = target->method_counters(); + if (mc != nullptr) { + mc->print_data_on(tty); + } else { + tty->print_cr(" (none)"); + } + } + + return true; +} + +ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file(const char* path) { + LoadResult result{LoadStatus::MissingPath, 0, 0, 0}; + if (path == nullptr) { + return result; + } + + FILE* f = os::fopen(path, "rb"); + if (f == nullptr) { + result.status = LoadStatus::FileOpenFailed; + return result; + } + + ResourceMark rm; + BinaryStreamReader reader(f); + + Header hdr; + if (!reader.read_header(hdr)) { + fclose(f); + result.status = LoadStatus::HeaderInvalid; + return result; + } + u4 sym_count = hdr.sym_count; + u4 rec_total = hdr.rec_count; + GrowableArray symtab((int)sym_count); + if (!reader.read_symtab(symtab, sym_count)) { + fclose(f); + result.status = LoadStatus::SymtabReadFailed; + return result; + } + + result.status = LoadStatus::Success; + + for (u4 i = 0; i < rec_total; i++) { + Record rec; + Fixup* fixups = nullptr; + char* mdo_bytes = nullptr; + char* mc_bytes = nullptr; + if (!reader.read_record(rec, fixups, mdo_bytes, mc_bytes)) { + log_debug(compilation)("MDO checkpoint: record read failed at %u", i); + result.status = LoadStatus::RecordReadFailed; + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (mc_bytes) os::free(mc_bytes); + break; + } + + _records_read++; + + if (rec.mdo_size == 0) { + log_debug(compilation)("MDO checkpoint: empty MDO payload for sym ids %u %u %u", + rec.key.klass.id, rec.key.name.id, rec.key.sig.id); + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (mc_bytes) os::free(mc_bytes); + continue; + } + + install_record(rec, fixups, mdo_bytes, mc_bytes, symtab); + + if (fixups != nullptr) os::free(fixups); + if (mdo_bytes != nullptr) os::free(mdo_bytes); + if (mc_bytes != nullptr) os::free(mc_bytes); + } + + fclose(f); + + result.records_read = _records_read; + result.records_installed = _records_installed; + result.size_mismatch = _size_mismatch; + return result; +} + // Build RecMeta (holder/name/sig/mdo size/pointer) for a method with an MDO static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { ProfileCheckpoint::RecMeta r{}; @@ -419,140 +601,76 @@ u4 ProfileCheckpoint::SymtabBuilder::id_of(const char* s) const { return (u4)UINT_MAX; } -bool ProfileCheckpoint::SymtabBuilder::write(fileStream* out) const { - for (int i = 0; i < _syms.length(); i++) { - const char* s = _syms.at(i); +bool ProfileCheckpoint::BinaryStreamWriter::write_header(u4 sym_count, u4 rec_count) { + Header h; + Header::init(h, sym_count, rec_count); + return Header::write(_out, h); +} + +bool ProfileCheckpoint::BinaryStreamWriter::write_symtab(const GrowableArray& symbols) const { + for (int i = 0; i < symbols.length(); i++) { + const char* s = symbols.at(i); u4 len = (u4)strlen(s); - if (!write_u4(out, len)) return false; - if (!write_exact(out, s, len)) return false; + if (!write_u4(_out, len)) return false; + if (!write_exact(_out, s, len)) return false; } return true; } +bool ProfileCheckpoint::BinaryStreamWriter::write_record(const Record& r, const void* mdo_bytes, + const Fixup* fixups, const void* mc_bytes) const { + return Record::write(_out, r, mdo_bytes, fixups, mc_bytes); +} -void ProfileCheckpoint::load(JavaThread* THREAD) { - if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; - FILE* f = os::fopen(MDOReplayLoadFile, "rb"); - if (f == nullptr) { return; } - ResourceMark rm; - log_info(compilation)("MDO checkpoint: loading (binary) from %s", MDOReplayLoadFile); +bool ProfileCheckpoint::BinaryStreamReader::read_header(Header& h) const { + return Header::read(_in, h); +} - ProfileCheckpoint::Header hdr; - if (!ProfileCheckpoint::Header::read(f, hdr)) { fclose(f); return; } - u4 sym_count = hdr.sym_count; - u4 rec_total = hdr.rec_count; - // Read symtab - GrowableArray symtab((int)sym_count); - for (u4 si = 0; si < sym_count; si++) { - char* s = read_str(f); - if (s == nullptr) { fclose(f); return; } - symtab.append(s); +bool ProfileCheckpoint::BinaryStreamReader::read_symtab(GrowableArray& symbols, u4 expected) const { + for (u4 si = 0; si < expected; si++) { + char* s = read_str(_in); + if (s == nullptr) { + return false; + } + symbols.append(s); } - int records_read = 0; - int records_installed = 0; - int size_mismatch = 0; + return true; +} - for (u4 i = 0; i < rec_total; i++) { - ProfileCheckpoint::Record rec; - ProfileCheckpoint::Fixup* fixups = nullptr; - char* mdo_bytes = nullptr; - char* mc_bytes = nullptr; - if (!ProfileCheckpoint::Record::read(f, rec, fixups, mdo_bytes, mc_bytes)) { log_debug(compilation)("MDO checkpoint: record read failed at %u", i); break; } - const char* kname = symtab.at((int)rec.key.klass.id); - const char* mname = symtab.at((int)rec.key.name.id); - const char* msig = symtab.at((int)rec.key.sig.id); - records_read++; - if (rec.mdo_size == 0) { log_debug(compilation)("MDO checkpoint: empty MDO payload for %s %s %s", kname, mname, msig); if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); continue; } - - InstanceKlass* holder = resolve_klass_utf8(kname, THREAD); - if (holder != nullptr) { - Method* target = resolve_method_utf8(holder, mname, msig); - if (target != nullptr) { - holder->link_class(THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - if (target->method_data() == nullptr) { - methodHandle mh(THREAD, target); - target->build_profiling_method_data(mh, CHECK); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - } - MethodData* mdo = target->method_data(); - if (mdo != nullptr) { - if (copy_mdo_payload(mdo, mdo_bytes, rec.mdo_size)) { - sanitize_type_entries(mdo); - if (fixups != nullptr && rec.fixup_count > 0) { - apply_fixups(mdo, fixups, rec.fixup_count, symtab, rec.key.loader, THREAD); - } - - records_installed++; - // Restore MethodCounters snapshot if present and counters object exists - if (rec.mc_size > 0) { - MethodCounters* mc = target->method_counters(); - if (mc == nullptr) { - methodHandle mh(THREAD, target); - MethodCounters* ensured = Method::build_method_counters(THREAD, target); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - mc = ensured; - } - if (mc != nullptr) { - const size_t ic_sz = sizeof(InvocationCounter); - if (rec.mc_size >= ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint)) { - char* p = mc_bytes; - // invocation counter - Copy::conjoint_jbytes(p, (char*)mc->invocation_counter(), (jlong)ic_sz); p += ic_sz; - // backedge counter - Copy::conjoint_jbytes(p, (char*)mc->backedge_counter(), (jlong)ic_sz); p += ic_sz; - // prev_time - jlong prev_time = *(jlong*)p; p += sizeof(jlong); - mc->set_prev_time(prev_time); - // rate - float rate = *(float*)p; p += sizeof(float); - mc->set_rate(rate); - // prev_event_count - jint pec = *(jint*)p; p += sizeof(jint); - mc->set_prev_event_count(pec); - } - } - } - if (PrintMDOAfterLoad) { - tty->print_cr("[AfterLoad] %s %s %s", kname, mname, msig); - tty->print_cr("[MethodData]"); - mdo->print_data_on(tty); - tty->print_cr("[MethodCounters]"); - MethodCounters* mc = target->method_counters(); - if (mc != nullptr) { - mc->print_data_on(tty); - } else { - tty->print_cr(" (none)"); - } - } - } else { - log_debug(compilation)("MDO checkpoint: copy payload failed for %s %s %s (size=%u)", kname, mname, msig, rec.mdo_size); - size_mismatch++; - } - } else { - log_debug(compilation)("MDO checkpoint: MethodData allocation/build failed for %s %s %s", kname, mname, msig); - } - } else { - log_debug(compilation)("MDO checkpoint: resolve method failed for %s %s %s", kname, mname, msig); - } - } else { - log_debug(compilation)("MDO checkpoint: resolve class failed for %s", kname); +bool ProfileCheckpoint::BinaryStreamReader::read_record(Record& r, Fixup*& fixups, + char*& mdo_bytes, char*& mc_bytes) const { + return Record::read(_in, r, fixups, mdo_bytes, mc_bytes); +} + +void ProfileCheckpoint::load(JavaThread* THREAD) { + if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; + Loader loader(THREAD); + log_info(compilation)("MDO checkpoint: initiating load from %s", MDOReplayLoadFile); + Loader::LoadResult res = loader.load_from_file(MDOReplayLoadFile); + if (res.ok()) { + log_info(compilation)("MDO checkpoint: loaded %d records (%d installed, %d size mismatch)", + res.records_read, res.records_installed, res.size_mismatch); + if (EagerCompileAllLoaded) { + eager_compile_after_load(THREAD); } - if (fixups != nullptr) os::free(fixups); - if (mdo_bytes != nullptr) os::free(mdo_bytes); - if (mc_bytes != nullptr) os::free(mc_bytes); + } else { + log_warning(compilation)("MDO checkpoint: load failed (status=%s, read=%d, installed=%d, mismatches=%d)", + Loader::load_status_name(res.status), + res.records_read, + res.records_installed, + res.size_mismatch); } - fclose(f); - log_info(compilation)("MDO checkpoint: loaded %d records (%d installed, %d size mismatch)", - records_read, records_installed, size_mismatch); } -void ProfileCheckpoint::dump_to_stream(fileStream* out) { +bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { + if (out == nullptr) { + return false; + } + ResourceMark rm; GrowableArray methods = get_methods(); - // Symtab builder and record metadata - ProfileCheckpoint::SymtabBuilder stb; - GrowableArray recs(1024); + SymtabBuilder stb; + GrowableArray recs(1024); for (int i = 0; i < methods.length(); i++) { Method* m = methods.at(i); @@ -560,38 +678,36 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { recs.append(make_rec_meta_for_method(m)); } - // Pre-scan all methods to collect fixups as raw strings - GrowableArray< GrowableArray* > fixups_per_rec(recs.length()); + GrowableArray< GrowableArray* > fixups_per_rec(recs.length()); for (int ri = 0; ri < recs.length(); ri++) { Method* m = methods.at(ri); MethodData* mdo = m->method_data(); - GrowableArray* fx = new GrowableArray(16); - collect_type_fixups_text(mdo, *fx); + GrowableArray* fx = new GrowableArray(16); + collect_type_fixups(mdo, stb, *fx); fixups_per_rec.append(fx); } - // Build symtab from all names: method keys + fixup names for (int ri = 0; ri < recs.length(); ri++) { stb.intern(recs.at(ri).kname); stb.intern(recs.at(ri).mname); stb.intern(recs.at(ri).sig); - GrowableArray* fx = fixups_per_rec.at(ri); - for (int i = 0; i < fx->length(); i++) stb.intern(fx->at(i).name); } stb.freeze(); - // Now write finalized header and symtab - ProfileCheckpoint::Writer writer(out); - writer.write_header(stb.length(), (u4)recs.length()); - stb.write(out); + BinaryStreamWriter writer(out); + if (!writer.write_header(stb.length(), (u4)recs.length())) { + return false; + } + if (!writer.write_symtab(stb.symbols())) { + return false; + } - // Write records using pre-built fixups u4 emitted = 0; for (int ri = 0; ri < recs.length(); ri++) { - const ProfileCheckpoint::RecMeta& rn = recs.at(ri); + const RecMeta& rn = recs.at(ri); Method* m = methods.at(ri); MethodData* mdo = m->method_data(); - ProfileCheckpoint::Record rec; + Record rec; oop cl = m->method_holder()->class_loader(); if (cl == nullptr) { rec.key.loader = LoaderId::BOOT; @@ -605,13 +721,9 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { rec.key.sig.id = stb.id_of(rn.sig); rec.key.bytecode_crc32 = 0; rec.mdo_size = rn.mdo_size; - GrowableArray* fx_text = fixups_per_rec.at(ri); - GrowableArray fx_ids(fx_text->length()); - for (int i = 0; i < fx_text->length(); i++) { - Fixup fxi; fxi.offset_in_mdo = fx_text->at(i).offset_in_mdo; fxi.kind = fx_text->at(i).kind; fxi.target.id = stb.id_of(fx_text->at(i).name); fx_ids.append(fxi); - } - rec.fixup_count = (u4)fx_ids.length(); - // Build MethodCounters snapshot (optional) + GrowableArray* fx_entries = fixups_per_rec.at(ri); + rec.fixup_count = (u4)fx_entries->length(); + const void* mc_bytes = nullptr; GrowableArray mc_buf(0); MethodCounters* mc = m->method_counters(); @@ -620,24 +732,22 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint); for (size_t fill = 0; fill < mc_sz; fill++) mc_buf.append((char)0); char* p = mc_buf.adr_at(0); - // invocation counter Copy::conjoint_jbytes((char*)mc->invocation_counter(), p, (jlong)ic_sz); p += ic_sz; - // backedge counter Copy::conjoint_jbytes((char*)mc->backedge_counter(), p, (jlong)ic_sz); p += ic_sz; - // prev_time *(jlong*)p = mc->prev_time(); p += sizeof(jlong); - // rate *(float*)p = mc->rate(); p += sizeof(float); - // prev_event_count *(jint*)p = mc->prev_event_count(); p += sizeof(jint); rec.mc_size = (u4)mc_sz; mc_bytes = mc_buf.adr_at(0); } else { rec.mc_size = 0; } - ProfileCheckpoint::Record::write(out, rec, rn.mdo_ptr, rec.fixup_count ? fx_ids.adr_at(0) : nullptr, mc_bytes); + Fixup* fixup_buf = rec.fixup_count ? fx_entries->adr_at(0) : nullptr; + if (!writer.write_record(rec, rn.mdo_ptr, fixup_buf, mc_bytes)) { + return false; + } if (PrintMDOAtDump) { const char* kname = rn.kname; const char* mname = rn.mname; @@ -646,9 +756,9 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { tty->print_cr("[MethodData]"); mdo->print_data_on(tty); tty->print_cr("[MethodCounters]"); - MethodCounters* mc = m->method_counters(); - if (mc != nullptr) { - mc->print_data_on(tty); + MethodCounters* mc_print = m->method_counters(); + if (mc_print != nullptr) { + mc_print->print_data_on(tty); } else { tty->print_cr(" (none)"); } @@ -656,6 +766,70 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { emitted++; } log_info(compilation)("MDO checkpoint: dumped %u MDOs (sym=%u)", emitted, stb.length()); + return true; +} + +void ProfileCheckpoint::dump_to_stream(fileStream* out) { + if (!Loader::dump_to_stream(out)) { + log_warning(compilation)("MDO checkpoint: dump failed"); + } +} + + +void ProfileCheckpoint::eager_compile_after_load(JavaThread* THREAD) { + if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) { + return; + } + log_info(compilation)("Eager compiling all loaded methods"); + // Optionally run on the specified main class + if (EagerMainClass != nullptr) { + ResourceMark rm(THREAD); + const char* dotted = EagerMainClass; + size_t len = strlen(dotted); + char* slash_name = NEW_RESOURCE_ARRAY(char, len + 1); + for (size_t i = 0; i < len; i++) { + char c = dotted[i]; + slash_name[i] = (c == '.') ? '/' : c; + } + slash_name[len] = '\0'; + TempNewSymbol name_sym = SymbolTable::new_symbol(slash_name); + Klass* k = SystemDictionary::resolve_or_null(name_sym, THREAD); + if (k != nullptr) { + InstanceKlass* ik = InstanceKlass::cast(k); + if (!ik->is_initialized()) { + ik->initialize(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + } + } + + // Visit all loaded methods and trigger the standard policy transition. + struct EagerCompileVisitor { + static void visit(Method* m) { + if (m == nullptr) return; + if (m->is_abstract() || m->is_native()) return; + JavaThread* THREAD = JavaThread::current(); // For exception macros. + methodHandle mh(THREAD, m); + nmethod* code = m->code(); + CompLevel level = (code != nullptr && code->is_in_use()) ? (CompLevel)code->comp_level() : CompLevel_none; + // Standard transition for normal invocation events. + CompilationPolicy::event(mh, mh, InvocationEntryBci, InvocationEntryBci, level, nullptr, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + }; + SystemDictionary::methods_do(EagerCompileVisitor::visit); + + // Block until compile queues are drained and no active tasks remain. + for (;;) { + CompileBroker::wait_for_no_active_tasks(); + CompileQueue* q1 = CompileBroker::c1_compile_queue(); + CompileQueue* q2 = CompileBroker::c2_compile_queue(); + bool empty1 = (q1 == nullptr) || q1->is_empty(); + bool empty2 = (q2 == nullptr) || q2->is_empty(); + if (empty1 && empty2) break; + os::naked_short_sleep(1); + } + log_info(compilation)("Eager compilation completed"); } diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 04b052cfc4b..1476b91e0af 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -99,20 +99,23 @@ class ProfileCheckpoint { static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes); }; - class Writer { + class BinaryStreamWriter { fileStream* _out; public: - explicit Writer(fileStream* out) : _out(out) {} - bool write_header(u4 sym_count, u4 rec_count) { Header h; Header::init(h, sym_count, rec_count); return Header::write(_out, h); } - bool write_record(const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes) { return Record::write(_out, r, mdo_bytes, fixups, mc_bytes); } + explicit BinaryStreamWriter(fileStream* out) : _out(out) {} + bool write_header(u4 sym_count, u4 rec_count); + bool write_symtab(const GrowableArray& symbols) const; + bool write_record(const Record& r, const void* mdo_bytes, + const Fixup* fixups, const void* mc_bytes) const; }; - class Reader { + class BinaryStreamReader { FILE* _in; public: - explicit Reader(FILE* in) : _in(in) {} - bool read_header(Header& h) { return Header::read(_in, h); } - bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) { return Record::read(_in, r, fixups, mdo_bytes, mc_bytes); } + explicit BinaryStreamReader(FILE* in) : _in(in) {} + bool read_header(Header& h) const; + bool read_symtab(GrowableArray& symbols, u4 expected) const; + bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) const; }; class SymtabBuilder { @@ -125,11 +128,53 @@ class ProfileCheckpoint { u4 id_of(const char* s) const; void freeze() { _frozen = true; } u4 length() const { return (u4)_syms.length(); } - bool write(fileStream* out) const; + const GrowableArray& symbols() const { return _syms; } + }; + + class Loader { + public: + enum class LoadStatus { + Success, + MissingPath, + FileOpenFailed, + HeaderInvalid, + SymtabReadFailed, + RecordReadFailed + }; + + struct LoadResult { + LoadStatus status; + int records_read; + int records_installed; + int size_mismatch; + bool ok() const { return status == LoadStatus::Success; } + }; + + explicit Loader(class JavaThread* thread); + LoadResult load_from_file(const char* path); + static const char* load_status_name(LoadStatus status); + static bool dump_to_stream(fileStream* out); + private: + class JavaThread* _thread; + int _records_read; + int _records_installed; + int _size_mismatch; + + bool install_record(const Record& rec, + Fixup* fixups, + char* mdo_bytes, + char* mc_bytes, + GrowableArray& symtab); + + static class InstanceKlass* resolve_klass_utf8(const char* name, TRAPS); + static class Method* resolve_method_utf8(class InstanceKlass* ik, + const char* mname, + const char* msig); }; static void load(class JavaThread* THREAD); static void dump_to_stream(class fileStream* out); + static void eager_compile_after_load(class JavaThread* THREAD); }; #endif // SHARE_SERVICES_PROFILECHECKPOINT_HPP diff --git a/src/hotspot/share/services/profileCheckpoint_globals.hpp b/src/hotspot/share/services/profileCheckpoint_globals.hpp index f7271af8a49..6edf3d7aebf 100644 --- a/src/hotspot/share/services/profileCheckpoint_globals.hpp +++ b/src/hotspot/share/services/profileCheckpoint_globals.hpp @@ -27,7 +27,16 @@ "Debug: print MethodData/MethodCounters for each dumped method") \ \ product(bool, PrintMDOAfterLoad, false, DIAGNOSTIC, \ - "Debug: print MethodData/MethodCounters after loading each method") + "Debug: print MethodData/MethodCounters after loading each method") \ + \ + product(bool, EagerCompileAllLoaded, false, DIAGNOSTIC, \ + "After loading MDOs at startup, run on EagerMainClass " \ + "if set, then iterate all loaded methods, trigger standard policy " \ + "transition, and queue compiles; block until compile queues drain") \ + \ + product(ccstr, EagerMainClass, nullptr, DIAGNOSTIC, \ + "Dotted name of the main class whose should be run " \ + "before eager compilation (e.g., com.example.Main)") DECLARE_FLAGS(PROFILECHECKPOINT_FLAGS) From 13c764c1648e1c5a081201a324e52b262b25d852 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Wed, 19 Nov 2025 00:05:42 -0800 Subject: [PATCH 09/23] restore mdo header --- src/hotspot/share/oops/methodData.cpp | 50 ++++ src/hotspot/share/oops/methodData.hpp | 24 ++ .../share/services/profileCheckpoint.cpp | 272 +++++++++++++----- .../share/services/profileCheckpoint.hpp | 16 +- .../services/profileCheckpoint_globals.hpp | 10 +- 5 files changed, 279 insertions(+), 93 deletions(-) diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 4c027e0839a..09ae7da5d56 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -1664,6 +1664,56 @@ void MethodData::print_data_on(outputStream* st) const { } } +void MethodData::snapshot_header(HeaderSnapshot* dst) const { + assert(dst != nullptr, "must be"); + Copy::conjoint_jbytes((const char*)&_compiler_counters, + (char*)&dst->_compiler_counters, + sizeof(CompilerCounters)); + dst->_invocation_counter = _invocation_counter; + dst->_backedge_counter = _backedge_counter; + dst->_invocation_counter_start = _invocation_counter_start; + dst->_backedge_counter_start = _backedge_counter_start; + dst->_tenure_traps = _tenure_traps; + dst->_invoke_mask = _invoke_mask; + dst->_backedge_mask = _backedge_mask; + dst->_num_loops = _num_loops; + dst->_num_blocks = _num_blocks; + dst->_would_profile = _would_profile; + dst->_eflags = _eflags; + dst->_arg_local = _arg_local; + dst->_arg_stack = _arg_stack; + dst->_arg_returned = _arg_returned; + dst->_data_size = _data_size; + dst->_parameters_type_data_di = _parameters_type_data_di; + dst->_exception_handler_data_di = _exception_handler_data_di; +} + +bool MethodData::restore_header(const HeaderSnapshot& src) { + if (_data_size != src._data_size || + _parameters_type_data_di != src._parameters_type_data_di || + _exception_handler_data_di != src._exception_handler_data_di) { + return false; + } + Copy::conjoint_jbytes((const char*)&src._compiler_counters, + (char*)&_compiler_counters, + sizeof(CompilerCounters)); + _invocation_counter = src._invocation_counter; + _backedge_counter = src._backedge_counter; + _invocation_counter_start = src._invocation_counter_start; + _backedge_counter_start = src._backedge_counter_start; + _tenure_traps = src._tenure_traps; + _invoke_mask = src._invoke_mask; + _backedge_mask = src._backedge_mask; + _num_loops = src._num_loops; + _num_blocks = src._num_blocks; + _would_profile = src._would_profile; + _eflags = src._eflags; + _arg_local = src._arg_local; + _arg_stack = src._arg_stack; + _arg_returned = src._arg_returned; + return true; +} + // Verification void MethodData::verify_on(outputStream* st) { diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index 61137d9fb7a..d5ae42de09c 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -2247,6 +2247,27 @@ class MethodData : public Metadata { DataLayout* exception_handler_bci_to_data_helper(int bci); public: + struct HeaderSnapshot { + CompilerCounters _compiler_counters; + InvocationCounter _invocation_counter; + InvocationCounter _backedge_counter; + int _invocation_counter_start; + int _backedge_counter_start; + uint _tenure_traps; + int _invoke_mask; + int _backedge_mask; + short _num_loops; + short _num_blocks; + WouldProfile _would_profile; + intx _eflags; + intx _arg_local; + intx _arg_stack; + intx _arg_returned; + int _data_size; + int _parameters_type_data_di; + int _exception_handler_data_di; + }; + void clean_extra_data(CleanExtraDataClosure* cl); static int header_size() { @@ -2308,6 +2329,9 @@ class MethodData : public Metadata { InvocationCounter* invocation_counter() { return &_invocation_counter; } InvocationCounter* backedge_counter() { return &_backedge_counter; } + void snapshot_header(HeaderSnapshot* dst) const; + bool restore_header(const HeaderSnapshot& src); + #if INCLUDE_JVMCI FailedSpeculation** get_failed_speculations_address() { return &_failed_speculations; diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index e4cb7dcf03f..c73856d9c2b 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -13,9 +13,11 @@ #include "utilities/copy.hpp" #include "runtime/os.hpp" #include "runtime/globals.hpp" +#include "runtime/deoptimization.hpp" #include "compiler/compilationPolicy.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerDefinitions.hpp" +#include "memory/resourceArea.hpp" #include #include @@ -32,6 +34,64 @@ static bool copy_mdo_payload(MethodData* dst_mdo, const char* src_bytes, u4 src_ return true; } +static void print_mdo_header(MethodData* mdo, outputStream* st = tty) { + if (mdo == nullptr) { + st->print_cr(" (MethodData is null)"); + return; + } + ResourceMark rm; + Method* m = mdo->method(); + const char* method_name = (m != nullptr) ? m->name_and_sig_as_C_string() : ""; + const char* holder_name = (m != nullptr && m->method_holder() != nullptr) ? m->method_holder()->external_name() : ""; + st->print_cr(" method=%s holder=%s", method_name, holder_name); + st->print_cr(" size_in_bytes=%d data_size=%d extra_data_size=%d", + mdo->size_in_bytes(), + mdo->data_size(), + mdo->extra_data_size()); + st->print_cr(" parameters_type_data_di=%d exception_handler_data_di=%d", + mdo->parameters_type_data_di(), + mdo->exception_handlers_data_di()); + st->print_cr(" invocation_counter=%d start=%d delta=%d", + mdo->invocation_count(), + mdo->invocation_count_start(), + mdo->invocation_count_delta()); + st->print_cr(" backedge_counter=%d start=%d delta=%d", + mdo->backedge_count(), + mdo->backedge_count_start(), + mdo->backedge_count_delta()); + st->print_cr(" tenure_traps=%u decompile_count=%u overflow_recompiles=%u overflow_traps=%u", + mdo->tenure_traps(), + mdo->decompile_count(), + mdo->overflow_recompile_count(), + mdo->overflow_trap_count()); + st->print_cr(" loops=%d blocks=%d mature=%s would_profile=%s", + mdo->num_loops(), + mdo->num_blocks(), + mdo->is_mature() ? "true" : "false", + mdo->would_profile() ? "true" : "false"); + st->print_cr(" escape_flags=0x%lx arg_local=0x%lx arg_stack=0x%lx arg_returned=0x%lx", + (long)mdo->eflags(), + (long)mdo->arg_local(), + (long)mdo->arg_stack(), + (long)mdo->arg_returned()); + + st->print(" trap_counts:"); + bool printed = false; + const uint reason_limit = MethodData::trap_reason_limit(); + for (uint reason = 0; reason < reason_limit; reason++) { + uint count = mdo->trap_count((int)reason); + if (count == 0) { + continue; + } + st->print(" %s=%u", Deoptimization::trap_reason_name(reason), count); + printed = true; + } + if (!printed) { + st->print(" (none)"); + } + st->cr(); +} + static void sanitize_type_entries(MethodData* mdo) { for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { @@ -257,7 +317,6 @@ static bool read_u2(FILE* in, u2& v) { return read_exact(in, &v, sizeof(v)); } bool ProfileCheckpoint::Header::write(fileStream* out, const Header& h) { if (!write_exact(out, h.magic, sizeof(h.magic))) return false; - if (!write_u4(out, h.version)) return false; if (!write_u2(out, h.pointer_size)) return false; if (!write_u2(out, h.endianness)) return false; if (!write_exact(out, &h.layout, sizeof(h.layout))) return false; @@ -269,8 +328,6 @@ bool ProfileCheckpoint::Header::write(fileStream* out, const Header& h) { bool ProfileCheckpoint::Header::read(FILE* in, Header& h) { if (!read_exact(in, h.magic, sizeof(h.magic))) return false; if (h.magic[0] != 'M' || h.magic[1] != 'D' || h.magic[2] != 'O' || h.magic[3] != 'X') return false; - if (!read_u4(in, h.version)) return false; - if (h.version != 3u) return false; if (!read_u2(in, h.pointer_size)) return false; if (!read_u2(in, h.endianness)) return false; if (!read_exact(in, &h.layout, sizeof(h.layout))) return false; @@ -286,7 +343,6 @@ static u2 detect_endianness() { void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count) { h.magic[0] = 'M'; h.magic[1] = 'D'; h.magic[2] = 'O'; h.magic[3] = 'X'; - h.version = 3u; h.pointer_size = (u2)sizeof(void*); h.endianness = detect_endianness(); h.layout.type_profile_level = (uint32_t)TypeProfileLevel; @@ -300,12 +356,13 @@ void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count) { h.rec_count = rec_count; } -bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes) { +bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes) { if (!write_u4(out, r.key.klass.id)) return false; if (!write_u4(out, r.key.name.id)) return false; if (!write_u4(out, r.key.sig.id)) return false; u1 loader = (u1)r.key.loader; if (!write_exact(out, &loader, sizeof(loader))) return false; + if (!write_exact(out, &r.comp_level, sizeof(r.comp_level))) return false; if (!write_u4(out, r.mdo_size)) return false; if (!write_u4(out, r.fixup_count)) return false; for (u4 i = 0; i < r.fixup_count; i++) { @@ -315,6 +372,10 @@ bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const vo if (!write_u4(out, fixups[i].target.id)) return false; } if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; + if (!write_u4(out, r.header_size)) return false; + if (r.header_size > 0) { + if (!write_exact(out, header_bytes, r.header_size)) return false; + } if (!write_u4(out, r.mc_size)) return false; if (r.mc_size > 0) { if (!write_exact(out, mc_bytes, r.mc_size)) return false; @@ -322,13 +383,14 @@ bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const vo return true; } -bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) { +bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) { if (!read_u4(in, r.key.klass.id)) return false; if (!read_u4(in, r.key.name.id)) return false; if (!read_u4(in, r.key.sig.id)) return false; u1 loader = 0; if (!read_exact(in, &loader, sizeof(loader))) return false; r.key.loader = (LoaderId)loader; + if (!read_exact(in, &r.comp_level, sizeof(r.comp_level))) return false; if (!read_u4(in, r.mdo_size)) return false; if (!read_u4(in, r.fixup_count)) return false; fixups = nullptr; @@ -349,12 +411,43 @@ bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& if (mdo_bytes == nullptr) { if (fixups) os::free(fixups); return false; } if (!read_exact(in, mdo_bytes, r.mdo_size)) { os::free(mdo_bytes); if (fixups) os::free(fixups); return false; } } + header_bytes = nullptr; + if (!read_u4(in, r.header_size)) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + if (r.header_size > 0) { + header_bytes = (char*)os::malloc(r.header_size, mtInternal); + if (header_bytes == nullptr) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + if (!read_exact(in, header_bytes, r.header_size)) { + os::free(header_bytes); + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + } if (!read_u4(in, r.mc_size)) return false; mc_bytes = nullptr; if (r.mc_size > 0) { mc_bytes = (char*)os::malloc(r.mc_size, mtInternal); - if (mc_bytes == nullptr) { if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); return false; } - if (!read_exact(in, mc_bytes, r.mc_size)) { os::free(mc_bytes); if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); return false; } + if (mc_bytes == nullptr) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (header_bytes) os::free(header_bytes); + return false; + } + if (!read_exact(in, mc_bytes, r.mc_size)) { + os::free(mc_bytes); + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (header_bytes) os::free(header_bytes); + return false; + } } return true; } @@ -393,10 +486,47 @@ Method* ProfileCheckpoint::Loader::resolve_method_utf8(InstanceKlass* ik, const return ik->find_method(mn, sg); } +static void trigger_eager_compile(Method* target, u1 stored_level, JavaThread* thread) { + if (!EagerCompileAfterLoad) return; + if (target == nullptr || thread == nullptr) return; + if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) return; + if (target->is_abstract() || target->is_native()) return; + CompLevel level = (CompLevel)stored_level; + if (level < CompLevel_none) { + level = CompLevel_none; + } else if (level > CompLevel_full_optimization) { + level = CompLevel_full_optimization; + } + JavaThread* THREAD = thread; // For exception macros. + methodHandle mh(THREAD, target); + log_info(compilation)("Eager compiling %s %s %s at level %u", target->name()->as_utf8(), target->signature()->as_utf8(), target->method_holder()->name()->as_utf8(), level); + CompileBroker::compile_method(mh, InvocationEntryBci, level, 0, CompileTask::Reason_MustBeCompiled, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } +} + +void ProfileCheckpoint::wait_for_compile_completion(JavaThread* THREAD) { + if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) { + return; + } + + // Block until compile queues are drained and no active tasks remain. + for (;;) { + CompileBroker::wait_for_no_active_tasks(); + CompileQueue* q1 = CompileBroker::c1_compile_queue(); + CompileQueue* q2 = CompileBroker::c2_compile_queue(); + bool empty1 = (q1 == nullptr) || q1->is_empty(); + bool empty2 = (q2 == nullptr) || q2->is_empty(); + if (empty1 && empty2) break; + os::naked_short_sleep(1); + } + log_info(compilation)("Eager compilation completed"); +} + bool ProfileCheckpoint::Loader::install_record(const Record& rec, Fixup* fixups, char* mdo_bytes, char* mc_bytes, + char* header_bytes, GrowableArray& symtab) { JavaThread* THREAD = _thread; // For exception macros. const char* kname = symtab.at((int)rec.key.klass.id); @@ -435,6 +565,19 @@ bool ProfileCheckpoint::Loader::install_record(const Record& rec, return false; } + if (header_bytes != nullptr) { + if (rec.header_size == sizeof(MethodData::HeaderSnapshot)) { + MethodData::HeaderSnapshot snapshot; + Copy::conjoint_jbytes(header_bytes, (char*)&snapshot, rec.header_size); + if (!mdo->restore_header(snapshot)) { + log_debug(compilation)("MDO checkpoint: header restore mismatch for %s %s %s", kname, mname, msig); + } + } else { + log_debug(compilation)("MDO checkpoint: header size mismatch for %s %s %s (rec=%u expected=%zu)", + kname, mname, msig, rec.header_size, sizeof(MethodData::HeaderSnapshot)); + } + } + sanitize_type_entries(mdo); if (fixups != nullptr && rec.fixup_count > 0) { apply_fixups(mdo, fixups, rec.fixup_count, symtab, rec.key.loader, THREAD); @@ -466,8 +609,12 @@ bool ProfileCheckpoint::Loader::install_record(const Record& rec, } } + trigger_eager_compile(target, rec.comp_level, THREAD); + if (PrintMDOAfterLoad) { tty->print_cr("[AfterLoad] %s %s %s", kname, mname, msig); + tty->print_cr("[MethodDataHeader]"); + print_mdo_header(mdo); tty->print_cr("[MethodData]"); mdo->print_data_on(tty); tty->print_cr("[MethodCounters]"); @@ -519,12 +666,14 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( Fixup* fixups = nullptr; char* mdo_bytes = nullptr; char* mc_bytes = nullptr; - if (!reader.read_record(rec, fixups, mdo_bytes, mc_bytes)) { + char* header_bytes = nullptr; + if (!reader.read_record(rec, fixups, mdo_bytes, mc_bytes, header_bytes)) { log_debug(compilation)("MDO checkpoint: record read failed at %u", i); result.status = LoadStatus::RecordReadFailed; if (fixups) os::free(fixups); if (mdo_bytes) os::free(mdo_bytes); if (mc_bytes) os::free(mc_bytes); + if (header_bytes) os::free(header_bytes); break; } @@ -539,11 +688,12 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( continue; } - install_record(rec, fixups, mdo_bytes, mc_bytes, symtab); + install_record(rec, fixups, mdo_bytes, mc_bytes, header_bytes, symtab); if (fixups != nullptr) os::free(fixups); if (mdo_bytes != nullptr) os::free(mdo_bytes); if (mc_bytes != nullptr) os::free(mc_bytes); + if (header_bytes != nullptr) os::free(header_bytes); } fclose(f); @@ -566,6 +716,22 @@ static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { r.sig = m->signature()->as_utf8(); r.mdo_size = (u4)mdo->size_in_bytes(); r.mdo_ptr = (const void*)mdo; + r.comp_level = (u1)CompLevel_none; + if (UseCompiler) { + CompLevel level = CompLevel_none; + nmethod* code = m->code(); + if (code != nullptr && code->is_in_use()) { + level = (CompLevel)code->comp_level(); + } else { + level = (CompLevel)m->highest_comp_level(); + } + if (level < CompLevel_none) { + level = CompLevel_none; + } else if (level > CompLevel_full_optimization) { + level = CompLevel_full_optimization; + } + r.comp_level = (u1)level; + } return r; } @@ -618,8 +784,8 @@ bool ProfileCheckpoint::BinaryStreamWriter::write_symtab(const GrowableArray& sy } bool ProfileCheckpoint::BinaryStreamReader::read_record(Record& r, Fixup*& fixups, - char*& mdo_bytes, char*& mc_bytes) const { - return Record::read(_in, r, fixups, mdo_bytes, mc_bytes); + char*& mdo_bytes, char*& mc_bytes, + char*& header_bytes) const { + return Record::read(_in, r, fixups, mdo_bytes, mc_bytes, header_bytes); } void ProfileCheckpoint::load(JavaThread* THREAD) { @@ -650,9 +817,7 @@ void ProfileCheckpoint::load(JavaThread* THREAD) { if (res.ok()) { log_info(compilation)("MDO checkpoint: loaded %d records (%d installed, %d size mismatch)", res.records_read, res.records_installed, res.size_mismatch); - if (EagerCompileAllLoaded) { - eager_compile_after_load(THREAD); - } + wait_for_compile_completion(THREAD); } else { log_warning(compilation)("MDO checkpoint: load failed (status=%s, read=%d, installed=%d, mismatches=%d)", Loader::load_status_name(res.status), @@ -708,6 +873,8 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { Method* m = methods.at(ri); MethodData* mdo = m->method_data(); Record rec; + + // todo: handle custom loaders oop cl = m->method_holder()->class_loader(); if (cl == nullptr) { rec.key.loader = LoaderId::BOOT; @@ -721,10 +888,15 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { rec.key.sig.id = stb.id_of(rn.sig); rec.key.bytecode_crc32 = 0; rec.mdo_size = rn.mdo_size; + rec.comp_level = rn.comp_level; GrowableArray* fx_entries = fixups_per_rec.at(ri); rec.fixup_count = (u4)fx_entries->length(); const void* mc_bytes = nullptr; + MethodData::HeaderSnapshot header_snapshot; + mdo->snapshot_header(&header_snapshot); + rec.header_size = sizeof(header_snapshot); + const void* header_bytes = &header_snapshot; GrowableArray mc_buf(0); MethodCounters* mc = m->method_counters(); if (mc != nullptr) { @@ -745,14 +917,17 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { rec.mc_size = 0; } Fixup* fixup_buf = rec.fixup_count ? fx_entries->adr_at(0) : nullptr; - if (!writer.write_record(rec, rn.mdo_ptr, fixup_buf, mc_bytes)) { + if (!writer.write_record(rec, rn.mdo_ptr, fixup_buf, mc_bytes, header_bytes)) { return false; } if (PrintMDOAtDump) { const char* kname = rn.kname; const char* mname = rn.mname; const char* sig = rn.sig; - tty->print_cr("[Dump] %s %s %s", kname, mname, sig); + u1 comp_level = rn.comp_level; + tty->print_cr("[Dump] %s %s %s %u", kname, mname, sig, comp_level); + tty->print_cr("[MethodDataHeader]"); + print_mdo_header(mdo); tty->print_cr("[MethodData]"); mdo->print_data_on(tty); tty->print_cr("[MethodCounters]"); @@ -774,62 +949,3 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { log_warning(compilation)("MDO checkpoint: dump failed"); } } - - -void ProfileCheckpoint::eager_compile_after_load(JavaThread* THREAD) { - if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) { - return; - } - log_info(compilation)("Eager compiling all loaded methods"); - // Optionally run on the specified main class - if (EagerMainClass != nullptr) { - ResourceMark rm(THREAD); - const char* dotted = EagerMainClass; - size_t len = strlen(dotted); - char* slash_name = NEW_RESOURCE_ARRAY(char, len + 1); - for (size_t i = 0; i < len; i++) { - char c = dotted[i]; - slash_name[i] = (c == '.') ? '/' : c; - } - slash_name[len] = '\0'; - TempNewSymbol name_sym = SymbolTable::new_symbol(slash_name); - Klass* k = SystemDictionary::resolve_or_null(name_sym, THREAD); - if (k != nullptr) { - InstanceKlass* ik = InstanceKlass::cast(k); - if (!ik->is_initialized()) { - ik->initialize(THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - } - } - } - - // Visit all loaded methods and trigger the standard policy transition. - struct EagerCompileVisitor { - static void visit(Method* m) { - if (m == nullptr) return; - if (m->is_abstract() || m->is_native()) return; - JavaThread* THREAD = JavaThread::current(); // For exception macros. - methodHandle mh(THREAD, m); - nmethod* code = m->code(); - CompLevel level = (code != nullptr && code->is_in_use()) ? (CompLevel)code->comp_level() : CompLevel_none; - // Standard transition for normal invocation events. - CompilationPolicy::event(mh, mh, InvocationEntryBci, InvocationEntryBci, level, nullptr, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - } - }; - SystemDictionary::methods_do(EagerCompileVisitor::visit); - - // Block until compile queues are drained and no active tasks remain. - for (;;) { - CompileBroker::wait_for_no_active_tasks(); - CompileQueue* q1 = CompileBroker::c1_compile_queue(); - CompileQueue* q2 = CompileBroker::c2_compile_queue(); - bool empty1 = (q1 == nullptr) || q1->is_empty(); - bool empty2 = (q2 == nullptr) || q2->is_empty(); - if (empty1 && empty2) break; - os::naked_short_sleep(1); - } - log_info(compilation)("Eager compilation completed"); -} - - diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 1476b91e0af..2a4c294819d 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -12,7 +12,6 @@ class ProfileCheckpoint { // Binary format v3 (File = [HEADER][SYMTAB][RECORDS]) // HEADER: // magic[4] = 'M','D','O','X' - // version : u32 (==3) // pointer_size: u16 (e.g., 8) // endianness : u16 (0=little, 1=big) // layout flags: see LayoutFlags @@ -72,11 +71,11 @@ class ProfileCheckpoint { const char* sig; u4 mdo_size; const void* mdo_ptr; + u1 comp_level; }; struct Header { char magic[4]; - u4 version; u2 pointer_size; u2 endianness; LayoutFlags layout; @@ -92,11 +91,13 @@ class ProfileCheckpoint { MethodKey key; u4 mdo_size; u4 fixup_count; + u4 header_size; u4 mc_size; // bytes of MethodCounters snapshot (may be 0) + u1 comp_level; static bool write(fileStream* out, const Record& r, const void* mdo_bytes, - const Fixup* fixups, const void* mc_bytes); - static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes); + const Fixup* fixups, const void* mc_bytes, const void* header_bytes); + static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes); }; class BinaryStreamWriter { @@ -106,7 +107,7 @@ class ProfileCheckpoint { bool write_header(u4 sym_count, u4 rec_count); bool write_symtab(const GrowableArray& symbols) const; bool write_record(const Record& r, const void* mdo_bytes, - const Fixup* fixups, const void* mc_bytes) const; + const Fixup* fixups, const void* mc_bytes, const void* header_bytes) const; }; class BinaryStreamReader { @@ -115,7 +116,7 @@ class ProfileCheckpoint { explicit BinaryStreamReader(FILE* in) : _in(in) {} bool read_header(Header& h) const; bool read_symtab(GrowableArray& symbols, u4 expected) const; - bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes) const; + bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) const; }; class SymtabBuilder { @@ -164,6 +165,7 @@ class ProfileCheckpoint { Fixup* fixups, char* mdo_bytes, char* mc_bytes, + char* header_bytes, GrowableArray& symtab); static class InstanceKlass* resolve_klass_utf8(const char* name, TRAPS); @@ -174,7 +176,7 @@ class ProfileCheckpoint { static void load(class JavaThread* THREAD); static void dump_to_stream(class fileStream* out); - static void eager_compile_after_load(class JavaThread* THREAD); + static void wait_for_compile_completion(class JavaThread* THREAD); }; #endif // SHARE_SERVICES_PROFILECHECKPOINT_HPP diff --git a/src/hotspot/share/services/profileCheckpoint_globals.hpp b/src/hotspot/share/services/profileCheckpoint_globals.hpp index 6edf3d7aebf..b61c909f734 100644 --- a/src/hotspot/share/services/profileCheckpoint_globals.hpp +++ b/src/hotspot/share/services/profileCheckpoint_globals.hpp @@ -29,14 +29,8 @@ product(bool, PrintMDOAfterLoad, false, DIAGNOSTIC, \ "Debug: print MethodData/MethodCounters after loading each method") \ \ - product(bool, EagerCompileAllLoaded, false, DIAGNOSTIC, \ - "After loading MDOs at startup, run on EagerMainClass " \ - "if set, then iterate all loaded methods, trigger standard policy " \ - "transition, and queue compiles; block until compile queues drain") \ - \ - product(ccstr, EagerMainClass, nullptr, DIAGNOSTIC, \ - "Dotted name of the main class whose should be run " \ - "before eager compilation (e.g., com.example.Main)") + product(bool, EagerCompileAfterLoad, false, DIAGNOSTIC, \ + "After loading MDOs at startup, run eager compilation") DECLARE_FLAGS(PROFILECHECKPOINT_FLAGS) From 7df721293141f3d2881b7a07766f56e062fae6ff Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 20 Nov 2025 01:30:44 -0800 Subject: [PATCH 10/23] properly derive and pass loader_ids with class names, refactor --- .../share/services/profileCheckpoint.cpp | 83 ++++++++++--------- .../share/services/profileCheckpoint.hpp | 8 +- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index c73856d9c2b..6c0e6bc0598 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -92,6 +92,37 @@ static void print_mdo_header(MethodData* mdo, outputStream* st = tty) { st->cr(); } +static ProfileCheckpoint::LoaderId loader_id_from_loader(ClassLoaderData* cld) { + if (cld == nullptr || cld->is_boot_class_loader_data()) return ProfileCheckpoint::LoaderId::BOOT; + if (cld->is_platform_class_loader_data()) return ProfileCheckpoint::LoaderId::PLATFORM; + if (cld->is_system_class_loader_data()) return ProfileCheckpoint::LoaderId::SYSTEM; + return ProfileCheckpoint::LoaderId::UNDEFINED; +} + +static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, TRAPS) { + switch (loader_id) { + case ProfileCheckpoint::LoaderId::BOOT: return Handle(); + case ProfileCheckpoint::LoaderId::PLATFORM: return Handle(THREAD, SystemDictionary::java_platform_loader()); + case ProfileCheckpoint::LoaderId::SYSTEM: return Handle(THREAD, SystemDictionary::java_system_loader()); + default: return Handle(); + } +} + +static InstanceKlass* resolve_klass_utf8(const char* name, ProfileCheckpoint::LoaderId loader_id, TRAPS) { + Symbol* sym = SymbolTable::new_symbol(name); + Handle loader = loader_handle_from_loader(loader_id, THREAD); + Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } + return (k != nullptr && k->is_instance_klass()) ? InstanceKlass::cast(k) : nullptr; +} + +static Method* resolve_method_utf8(InstanceKlass* ik, const char* mname, const char* msig) { + if (ik == nullptr) return nullptr; + Symbol* mn = SymbolTable::new_symbol(mname); + Symbol* sg = SymbolTable::new_symbol(msig); + return ik->find_method(mn, sg); +} + static void sanitize_type_entries(MethodData* mdo) { for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { @@ -159,6 +190,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -176,6 +208,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -190,6 +223,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -207,6 +241,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -221,6 +256,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -238,6 +274,7 @@ static void collect_type_fixups(MethodData* mdo, fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); } } @@ -248,15 +285,7 @@ static void apply_fixups(MethodData* mdo, const ProfileCheckpoint::Fixup* fixups, u4 fixup_count, GrowableArray& symtab, - ProfileCheckpoint::LoaderId loader, TRAPS) { - Handle loader_h; - switch (loader) { - case ProfileCheckpoint::LoaderId::BOOT: loader_h = Handle(); break; - case ProfileCheckpoint::LoaderId::PLATFORM: loader_h = Handle(THREAD, SystemDictionary::java_platform_loader()); break; - case ProfileCheckpoint::LoaderId::APP: loader_h = Handle(THREAD, SystemDictionary::java_system_loader()); break; - default: loader_h = Handle(); break; - } for (u4 fi = 0; fi < fixup_count; fi++) { const ProfileCheckpoint::Fixup& fx = fixups[fi]; if (fx.kind != ProfileCheckpoint::FixupKind::KLASS) { @@ -270,16 +299,14 @@ static void apply_fixups(MethodData* mdo, continue; } const char* cname = symtab.at((int)fx.target.id); - Symbol* sym = SymbolTable::new_symbol(cname); - Klass* k = SystemDictionary::resolve_or_fail(sym, loader_h, true, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; k = nullptr; } - if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* k = resolve_klass_utf8(cname, fx.loader, THREAD); + if (k != nullptr) { address cell_addr = (address)mdo + fx.offset_in_mdo; intptr_t* cell = (intptr_t*)cell_addr; *cell = TypeEntries::with_status(InstanceKlass::cast(k), *cell); } else { log_debug(compilation)("MDO checkpoint: fixup unresolved %s (loader=%d) at off=%u", - cname, (int)loader, fx.offset_in_mdo); + cname, (int)fx.loader, fx.offset_in_mdo); } } } @@ -470,22 +497,6 @@ const char* ProfileCheckpoint::Loader::load_status_name(LoadStatus status) { } } -InstanceKlass* ProfileCheckpoint::Loader::resolve_klass_utf8(const char* name, TRAPS) { - Symbol* sym = SymbolTable::new_symbol(name); - oop sys_loader_oop = SystemDictionary::java_system_loader(); - Handle loader(THREAD, sys_loader_oop); - Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } - return (k != nullptr && k->is_instance_klass()) ? InstanceKlass::cast(k) : nullptr; -} - -Method* ProfileCheckpoint::Loader::resolve_method_utf8(InstanceKlass* ik, const char* mname, const char* msig) { - if (ik == nullptr) return nullptr; - Symbol* mn = SymbolTable::new_symbol(mname); - Symbol* sg = SymbolTable::new_symbol(msig); - return ik->find_method(mn, sg); -} - static void trigger_eager_compile(Method* target, u1 stored_level, JavaThread* thread) { if (!EagerCompileAfterLoad) return; if (target == nullptr || thread == nullptr) return; @@ -533,7 +544,7 @@ bool ProfileCheckpoint::Loader::install_record(const Record& rec, const char* mname = symtab.at((int)rec.key.name.id); const char* msig = symtab.at((int)rec.key.sig.id); - InstanceKlass* holder = resolve_klass_utf8(kname, THREAD); + InstanceKlass* holder = resolve_klass_utf8(kname, rec.key.loader, THREAD); if (holder == nullptr) { log_debug(compilation)("MDO checkpoint: resolve class failed for %s", kname); return false; @@ -580,7 +591,7 @@ bool ProfileCheckpoint::Loader::install_record(const Record& rec, sanitize_type_entries(mdo); if (fixups != nullptr && rec.fixup_count > 0) { - apply_fixups(mdo, fixups, rec.fixup_count, symtab, rec.key.loader, THREAD); + apply_fixups(mdo, fixups, rec.fixup_count, symtab, THREAD); } _records_installed++; @@ -874,15 +885,7 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { MethodData* mdo = m->method_data(); Record rec; - // todo: handle custom loaders - oop cl = m->method_holder()->class_loader(); - if (cl == nullptr) { - rec.key.loader = LoaderId::BOOT; - } else if (SystemDictionary::is_platform_class_loader(cl)) { - rec.key.loader = LoaderId::PLATFORM; - } else { - rec.key.loader = LoaderId::APP; - } + rec.key.loader = loader_id_from_loader(m->method_holder()->class_loader_data()); rec.key.klass.id = stb.id_of(rn.kname); rec.key.name.id = stb.id_of(rn.mname); rec.key.sig.id = stb.id_of(rn.sig); diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 2a4c294819d..b9a92ef2c68 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -25,7 +25,7 @@ class ProfileCheckpoint { // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] - enum class LoaderId : u1 { BOOT, PLATFORM, APP, UNDEFINED }; + enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED }; enum class FixupKind : u1 { KLASS, METHOD }; struct SymbolId { uint32_t id; }; @@ -34,6 +34,7 @@ class ProfileCheckpoint { uint32_t offset_in_mdo; FixupKind kind; SymbolId target; + LoaderId loader; }; struct ByteRange { uint64_t off; uint32_t size; }; @@ -167,11 +168,6 @@ class ProfileCheckpoint { char* mc_bytes, char* header_bytes, GrowableArray& symtab); - - static class InstanceKlass* resolve_klass_utf8(const char* name, TRAPS); - static class Method* resolve_method_utf8(class InstanceKlass* ik, - const char* mname, - const char* msig); }; static void load(class JavaThread* THREAD); From 1ba702fa3254341ae627fae0e0e9d71fc745354b Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 20 Nov 2025 01:35:52 -0800 Subject: [PATCH 11/23] CLDG methods_do --- src/hotspot/share/services/profileCheckpoint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 6c0e6bc0598..39709b6d0be 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -2,6 +2,7 @@ #include "utilities/ostream.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/symbolTable.hpp" +#include "classfile/classLoaderDataGraph.hpp" #include "services/profileCheckpoint_globals.hpp" #include "oops/instanceKlass.hpp" #include "oops/methodData.hpp" @@ -756,7 +757,7 @@ GrowableArray get_methods() { g_methods->push(m); } }; - SystemDictionary::methods_do(collect_with_mdo); + ClassLoaderDataGraph::methods_do(collect_with_mdo); return methods; } From ebd5bb99ef5f43270819dc503da381f08ea40e1c Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Fri, 21 Nov 2025 18:45:50 -0600 Subject: [PATCH 12/23] eager load all classes --- .../share/services/profileCheckpoint.cpp | 119 ++++++++++++++---- .../share/services/profileCheckpoint.hpp | 90 +++++++------ 2 files changed, 147 insertions(+), 62 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 39709b6d0be..4c02a1e4d35 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -3,6 +3,7 @@ #include "classfile/systemDictionary.hpp" #include "classfile/symbolTable.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "classfile/classLoaderData.hpp" #include "services/profileCheckpoint_globals.hpp" #include "oops/instanceKlass.hpp" #include "oops/methodData.hpp" @@ -189,7 +190,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -207,7 +207,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -222,7 +221,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -240,7 +238,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -255,7 +252,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -273,7 +269,6 @@ static void collect_type_fixups(MethodData* mdo, const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); - fx.kind = ProfileCheckpoint::FixupKind::KLASS; fx.target.id = stb.intern(cname); fx.loader = loader_id_from_loader(k->class_loader_data()); out_fixups.append(fx); @@ -289,11 +284,6 @@ static void apply_fixups(MethodData* mdo, TRAPS) { for (u4 fi = 0; fi < fixup_count; fi++) { const ProfileCheckpoint::Fixup& fx = fixups[fi]; - if (fx.kind != ProfileCheckpoint::FixupKind::KLASS) { - log_debug(compilation)("MDO checkpoint: unsupported fixup kind=%d at off=%u sym_id=%u", - (int)fx.kind, fx.offset_in_mdo, fx.target.id); - continue; - } if ((int)fx.target.id < 0 || (int)fx.target.id >= symtab.length()) { log_debug(compilation)("MDO checkpoint: invalid sym_id=%u (symtab_len=%d) at off=%u", fx.target.id, symtab.length(), fx.offset_in_mdo); @@ -326,10 +316,18 @@ static bool write_u4(fileStream* out, u4 v) { return write_exact(out, &v, sizeof(v)); } +static bool write_u1(fileStream* out, u1 v) { + return write_exact(out, &v, sizeof(v)); +} + static bool read_u4(FILE* in, u4& v) { return read_exact(in, &v, sizeof(v)); } +static bool read_u1(FILE* in, u1& v) { + return read_exact(in, &v, sizeof(v)); +} + static char* read_str(FILE* in) { u4 len = 0; if (!read_u4(in, len)) return nullptr; @@ -350,6 +348,7 @@ bool ProfileCheckpoint::Header::write(fileStream* out, const Header& h) { if (!write_exact(out, &h.layout, sizeof(h.layout))) return false; if (!write_u4(out, h.sym_count)) return false; if (!write_u4(out, h.rec_count)) return false; + if (!write_u4(out, h.class_count)) return false; return true; } @@ -361,6 +360,7 @@ bool ProfileCheckpoint::Header::read(FILE* in, Header& h) { if (!read_exact(in, &h.layout, sizeof(h.layout))) return false; if (!read_u4(in, h.sym_count)) return false; if (!read_u4(in, h.rec_count)) return false; + if (!read_u4(in, h.class_count)) return false; return true; } @@ -369,7 +369,7 @@ static u2 detect_endianness() { return (u.b[0] == 1) ? (u2)0 : (u2)1; } -void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count) { +void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count, u4 class_count) { h.magic[0] = 'M'; h.magic[1] = 'D'; h.magic[2] = 'O'; h.magic[3] = 'X'; h.pointer_size = (u2)sizeof(void*); h.endianness = detect_endianness(); @@ -382,6 +382,7 @@ void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count) { h.layout.spec_trap_limit_extra_entries = (int32_t)SpecTrapLimitExtraEntries; h.sym_count = sym_count; h.rec_count = rec_count; + h.class_count = class_count; } bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes) { @@ -395,8 +396,6 @@ bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const vo if (!write_u4(out, r.fixup_count)) return false; for (u4 i = 0; i < r.fixup_count; i++) { if (!write_u4(out, fixups[i].offset_in_mdo)) return false; - u1 kind = (u1)fixups[i].kind; - if (!write_exact(out, &kind, sizeof(kind))) return false; if (!write_u4(out, fixups[i].target.id)) return false; } if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; @@ -426,10 +425,7 @@ bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& fixups = (Fixup*)os::malloc(sizeof(Fixup) * r.fixup_count, mtInternal); if (fixups == nullptr) return false; for (u4 i = 0; i < r.fixup_count; i++) { - u1 kind = 0; if (!read_u4(in, fixups[i].offset_in_mdo)) { os::free(fixups); return false; } - if (!read_exact(in, &kind, sizeof(kind))) { os::free(fixups); return false; } - fixups[i].kind = (FixupKind)kind; if (!read_u4(in, fixups[i].target.id)) { os::free(fixups); return false; } } } @@ -670,6 +666,29 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( result.status = LoadStatus::SymtabReadFailed; return result; } + GrowableArray classes((int)hdr.class_count); + if (!reader.read_classes(classes, hdr.class_count)) { + fclose(f); + result.status = LoadStatus::RecordReadFailed; + return result; + } + + JavaThread* THREAD = _thread; // For exception macros. + for (int ci = 0; ci < classes.length(); ci++) { + const ProfileCheckpoint::Class& cls = classes.at(ci); + if ((int)cls.klass.id >= symtab.length()) { + continue; + } + const char* cname = symtab.at((int)cls.klass.id); + InstanceKlass* holder = resolve_klass_utf8(cname, cls.loader, THREAD); + if (holder != nullptr) { + holder->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } else { + log_debug(compilation)("MDO checkpoint: preload class failed for %s (loader=%d)", + cname, (int)cls.loader); + } + } result.status = LoadStatus::Success; @@ -748,6 +767,30 @@ static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { } // Get all methods with MDO data +class CollectClassesClosure : public KlassClosure { + ProfileCheckpoint::SymtabBuilder* _stb; + GrowableArray* _classes; +public: + CollectClassesClosure(ProfileCheckpoint::SymtabBuilder* stb, + GrowableArray* classes) + : _stb(stb), _classes(classes) {} + + virtual void do_klass(Klass* k) { + if (k == nullptr || !k->is_instance_klass()) return; + InstanceKlass* ik = InstanceKlass::cast(k); + ProfileCheckpoint::Class c; + c.loader = loader_id_from_loader(ik->class_loader_data()); + c.klass.id = _stb->intern(ik->name()->as_utf8()); + _classes->append(c); + } +}; + +static void collect_classes(ProfileCheckpoint::SymtabBuilder& stb, + GrowableArray& classes) { + CollectClassesClosure closure(&stb, &classes); + ClassLoaderDataGraph::classes_do(&closure); +} + GrowableArray get_methods() { GrowableArray methods(1024); static GrowableArray* g_methods; @@ -779,9 +822,9 @@ u4 ProfileCheckpoint::SymtabBuilder::id_of(const char* s) const { return (u4)UINT_MAX; } -bool ProfileCheckpoint::BinaryStreamWriter::write_header(u4 sym_count, u4 rec_count) { +bool ProfileCheckpoint::BinaryStreamWriter::write_header(u4 sym_count, u4 rec_count, u4 class_count) { Header h; - Header::init(h, sym_count, rec_count); + Header::init(h, sym_count, rec_count, class_count); return Header::write(_out, h); } @@ -795,6 +838,15 @@ bool ProfileCheckpoint::BinaryStreamWriter::write_symtab(const GrowableArray& classes) const { + for (int i = 0; i < classes.length(); i++) { + const Class& c = classes.at(i); + if (!write_u1(_out, (u1)c.loader)) return false; + if (!write_u4(_out, c.klass.id)) return false; + } + return true; +} + bool ProfileCheckpoint::BinaryStreamWriter::write_record(const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes) const { return Record::write(_out, r, mdo_bytes, fixups, mc_bytes, header_bytes); @@ -815,6 +867,24 @@ bool ProfileCheckpoint::BinaryStreamReader::read_symtab(GrowableArray& sy return true; } +bool ProfileCheckpoint::BinaryStreamReader::read_classes(GrowableArray& classes, u4 expected) const { + for (u4 ci = 0; ci < expected; ci++) { + u1 loader_raw = 0; + if (!read_u1(_in, loader_raw)) { + return false; + } + u4 klass_id = 0; + if (!read_u4(_in, klass_id)) { + return false; + } + Class c; + c.loader = (LoaderId)loader_raw; + c.klass.id = klass_id; + classes.append(c); + } + return true; +} + bool ProfileCheckpoint::BinaryStreamReader::read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) const { @@ -845,12 +915,14 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { } ResourceMark rm; - GrowableArray methods = get_methods(); SymtabBuilder stb; + GrowableArray classes(1024); + collect_classes(stb, classes); + GrowableArray methods = get_methods(); GrowableArray recs(1024); for (int i = 0; i < methods.length(); i++) { - Method* m = methods.at(i); + Method* m = methods.at(i); if (m->method_data() == nullptr) continue; recs.append(make_rec_meta_for_method(m)); } @@ -872,12 +944,15 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { stb.freeze(); BinaryStreamWriter writer(out); - if (!writer.write_header(stb.length(), (u4)recs.length())) { + if (!writer.write_header(stb.length(), (u4)recs.length(), (u4)classes.length())) { return false; } if (!writer.write_symtab(stb.symbols())) { return false; } + if (!writer.write_classes(classes)) { + return false; + } u4 emitted = 0; for (int ri = 0; ri < recs.length(); ri++) { diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index b9a92ef2c68..5c51e9519c2 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -9,45 +9,47 @@ class fileStream; class ProfileCheckpoint { public: - // Binary format v3 (File = [HEADER][SYMTAB][RECORDS]) - // HEADER: - // magic[4] = 'M','D','O','X' - // pointer_size: u16 (e.g., 8) - // endianness : u16 (0=little, 1=big) - // layout flags: see LayoutFlags - // sym_count : u32 - // rec_count : u32 - // - // SYMTAB: repeated sym_count times - // [u32 len][len bytes utf8] - // - // RECORD: repeated rec_count times - // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, - // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] + // Binary format v3 (File = [HEADER][SYMTAB][RECORDS]) + // HEADER: + // magic[4] = 'M','D','O','X' + // pointer_size: u16 (e.g., 8) + // endianness : u16 (0=little, 1=big) + // layout flags: see LayoutFlags + // sym_count : u32 + // rec_count : u32 + // class_count : u32 + // + // SYMTAB: repeated sym_count times + // [u32 len][len bytes utf8] + // + // CLASS: repeated class_count times: + // [u4 loader_id][u4 klass_sym_id] + // + // RECORD: repeated rec_count times + // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, + // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED }; - enum class FixupKind : u1 { KLASS, METHOD }; struct SymbolId { uint32_t id; }; struct Fixup { uint32_t offset_in_mdo; - FixupKind kind; SymbolId target; LoaderId loader; }; - struct ByteRange { uint64_t off; uint32_t size; }; + struct ByteRange { uint64_t off; uint32_t size; }; - struct LayoutFlags { - uint32_t type_profile_level; - int32_t type_profile_args_limit; - int32_t type_profile_parms_limit; - int64_t type_profile_width; - uint8_t profile_traps; - uint8_t type_profile_casts; - int32_t spec_trap_limit_extra_entries; - }; + struct LayoutFlags { + uint32_t type_profile_level; + int32_t type_profile_args_limit; + int32_t type_profile_parms_limit; + int64_t type_profile_width; + uint8_t profile_traps; + uint8_t type_profile_casts; + int32_t spec_trap_limit_extra_entries; + }; struct MethodKey { LoaderId loader; @@ -75,38 +77,45 @@ class ProfileCheckpoint { u1 comp_level; }; - struct Header { + struct Header { char magic[4]; - u2 pointer_size; - u2 endianness; - LayoutFlags layout; + u2 pointer_size; + u2 endianness; + LayoutFlags layout; u4 sym_count; u4 rec_count; + u4 class_count; - static void init(Header& h, u4 sym_count, u4 rec_count); + static void init(Header& h, u4 sym_count, u4 rec_count, u4 class_count); static bool write(fileStream* out, const Header& h); static bool read(FILE* in, Header& h); }; - struct Record { - MethodKey key; - u4 mdo_size; - u4 fixup_count; + struct Class { + LoaderId loader; + SymbolId klass; + }; + + struct Record { + MethodKey key; + u4 mdo_size; + u4 fixup_count; u4 header_size; - u4 mc_size; // bytes of MethodCounters snapshot (may be 0) + u4 mc_size; // bytes of MethodCounters snapshot (may be 0) u1 comp_level; - static bool write(fileStream* out, const Record& r, const void* mdo_bytes, + static bool write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes); static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes); - }; + }; class BinaryStreamWriter { fileStream* _out; public: explicit BinaryStreamWriter(fileStream* out) : _out(out) {} - bool write_header(u4 sym_count, u4 rec_count); + bool write_header(u4 sym_count, u4 rec_count, u4 class_count); bool write_symtab(const GrowableArray& symbols) const; + bool write_classes(const GrowableArray& classes) const; bool write_record(const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes) const; }; @@ -117,6 +126,7 @@ class ProfileCheckpoint { explicit BinaryStreamReader(FILE* in) : _in(in) {} bool read_header(Header& h) const; bool read_symtab(GrowableArray& symbols, u4 expected) const; + bool read_classes(GrowableArray& classes, u4 expected) const; bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) const; }; From c6363efa636d1c6f15ea2ec3630257a539db2361 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Wed, 3 Dec 2025 22:56:44 -0800 Subject: [PATCH 13/23] dynolocator pass for dump side, to handle hidden classes --- .../share/services/dynoLocatorScan.cpp | 280 ++++++++++++++++++ .../share/services/dynoLocatorScan.hpp | 17 ++ .../share/services/profileCheckpoint.cpp | 64 ++-- .../share/services/profileCheckpoint.hpp | 3 +- 4 files changed, 346 insertions(+), 18 deletions(-) create mode 100644 src/hotspot/share/services/dynoLocatorScan.cpp create mode 100644 src/hotspot/share/services/dynoLocatorScan.hpp diff --git a/src/hotspot/share/services/dynoLocatorScan.cpp b/src/hotspot/share/services/dynoLocatorScan.cpp new file mode 100644 index 00000000000..6203fb246e7 --- /dev/null +++ b/src/hotspot/share/services/dynoLocatorScan.cpp @@ -0,0 +1,280 @@ +#include "services/dynoLocatorScan.hpp" + +#include "classfile/javaClasses.hpp" +#include "classfile/vmClasses.hpp" +#include "ci/ciReplay.hpp" +#include "interpreter/bytecodeStream.hpp" +#include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" +#include "oops/cpCache.inline.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "oops/oop.inline.hpp" +#include "oops/resolvedIndyEntry.hpp" +#include "oops/symbol.hpp" +#include "prims/methodHandles.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/thread.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/threads.hpp" +#include "runtime/mutex.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "runtime/threadSMR.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/ostream.hpp" +#include +#include + +namespace { + +// Limit borrowed from ciEnv::_dyno_name +static const int LOC_BUF_LEN = 1024; + +class DynoLocatorTable { + class Entry { + public: + InstanceKlass* _ik; + const char* _loc; + Entry() : _ik(nullptr), _loc(nullptr) {} + Entry(InstanceKlass* ik, const char* loc) : _ik(ik), _loc(loc) {} + }; + static GrowableArray* _entries; + static Mutex* _lock; + static void ensure_init() { + if (_entries == nullptr) { + _entries = new (mtInternal) GrowableArray(8, mtInternal); + } + if (_lock == nullptr) { + _lock = new Mutex(Mutex::nosafepoint, "DynoLocatorTable"); + } + } +public: + static void record(InstanceKlass* ik, const char* loc) { + log_debug(compilation)("recording ik: %s with name %s", ik->name()->as_utf8(), loc); + if (ik == nullptr || loc == nullptr) return; + ensure_init(); + MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag); + for (int i = 0; i < _entries->length(); i++) { + if (_entries->at(i)._ik == ik) return; + } + const char* copy = os::strdup(loc, mtInternal); + _entries->append(Entry(ik, copy)); + } + static const char* lookup(InstanceKlass* ik) { + if (_entries == nullptr || ik == nullptr) return nullptr; + MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag); + for (int i = 0; i < _entries->length(); i++) { + if (_entries->at(i)._ik == ik) return _entries->at(i)._loc; + } + return nullptr; + } +}; + +GrowableArray* DynoLocatorTable::_entries = nullptr; +Mutex* DynoLocatorTable::_lock = nullptr; + +class RecordLocation { + char* _start; + char* _end; + char* _buf; +public: + ATTRIBUTE_PRINTF(3, 4) + RecordLocation(char* buf, const char* fmt, ...) { + _buf = buf; + _start = _buf + (int)strlen(_buf); + va_list args; + va_start(args, fmt); + _end = _start + os::vsnprintf(_start, LOC_BUF_LEN - (_start - _buf), fmt, args); + va_end(args); + if (_end >= _buf + LOC_BUF_LEN) { + _end = _buf + LOC_BUF_LEN - 1; + *_end = '\0'; + } + } + ~RecordLocation() { + *_start = '\0'; + } +}; + +static void record_hidden(InstanceKlass* ik, const char* loc) { + if (ik != nullptr && ik->is_hidden() && loc != nullptr) { + DynoLocatorTable::record(ik, loc); + } +} + +static void record_call_site_obj(JavaThread* jt, oop obj, char* loc_buf); + +static void record_member(JavaThread* jt, oop member, char* loc_buf) { + assert(java_lang_invoke_MemberName::is_instance(member), "!"); + oop clazz = java_lang_invoke_MemberName::clazz(member); + if (clazz != nullptr && clazz->klass()->is_instance_klass()) { + RecordLocation rl(loc_buf, " clazz"); + InstanceKlass* ik = InstanceKlass::cast(clazz->klass()); + record_hidden(ik, loc_buf); + } + Method* vmtarget = java_lang_invoke_MemberName::vmtarget(member); + if (vmtarget != nullptr) { + RecordLocation rl(loc_buf, " "); + InstanceKlass* ik = vmtarget->method_holder(); + record_hidden(ik, loc_buf); + } +} + +static void record_mh(JavaThread* jt, oop mh, char* loc_buf) { + assert(java_lang_invoke_MethodHandle::is_instance(mh), "!"); + if (java_lang_invoke_DirectMethodHandle::is_instance(mh)) { + oop member = java_lang_invoke_DirectMethodHandle::member(mh); + RecordLocation rl(loc_buf, " member"); + record_member(jt, member, loc_buf); + return; + } + + // For BoundMethodHandle and friends, scan argL* fields. + char arg_name[] = " argLXX"; + const int max_arg = 99; + for (int index = 0; index <= max_arg; ++index) { + jio_snprintf(arg_name, sizeof(arg_name), " argL%d", index); + oop arg = ciReplay::obj_field(mh, arg_name + 1); // reuse helper to read field + if (arg != nullptr) { + RecordLocation rl(loc_buf, "%s", arg_name); + if (arg->klass()->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(arg->klass()); + record_hidden(ik, loc_buf); + record_call_site_obj(jt, arg, loc_buf); + } + } else { + break; + } + } +} + +static void record_call_site_obj(JavaThread* jt, oop obj, char* loc_buf) { + if (obj == nullptr) { + return; + } + if (java_lang_invoke_MethodHandle::is_instance(obj)) { + record_mh(jt, obj, loc_buf); + } else if (java_lang_invoke_ConstantCallSite::is_instance(obj)) { + oop target = java_lang_invoke_CallSite::target(obj); + if (target != nullptr && target->klass()->is_instance_klass()) { + RecordLocation rl(loc_buf, " target"); + InstanceKlass* ik = InstanceKlass::cast(target->klass()); + record_hidden(ik, loc_buf); + record_call_site_obj(jt, target, loc_buf); + } + } +} + +static void record_call_site_method(JavaThread* jt, Method* adapter, char* loc_buf) { + if (adapter == nullptr) return; + InstanceKlass* holder = adapter->method_holder(); + if (!holder->is_hidden()) return; + RecordLocation rl(loc_buf, " "); + record_hidden(holder, loc_buf); +} + +static void process_invokedynamic(const constantPoolHandle& cp, int indy_index, JavaThread* jt, char* loc_buf) { + ResolvedIndyEntry* indy_info = cp->resolved_indy_entry_at(indy_index); + if (indy_info == nullptr || indy_info->method() == nullptr) { + return; + } + // adapter + Method* adapter = indy_info->method(); + record_call_site_method(jt, adapter, loc_buf); + // appendix + oop appendix = cp->resolved_reference_from_indy(indy_index); + { + RecordLocation rl(loc_buf, " "); + record_call_site_obj(jt, appendix, loc_buf); + } + // BSM + int pool_index = indy_info->constant_pool_index(); + BootstrapInfo bootstrap_specifier(cp, pool_index, indy_index); + oop bsm = cp->resolve_possibly_cached_constant_at(bootstrap_specifier.bsm_index(), jt); + { + RecordLocation rl(loc_buf, " "); + record_call_site_obj(jt, bsm, loc_buf); + } +} + +static void process_invokehandle(const constantPoolHandle& cp, int index, JavaThread* jt, char* loc_buf) { + const int holder_index = cp->klass_ref_index_at(index, Bytecodes::_invokehandle); + if (!cp->tag_at(holder_index).is_klass()) { + return; // not resolved + } + Klass* holder = ConstantPool::klass_at_if_loaded(cp, holder_index); + Symbol* name = cp->name_ref_at(index, Bytecodes::_invokehandle); + if (MethodHandles::is_signature_polymorphic_name(holder, name)) { + ResolvedMethodEntry* method_entry = cp->resolved_method_entry_at(index); + if (method_entry->is_resolved(Bytecodes::_invokehandle)) { + Method* adapter = method_entry->method(); + oop appendix = cp->cache()->appendix_if_resolved(method_entry); + record_call_site_method(jt, adapter, loc_buf); + { + RecordLocation rl(loc_buf, " "); + record_call_site_obj(jt, appendix, loc_buf); + } + } + } +} + +} // anonymous namespace + +void DynoLocatorScan::scan_all_classes() { + ResourceMark rm; + char loc_buf[LOC_BUF_LEN]; + loc_buf[0] = '\0'; + + Thread* thread = Thread::current(); + JavaThread* jt = thread->is_Java_thread() ? JavaThread::cast(thread) : nullptr; + if (jt == nullptr) { + // Fallback: use any alive JavaThread as context. + JavaThreadIteratorWithHandle jtiwh; + for (JavaThread* t = jtiwh.next(); t != nullptr; t = jtiwh.next()) { + if (!t->is_terminated()) { jt = t; break; } + } + } + if (jt == nullptr) { + log_debug(compilation)("dls: no JavaThread available"); + return; // cannot walk without a JavaThread context + } + + for (ClassHierarchyIterator iter(vmClasses::Object_klass()); !iter.done(); iter.next()) { + Klass* k = iter.klass(); + if (!k->is_instance_klass()) continue; + InstanceKlass* ik = InstanceKlass::cast(k); + if (!ik->is_linked()) continue; + if (ik->is_hidden()) continue; // only scan non-hidden sources + + + const constantPoolHandle cp(jt, ik->constants()); + Array* methods = ik->methods(); + for (int mi = 0; mi < methods->length(); mi++) { + Method* m = methods->at(mi); + BytecodeStream bcs(methodHandle(jt, m)); + while (!bcs.is_last_bytecode()) { + Bytecodes::Code opcode = bcs.next(); + opcode = bcs.raw_code(); + if (opcode == Bytecodes::_invokedynamic || opcode == Bytecodes::_invokehandle) { + RecordLocation rl(loc_buf, "@bci %s %s %s %d", + ik->name()->as_quoted_ascii(), + m->name()->as_quoted_ascii(), + m->signature()->as_quoted_ascii(), + bcs.bci()); + if (opcode == Bytecodes::_invokedynamic) { + int index = bcs.get_index_u4(); + process_invokedynamic(cp, index, jt, loc_buf); + } else { + int cp_cache_index = bcs.get_index_u2(); + process_invokehandle(cp, cp_cache_index, jt, loc_buf); + } + } + } + } + } +} + +const char* DynoLocatorScan::lookup(InstanceKlass* ik) { + return DynoLocatorTable::lookup(ik); +} diff --git a/src/hotspot/share/services/dynoLocatorScan.hpp b/src/hotspot/share/services/dynoLocatorScan.hpp new file mode 100644 index 00000000000..bfbca01aca9 --- /dev/null +++ b/src/hotspot/share/services/dynoLocatorScan.hpp @@ -0,0 +1,17 @@ +#ifndef SHARE_SERVICES_DYNOLOCATORSCAN_HPP +#define SHARE_SERVICES_DYNOLOCATORSCAN_HPP + +class InstanceKlass; + +class DynoLocatorScan { +public: + // Walk resolved invokedynamic/invokehandle sites in all linked, non-hidden + // classes and record locator strings for any hidden classes they target. + // Intended to be called at a safepoint on the VM thread. + static void scan_all_classes(); + + // Lookup a recorded locator for a hidden class scanned previously. + static const char* lookup(InstanceKlass* ik); +}; + +#endif // SHARE_SERVICES_DYNOLOCATORSCAN_HPP diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 4c02a1e4d35..10a51de07b6 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -20,6 +20,7 @@ #include "compiler/compileBroker.hpp" #include "compiler/compilerDefinitions.hpp" #include "memory/resourceArea.hpp" +#include "services/dynoLocatorScan.hpp" #include #include @@ -94,13 +95,32 @@ static void print_mdo_header(MethodData* mdo, outputStream* st = tty) { st->cr(); } -static ProfileCheckpoint::LoaderId loader_id_from_loader(ClassLoaderData* cld) { +static ProfileCheckpoint::LoaderId loader_id_from_klass(InstanceKlass* ik) { + if (ik != nullptr && ik->is_hidden()) { + return ProfileCheckpoint::LoaderId::HIDDEN; + } + ClassLoaderData* cld = (ik != nullptr) ? ik->class_loader_data() : nullptr; if (cld == nullptr || cld->is_boot_class_loader_data()) return ProfileCheckpoint::LoaderId::BOOT; if (cld->is_platform_class_loader_data()) return ProfileCheckpoint::LoaderId::PLATFORM; if (cld->is_system_class_loader_data()) return ProfileCheckpoint::LoaderId::SYSTEM; + log_debug(compilation)("Non-builtin loader encountered: %s", cld->loader_name_and_id()); return ProfileCheckpoint::LoaderId::UNDEFINED; } +static const char* get_klassname_utf8(InstanceKlass* ik) { + if (ik == nullptr) { + return ""; + } + if (ik->is_hidden()) { + if (const char* loc = DynoLocatorScan::lookup(ik)) { + log_debug(compilation)("found locator for hidden ik %s: %s", ik->name()->as_utf8(), loc); + return loc; + } + log_debug(compilation)("hidden ik encountered without locator: %s", ik->name()->as_utf8()); + } + return ik->name()->as_utf8(); +} + static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, TRAPS) { switch (loader_id) { case ProfileCheckpoint::LoaderId::BOOT: return Handle(); @@ -187,11 +207,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -204,11 +225,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -218,11 +240,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -235,11 +258,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -249,11 +273,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(pd->dp() + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -266,11 +291,12 @@ static void collect_type_fixups(MethodData* mdo, intptr_t* cell = (intptr_t*)(p_dp + off_b); Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { - const char* cname = InstanceKlass::cast(k)->name()->as_utf8(); + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); ProfileCheckpoint::Fixup fx; fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_loader(k->class_loader_data()); + fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); } } @@ -742,7 +768,7 @@ static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { // Caller guarantees mdo != nullptr MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); InstanceKlass* holder = m->method_holder(); - r.kname = holder->name()->as_utf8(); + r.kname = get_klassname_utf8(holder); r.mname = m->name()->as_utf8(); r.sig = m->signature()->as_utf8(); r.mdo_size = (u4)mdo->size_in_bytes(); @@ -779,8 +805,8 @@ class CollectClassesClosure : public KlassClosure { if (k == nullptr || !k->is_instance_klass()) return; InstanceKlass* ik = InstanceKlass::cast(k); ProfileCheckpoint::Class c; - c.loader = loader_id_from_loader(ik->class_loader_data()); - c.klass.id = _stb->intern(ik->name()->as_utf8()); + c.loader = loader_id_from_klass(ik); + c.klass.id = _stb->intern(get_klassname_utf8(ik)); _classes->append(c); } }; @@ -961,7 +987,7 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { MethodData* mdo = m->method_data(); Record rec; - rec.key.loader = loader_id_from_loader(m->method_holder()->class_loader_data()); + rec.key.loader = loader_id_from_klass(m->method_holder()); rec.key.klass.id = stb.id_of(rn.kname); rec.key.name.id = stb.id_of(rn.mname); rec.key.sig.id = stb.id_of(rn.sig); @@ -1024,6 +1050,10 @@ bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { } void ProfileCheckpoint::dump_to_stream(fileStream* out) { + // Opportunistically scan resolved indy/invokehandle sites to harvest locators + // for hidden classes before dumping. + DynoLocatorScan::scan_all_classes(); + if (!Loader::dump_to_stream(out)) { log_warning(compilation)("MDO checkpoint: dump failed"); } diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 5c51e9519c2..4b13ffa4dbc 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -29,7 +29,7 @@ class ProfileCheckpoint { // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] - enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED }; + enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED, HIDDEN }; struct SymbolId { uint32_t id; }; @@ -183,6 +183,7 @@ class ProfileCheckpoint { static void load(class JavaThread* THREAD); static void dump_to_stream(class fileStream* out); static void wait_for_compile_completion(class JavaThread* THREAD); + static void scan_hidden_class_locators(); }; #endif // SHARE_SERVICES_PROFILECHECKPOINT_HPP From c9e7f0d1b9d79dc1b5c9dbf81b8621727b9fd62b Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 4 Dec 2025 00:02:19 -0800 Subject: [PATCH 14/23] dynolocator updates --- .../share/services/dynoLocatorScan.cpp | 245 +++++++++++++++++- .../share/services/dynoLocatorScan.hpp | 4 + .../share/services/profileCheckpoint.cpp | 16 +- 3 files changed, 263 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/services/dynoLocatorScan.cpp b/src/hotspot/share/services/dynoLocatorScan.cpp index 6203fb246e7..37b1db468f0 100644 --- a/src/hotspot/share/services/dynoLocatorScan.cpp +++ b/src/hotspot/share/services/dynoLocatorScan.cpp @@ -2,6 +2,7 @@ #include "classfile/javaClasses.hpp" #include "classfile/vmClasses.hpp" +#include "classfile/symbolTable.hpp" #include "ci/ciReplay.hpp" #include "interpreter/bytecodeStream.hpp" #include "memory/resourceArea.hpp" @@ -21,6 +22,9 @@ #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" #include "runtime/threadSMR.hpp" +#include "interpreter/linkResolver.hpp" +#include "ci/ciReplay.hpp" +#include "oops/objArrayOop.inline.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" #include @@ -74,6 +78,170 @@ class DynoLocatorTable { GrowableArray* DynoLocatorTable::_entries = nullptr; Mutex* DynoLocatorTable::_lock = nullptr; +// --- Hidden locator parser (based on ciReplay) --- +class HiddenLocatorParser { + JavaThread* _jt; + char* _buf; + char* _p; + + void skip_ws() { + while (*_p == ' ' || *_p == '\t') _p++; + } + + char* next_token() { + skip_ws(); + if (*_p == '\0') return nullptr; + char* tok = _p; + while (*_p != ' ' && *_p != '\t' && *_p != '\0' && *_p != ';') _p++; + if (*_p != '\0') { *_p = '\0'; _p++; } + return tok; + } + + int parse_int(bool* ok) { + skip_ws(); + char* endp = nullptr; + long v = strtol(_p, &endp, 10); + if (endp == _p) { if (ok) *ok = false; return 0; } + _p = endp; + if (ok) *ok = true; + return (int)v; + } + + InstanceKlass* parse_bci() { + char* klass = next_token(); + char* mname = next_token(); + char* msig = next_token(); + bool ok = true; + int bci = parse_int(&ok); + if (!ok || klass == nullptr || mname == nullptr || msig == nullptr) return nullptr; + Symbol* ksym = SymbolTable::new_symbol(klass); + Symbol* mnsym = SymbolTable::new_symbol(mname); + Symbol* mssym = SymbolTable::new_symbol(msig); + Handle loader(_jt, SystemDictionary::java_system_loader()); + InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::resolve_or_fail(ksym, loader, true, _jt)); + if (_jt->has_pending_exception()) { _jt->clear_pending_exception(); return nullptr; } + ik->link_class(_jt); + if (_jt->has_pending_exception()) { _jt->clear_pending_exception(); return nullptr; } + Method* m = ik->find_method(mnsym, mssym); + if (m == nullptr) return nullptr; + methodHandle caller(_jt, m); + Bytecode_invoke bytecode = Bytecode_invoke_check(caller, bci); + if (!Bytecodes::is_defined(bytecode.code()) || !bytecode.is_valid()) return nullptr; + int index = bytecode.index(); + const constantPoolHandle cp(_jt, ik->constants()); + CallInfo callInfo; + Bytecodes::Code bc = bytecode.invoke_code(); + LinkResolver::resolve_invoke(callInfo, Handle(), cp, index, bc, _jt); + if (_jt->has_pending_exception()) { _jt->clear_pending_exception(); return nullptr; } + oop appendix = nullptr; + Method* adapter_method = nullptr; + int pool_index = 0; + if (bytecode.is_invokedynamic()) { + cp->cache()->set_dynamic_call(callInfo, index); + appendix = cp->resolved_reference_from_indy(index); + adapter_method = cp->resolved_indy_entry_at(index)->method(); + pool_index = cp->resolved_indy_entry_at(index)->constant_pool_index(); + } else if (bytecode.is_invokehandle()) { + ResolvedMethodEntry* method_entry = cp->cache()->set_method_handle(index, callInfo); + appendix = cp->cache()->appendix_if_resolved(method_entry); + adapter_method = method_entry->method(); + pool_index = method_entry->constant_pool_index(); + } else { + return nullptr; + } + char* dyno_ref = next_token(); + if (dyno_ref == nullptr) return nullptr; + oop obj = nullptr; + if (strcmp(dyno_ref, "") == 0) { + obj = appendix; + } else if (strcmp(dyno_ref, "") == 0) { + char* term = next_token(); + if (term != nullptr && strcmp(term, ";") == 0 && adapter_method != nullptr) { + return adapter_method->method_holder(); + } + return nullptr; + } else if (strcmp(dyno_ref, "") == 0) { + BootstrapInfo bs(cp, pool_index, index); + obj = cp->resolve_possibly_cached_constant_at(bs.bsm_index(), _jt); + } else { + return nullptr; + } + char* field = next_token(); + while (field != nullptr && strcmp(field, ";") != 0) { + if (strcmp(field, "") == 0) { + Method* vmtarget = java_lang_invoke_MemberName::vmtarget(obj); + return (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + } + obj = ciReplay::obj_field(obj, field); + if (obj != nullptr && obj->is_objArray()) { + bool idx_ok = true; + int index = parse_int(&idx_ok); + if (!idx_ok || index >= ((objArrayOop)obj)->length()) return nullptr; + obj = ((objArrayOop)obj)->obj_at(index); + } + field = next_token(); + } + return (obj != nullptr && obj->klass()->is_instance_klass()) ? InstanceKlass::cast(obj->klass()) : nullptr; + } + + InstanceKlass* parse_cpi() { + char* klass = next_token(); + bool ok = true; + int cpi = parse_int(&ok); + if (!ok || klass == nullptr) return nullptr; + Symbol* ksym = SymbolTable::new_symbol(klass); + Handle loader(_jt, SystemDictionary::java_system_loader()); + InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::resolve_or_fail(ksym, loader, true, _jt)); + if (_jt->has_pending_exception()) { _jt->clear_pending_exception(); return nullptr; } + ik->link_class(_jt); + if (_jt->has_pending_exception()) { _jt->clear_pending_exception(); return nullptr; } + const constantPoolHandle cp(_jt, ik->constants()); + if (cpi >= cp->length() || !cp->tag_at(cpi).is_method_handle()) return nullptr; + oop obj = cp->resolve_possibly_cached_constant_at(cpi, _jt); + if (obj == nullptr) return nullptr; + skip_ws(); + char* field = next_token(); + while (field != nullptr && strcmp(field, ";") != 0) { + if (strcmp(field, "") == 0) { + Method* vmtarget = java_lang_invoke_MemberName::vmtarget(obj); + return (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + } + obj = ciReplay::obj_field(obj, field); + if (obj != nullptr && obj->is_objArray()) { + bool idx_ok = true; + int index = parse_int(&idx_ok); + if (!idx_ok || index >= ((objArrayOop)obj)->length()) return nullptr; + obj = ((objArrayOop)obj)->obj_at(index); + } + field = next_token(); + } + return (obj != nullptr && obj->klass()->is_instance_klass()) ? InstanceKlass::cast(obj->klass()) : nullptr; + } + +public: + HiddenLocatorParser(const char* loc, JavaThread* jt) : _jt(jt) { + size_t len = strlen(loc); + _buf = NEW_RESOURCE_ARRAY(char, len + 1); + strncpy(_buf, loc, len + 1); + _p = _buf; + } + + InstanceKlass* parse() { + skip_ws(); + if (*_p != '@') return nullptr; + _p++; + char* kind = next_token(); + if (kind == nullptr) return nullptr; + if (strcmp(kind, "bci") == 0) { + return parse_bci(); + } + if (strcmp(kind, "cpi") == 0) { + return parse_cpi(); + } + return nullptr; + } +}; + class RecordLocation { char* _start; char* _end; @@ -104,6 +272,12 @@ static void record_hidden(InstanceKlass* ik, const char* loc) { } static void record_call_site_obj(JavaThread* jt, oop obj, char* loc_buf); +static void record_mh(JavaThread* jt, oop mh, char* loc_buf); + +// Read an object field by name. +static inline oop obj_field(oop obj, const char* name) { + return ciReplay::obj_field(obj, name); +} static void record_member(JavaThread* jt, oop member, char* loc_buf) { assert(java_lang_invoke_MemberName::is_instance(member), "!"); @@ -121,8 +295,54 @@ static void record_member(JavaThread* jt, oop member, char* loc_buf) { } } +static void record_lambdaform(JavaThread* jt, oop form, char* loc_buf) { + assert(java_lang_invoke_LambdaForm::is_instance(form), "!"); + + { + oop member = java_lang_invoke_LambdaForm::vmentry(form); + RecordLocation rl(loc_buf, " vmentry"); + record_member(jt, member, loc_buf); + } + + objArrayOop names = (objArrayOop)obj_field(form, "names"); + if (names != nullptr) { + RecordLocation lp0(loc_buf, " names"); + int len = names->length(); + for (int i = 0; i < len; ++i) { + oop name = names->obj_at(i); + RecordLocation lp1(loc_buf, " %d", i); + RecordLocation lp2(loc_buf, " function"); + oop function = obj_field(name, "function"); + if (function != nullptr) { + oop member = obj_field(function, "member"); + if (member != nullptr) { + RecordLocation lp3(loc_buf, " member"); + record_member(jt, member, loc_buf); + } + oop mh = obj_field(function, "resolvedHandle"); + if (mh != nullptr) { + RecordLocation lp3(loc_buf, " resolvedHandle"); + record_mh(jt, mh, loc_buf); // will recurse + } + oop invoker = obj_field(function, "invoker"); + if (invoker != nullptr) { + RecordLocation lp3(loc_buf, " invoker"); + record_mh(jt, invoker, loc_buf); + } + } + } + } +} + static void record_mh(JavaThread* jt, oop mh, char* loc_buf) { assert(java_lang_invoke_MethodHandle::is_instance(mh), "!"); + // MethodHandle.form + { + oop form = java_lang_invoke_MethodHandle::form(mh); + RecordLocation rl(loc_buf, " form"); + record_lambdaform(jt, form, loc_buf); + } + if (java_lang_invoke_DirectMethodHandle::is_instance(mh)) { oop member = java_lang_invoke_DirectMethodHandle::member(mh); RecordLocation rl(loc_buf, " member"); @@ -135,7 +355,7 @@ static void record_mh(JavaThread* jt, oop mh, char* loc_buf) { const int max_arg = 99; for (int index = 0; index <= max_arg; ++index) { jio_snprintf(arg_name, sizeof(arg_name), " argL%d", index); - oop arg = ciReplay::obj_field(mh, arg_name + 1); // reuse helper to read field + oop arg = obj_field(mh, arg_name + 1); if (arg != nullptr) { RecordLocation rl(loc_buf, "%s", arg_name); if (arg->klass()->is_instance_klass()) { @@ -272,9 +492,32 @@ void DynoLocatorScan::scan_all_classes() { } } } + + // Scan constant pool MethodHandle entries (@cpi) + { + RecordLocation rp(loc_buf, "@cpi %s", ik->name()->as_quoted_ascii()); + int len = cp->length(); + for (int i = 0; i < len; ++i) { + if (cp->tag_at(i).is_method_handle()) { + bool found_it; + oop mh = cp->find_cached_constant_at(i, found_it, jt); + if (mh != nullptr) { + RecordLocation rl(loc_buf, " %d", i); + record_mh(jt, mh, loc_buf); + } + } + } + } } } const char* DynoLocatorScan::lookup(InstanceKlass* ik) { return DynoLocatorTable::lookup(ik); } + +InstanceKlass* DynoLocatorScan::resolve_locator(const char* loc, JavaThread* jt) { + if (loc == nullptr || jt == nullptr) return nullptr; + ResourceMark rm(jt); + HiddenLocatorParser p(loc, jt); + return p.parse(); +} diff --git a/src/hotspot/share/services/dynoLocatorScan.hpp b/src/hotspot/share/services/dynoLocatorScan.hpp index bfbca01aca9..6163f9b5b73 100644 --- a/src/hotspot/share/services/dynoLocatorScan.hpp +++ b/src/hotspot/share/services/dynoLocatorScan.hpp @@ -12,6 +12,10 @@ class DynoLocatorScan { // Lookup a recorded locator for a hidden class scanned previously. static const char* lookup(InstanceKlass* ik); + + // Parse a dyno locator string (ciReplay format) and resolve the hidden class. + // Returns nullptr if resolution fails. + static InstanceKlass* resolve_locator(const char* loc, JavaThread* jt); }; #endif // SHARE_SERVICES_DYNOLOCATORSCAN_HPP diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 10a51de07b6..a64459a4e16 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -10,6 +10,7 @@ #include "oops/method.hpp" #include "oops/methodCounters.hpp" #include "oops/klass.inline.hpp" +#include "oops/objArrayOop.inline.hpp" #include "runtime/handles.inline.hpp" #include "logging/log.hpp" #include "utilities/copy.hpp" @@ -20,6 +21,12 @@ #include "compiler/compileBroker.hpp" #include "compiler/compilerDefinitions.hpp" #include "memory/resourceArea.hpp" +#include "interpreter/linkResolver.hpp" +#include "oops/constantPool.inline.hpp" +#include "oops/cpCache.inline.hpp" +#include "oops/resolvedIndyEntry.hpp" +#include "prims/methodHandles.hpp" +#include "ci/ciReplay.hpp" #include "services/dynoLocatorScan.hpp" #include #include @@ -130,7 +137,14 @@ static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, T } } + + static InstanceKlass* resolve_klass_utf8(const char* name, ProfileCheckpoint::LoaderId loader_id, TRAPS) { + if (name != nullptr && name[0] == '@') { + log_debug(compilation)("resolving %s", name); + InstanceKlass* hk = DynoLocatorScan::resolve_locator(name, THREAD); + if (hk != nullptr) return hk; + } Symbol* sym = SymbolTable::new_symbol(name); Handle loader = loader_handle_from_loader(loader_id, THREAD); Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); @@ -1057,4 +1071,4 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { if (!Loader::dump_to_stream(out)) { log_warning(compilation)("MDO checkpoint: dump failed"); } -} +} \ No newline at end of file From 8f8fa72f4b191d81e3907a2008a02f573edcaf29 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Thu, 4 Dec 2025 01:15:28 -0800 Subject: [PATCH 15/23] dynoLocatorScan updaters --- .../share/services/dynoLocatorScan.cpp | 191 +++++++++++++++--- 1 file changed, 160 insertions(+), 31 deletions(-) diff --git a/src/hotspot/share/services/dynoLocatorScan.cpp b/src/hotspot/share/services/dynoLocatorScan.cpp index 37b1db468f0..712e45b69c7 100644 --- a/src/hotspot/share/services/dynoLocatorScan.cpp +++ b/src/hotspot/share/services/dynoLocatorScan.cpp @@ -25,9 +25,12 @@ #include "interpreter/linkResolver.hpp" #include "ci/ciReplay.hpp" #include "oops/objArrayOop.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/utf8.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" #include +#include #include namespace { @@ -84,19 +87,96 @@ class HiddenLocatorParser { char* _buf; char* _p; + static void unescape_string(char* value) { + char* from = value; + char* to = value; + while (*from != '\0') { + if (*from != '\\') { + *to++ = *from++; + } else { + switch (from[1]) { + case 'u': { + from += 2; + jchar v = 0; + for (int i = 0; i < 4; i++) { + char c = *from++; + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + v = (v << 4) + c - '0'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + v = (v << 4) + 10 + c - 'a'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + v = (v << 4) + 10 + c - 'A'; + break; + default: + ShouldNotReachHere(); + } + } + UNICODE::convert_to_utf8(&v, 1, to); + to++; + break; + } + case 't': *to++ = '\t'; from += 2; break; + case 'n': *to++ = '\n'; from += 2; break; + case 'r': *to++ = '\r'; from += 2; break; + case 'f': *to++ = '\f'; from += 2; break; + default: + ShouldNotReachHere(); + } + } + } + *from = *to; + } + void skip_ws() { while (*_p == ' ' || *_p == '\t') _p++; } - char* next_token() { + char* parse_token() { skip_ws(); if (*_p == '\0') return nullptr; - char* tok = _p; - while (*_p != ' ' && *_p != '\t' && *_p != '\0' && *_p != ';') _p++; - if (*_p != '\0') { *_p = '\0'; _p++; } + if (*_p == ';') { + _p++; + return (char*)";"; + } + char* tok = nullptr; + if (*_p == '"') { + _p++; + tok = _p; + while (*_p != '\0' && *_p != '"') { + if (*_p == '\\' && _p[1] != '\0') { + _p += 2; // skip escaped char + } else { + _p++; + } + } + if (*_p == '"') { + *_p = '\0'; + _p++; + } + } else { + tok = _p; + while (*_p != ' ' && *_p != '\t' && *_p != '\0' && *_p != ';') _p++; + if (*_p != '\0') { *_p = '\0'; _p++; } + } + if (tok != nullptr && !(tok[0] == ';' && tok[1] == '\0')) { + unescape_string(tok); + } return tok; } + bool parse_terminator() { + skip_ws(); + if (*_p == ';') { + _p++; + return true; + } + return false; + } + int parse_int(bool* ok) { skip_ws(); char* endp = nullptr; @@ -108,9 +188,9 @@ class HiddenLocatorParser { } InstanceKlass* parse_bci() { - char* klass = next_token(); - char* mname = next_token(); - char* msig = next_token(); + char* klass = parse_token(); + char* mname = parse_token(); + char* msig = parse_token(); bool ok = true; int bci = parse_int(&ok); if (!ok || klass == nullptr || mname == nullptr || msig == nullptr) return nullptr; @@ -125,8 +205,12 @@ class HiddenLocatorParser { Method* m = ik->find_method(mnsym, mssym); if (m == nullptr) return nullptr; methodHandle caller(_jt, m); + if (m->validate_bci(bci) != bci) { + return nullptr; + } Bytecode_invoke bytecode = Bytecode_invoke_check(caller, bci); if (!Bytecodes::is_defined(bytecode.code()) || !bytecode.is_valid()) return nullptr; + bytecode.verify(); int index = bytecode.index(); const constantPoolHandle cp(_jt, ik->constants()); CallInfo callInfo; @@ -149,43 +233,54 @@ class HiddenLocatorParser { } else { return nullptr; } - char* dyno_ref = next_token(); + char* dyno_ref = parse_token(); if (dyno_ref == nullptr) return nullptr; oop obj = nullptr; if (strcmp(dyno_ref, "") == 0) { obj = appendix; } else if (strcmp(dyno_ref, "") == 0) { - char* term = next_token(); - if (term != nullptr && strcmp(term, ";") == 0 && adapter_method != nullptr) { - return adapter_method->method_holder(); - } - return nullptr; + if (!parse_terminator()) return nullptr; + return (adapter_method != nullptr) ? adapter_method->method_holder() : nullptr; } else if (strcmp(dyno_ref, "") == 0) { BootstrapInfo bs(cp, pool_index, index); obj = cp->resolve_possibly_cached_constant_at(bs.bsm_index(), _jt); } else { return nullptr; } - char* field = next_token(); + if (obj == nullptr) { + return nullptr; + } + char* field = parse_token(); while (field != nullptr && strcmp(field, ";") != 0) { if (strcmp(field, "") == 0) { Method* vmtarget = java_lang_invoke_MemberName::vmtarget(obj); - return (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + InstanceKlass* res = (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + if (!parse_terminator()) return nullptr; + return res; } obj = ciReplay::obj_field(obj, field); - if (obj != nullptr && obj->is_objArray()) { + if (obj == nullptr) { + return nullptr; + } + if (obj->is_objArray()) { bool idx_ok = true; int index = parse_int(&idx_ok); if (!idx_ok || index >= ((objArrayOop)obj)->length()) return nullptr; obj = ((objArrayOop)obj)->obj_at(index); } - field = next_token(); + field = parse_token(); + } + if (field == nullptr) { + if (!parse_terminator()) return nullptr; } - return (obj != nullptr && obj->klass()->is_instance_klass()) ? InstanceKlass::cast(obj->klass()) : nullptr; + if (obj == nullptr) { + return nullptr; + } + return obj->klass()->is_instance_klass() ? InstanceKlass::cast(obj->klass()) : nullptr; } InstanceKlass* parse_cpi() { - char* klass = next_token(); + char* klass = parse_token(); bool ok = true; int cpi = parse_int(&ok); if (!ok || klass == nullptr) return nullptr; @@ -200,22 +295,33 @@ class HiddenLocatorParser { oop obj = cp->resolve_possibly_cached_constant_at(cpi, _jt); if (obj == nullptr) return nullptr; skip_ws(); - char* field = next_token(); + char* field = parse_token(); while (field != nullptr && strcmp(field, ";") != 0) { if (strcmp(field, "") == 0) { Method* vmtarget = java_lang_invoke_MemberName::vmtarget(obj); - return (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + InstanceKlass* res = (vmtarget != nullptr) ? vmtarget->method_holder() : nullptr; + if (!parse_terminator()) return nullptr; + return res; } obj = ciReplay::obj_field(obj, field); - if (obj != nullptr && obj->is_objArray()) { + if (obj == nullptr) { + return nullptr; + } + if (obj->is_objArray()) { bool idx_ok = true; int index = parse_int(&idx_ok); if (!idx_ok || index >= ((objArrayOop)obj)->length()) return nullptr; obj = ((objArrayOop)obj)->obj_at(index); } - field = next_token(); + field = parse_token(); + } + if (field == nullptr) { + if (!parse_terminator()) return nullptr; + } + if (obj == nullptr) { + return nullptr; } - return (obj != nullptr && obj->klass()->is_instance_klass()) ? InstanceKlass::cast(obj->klass()) : nullptr; + return obj->klass()->is_instance_klass() ? InstanceKlass::cast(obj->klass()) : nullptr; } public: @@ -230,15 +336,18 @@ class HiddenLocatorParser { skip_ws(); if (*_p != '@') return nullptr; _p++; - char* kind = next_token(); + char* kind = parse_token(); if (kind == nullptr) return nullptr; + InstanceKlass* ik = nullptr; if (strcmp(kind, "bci") == 0) { - return parse_bci(); + ik = parse_bci(); + } else if (strcmp(kind, "cpi") == 0) { + ik = parse_cpi(); } - if (strcmp(kind, "cpi") == 0) { - return parse_cpi(); + if (ik != nullptr && !ik->is_hidden()) { + return nullptr; } - return nullptr; + return ik; } }; @@ -266,9 +375,30 @@ class RecordLocation { }; static void record_hidden(InstanceKlass* ik, const char* loc) { - if (ik != nullptr && ik->is_hidden() && loc != nullptr) { + if (ik == nullptr || !ik->is_hidden() || loc == nullptr) { + return; + } + // Mirror ciEnv::dyno_name: recorded locators must end with a terminator. + bool has_term = false; + size_t len = strlen(loc); + for (size_t i = len; i > 0; i--) { + char ch = loc[i - 1]; + if (!isspace(ch)) { + has_term = (ch == ';'); + break; + } + } + if (has_term) { DynoLocatorTable::record(ik, loc); + return; + } + const size_t needed = len + 3; // " ;" + '\0' + if (needed > LOC_BUF_LEN) { + return; // avoid overflow; nothing recorded. } + char term_buf[LOC_BUF_LEN]; + jio_snprintf(term_buf, sizeof(term_buf), "%s ;", loc); + DynoLocatorTable::record(ik, term_buf); } static void record_call_site_obj(JavaThread* jt, oop obj, char* loc_buf); @@ -381,7 +511,6 @@ static void record_call_site_obj(JavaThread* jt, oop obj, char* loc_buf) { RecordLocation rl(loc_buf, " target"); InstanceKlass* ik = InstanceKlass::cast(target->klass()); record_hidden(ik, loc_buf); - record_call_site_obj(jt, target, loc_buf); } } } From 2280cedb5b38d6edbe40a11dbff0000e5e1312f0 Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Fri, 2 Jan 2026 16:18:54 -0800 Subject: [PATCH 16/23] refactor + write/read loader id + vm flags verification --- .../share/services/profileCheckpoint.cpp | 997 ++++++++++-------- .../share/services/profileCheckpoint.hpp | 44 +- 2 files changed, 540 insertions(+), 501 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index a64459a4e16..931cdc874bc 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -31,16 +31,234 @@ #include #include -// ---- helpers (extracted for readability) ---- -static bool copy_mdo_payload(MethodData* dst_mdo, const char* src_bytes, u4 src_size) { - if (dst_mdo == nullptr) return false; - if ((u4)dst_mdo->size_in_bytes() != src_size) return false; - address dst_base = dst_mdo->data_base(); - const size_t data_sz = (size_t)dst_mdo->data_size(); - const size_t data_off = (size_t)(dst_base - (address)dst_mdo); - const char* src = src_bytes + data_off; - Copy::conjoint_jbytes(src, (char*)dst_base, (jlong)data_sz); +// ============================================================================ +// Binary format + I/O helpers (MDOX) + debug printing +// ============================================================================ +static u2 detect_endianness() { + union { u4 v; u1 b[4]; } u; u.v = 1; + return (u.b[0] == 1) ? (u2)0 : (u2)1; +} + +static bool write_exact(fileStream* out, const void* p, size_t n) { + if (n == 0) return true; + out->write((const char*)p, n); + return true; +} + +static bool read_exact(FILE* in, void* p, size_t n) { + return ::fread(p, 1, n, in) == n; +} + +static bool write_u4(fileStream* out, u4 v) { + return write_exact(out, &v, sizeof(v)); +} + +static bool write_u1(fileStream* out, u1 v) { + return write_exact(out, &v, sizeof(v)); +} + +static bool read_u4(FILE* in, u4& v) { + return read_exact(in, &v, sizeof(v)); +} + +static bool read_u1(FILE* in, u1& v) { + return read_exact(in, &v, sizeof(v)); +} + +static char* read_str(FILE* in) { + u4 len = 0; + if (!read_u4(in, len)) return nullptr; + char* buf = (char*)os::malloc(len + 1, mtInternal); + if (buf == nullptr) return nullptr; + if (len > 0 && !read_exact(in, buf, len)) { os::free(buf); return nullptr; } + buf[len] = '\0'; + return buf; +} + +static bool write_u2(fileStream* out, u2 v) { return write_exact(out, &v, sizeof(v)); } +static bool read_u2(FILE* in, u2& v) { return read_exact(in, &v, sizeof(v)); } + +static bool write_header(fileStream* out, const ProfileCheckpoint::Header& h) { + if (!write_exact(out, h.magic, sizeof(h.magic))) return false; + if (!write_u2(out, h.pointer_size)) return false; + if (!write_u2(out, h.endianness)) return false; + if (!write_exact(out, &h.layout, sizeof(h.layout))) return false; + if (!write_u4(out, h.sym_count)) return false; + if (!write_u4(out, h.rec_count)) return false; + if (!write_u4(out, h.class_count)) return false; + return true; +} + +static bool read_header(FILE* in, ProfileCheckpoint::Header& h) { + if (!read_exact(in, h.magic, sizeof(h.magic))) return false; + if (h.magic[0] != 'M' || h.magic[1] != 'D' || h.magic[2] != 'O' || h.magic[3] != 'X') return false; + if (!read_u2(in, h.pointer_size)) return false; + if (!read_u2(in, h.endianness)) return false; + if (!read_exact(in, &h.layout, sizeof(h.layout))) return false; + if (!read_u4(in, h.sym_count)) return false; + if (!read_u4(in, h.rec_count)) return false; + if (!read_u4(in, h.class_count)) return false; + return true; +} + +static void init_header(ProfileCheckpoint::Header& h, u4 sym_count, u4 rec_count, u4 class_count) { + h.magic[0] = 'M'; h.magic[1] = 'D'; h.magic[2] = 'O'; h.magic[3] = 'X'; + h.pointer_size = (u2)sizeof(void*); + h.endianness = detect_endianness(); + h.layout.type_profile_level = (uint32_t)TypeProfileLevel; + h.layout.type_profile_args_limit = (int32_t)TypeProfileArgsLimit; + h.layout.type_profile_parms_limit = (int32_t)TypeProfileParmsLimit; + h.layout.type_profile_width = (int64_t)TypeProfileWidth; + h.layout.profile_traps = (uint8_t)(ProfileTraps ? 1 : 0); + h.layout.type_profile_casts = (uint8_t)(TypeProfileCasts ? 1 : 0); + h.layout.spec_trap_limit_extra_entries = (int32_t)SpecTrapLimitExtraEntries; + h.sym_count = sym_count; + h.rec_count = rec_count; + h.class_count = class_count; +} + +static bool write_symtab(fileStream* out, const GrowableArray& symbols) { + for (int i = 0; i < symbols.length(); i++) { + const char* s = symbols.at(i); + u4 len = (u4)strlen(s); + if (!write_u4(out, len)) return false; + if (!write_exact(out, s, len)) return false; + } + return true; +} + +static bool read_symtab(FILE* in, GrowableArray& symbols, u4 expected) { + for (u4 si = 0; si < expected; si++) { + char* s = read_str(in); + if (s == nullptr) { + return false; + } + symbols.append(s); + } + return true; +} + +static bool write_classes(fileStream* out, const GrowableArray& classes) { + for (int i = 0; i < classes.length(); i++) { + const ProfileCheckpoint::Class& c = classes.at(i); + if (!write_u1(out, (u1)c.loader)) return false; + if (!write_u4(out, c.klass.id)) return false; + } + return true; +} + +static bool read_classes(FILE* in, GrowableArray& classes, u4 expected) { + for (u4 ci = 0; ci < expected; ci++) { + u1 loader_raw = 0; + if (!read_u1(in, loader_raw)) { + return false; + } + u4 klass_id = 0; + if (!read_u4(in, klass_id)) { + return false; + } + ProfileCheckpoint::Class c; + c.loader = (ProfileCheckpoint::LoaderId)loader_raw; + c.klass.id = klass_id; + classes.append(c); + } + return true; +} + +static bool write_record(fileStream* out, const ProfileCheckpoint::Record& r, const void* mdo_bytes, const ProfileCheckpoint::Fixup* fixups, const void* mc_bytes, const void* header_bytes) { + if (!write_u4(out, r.key.klass.id)) return false; + if (!write_u4(out, r.key.name.id)) return false; + if (!write_u4(out, r.key.sig.id)) return false; + u1 loader = (u1)r.key.loader; + if (!write_exact(out, &loader, sizeof(loader))) return false; + if (!write_exact(out, &r.comp_level, sizeof(r.comp_level))) return false; + if (!write_u4(out, r.mdo_size)) return false; + if (!write_u4(out, r.fixup_count)) return false; + for (u4 i = 0; i < r.fixup_count; i++) { + if (!write_u4(out, fixups[i].offset_in_mdo)) return false; + if (!write_u4(out, fixups[i].target.id)) return false; + u1 fx_loader = (u1)fixups[i].loader; + if (!write_u1(out, fx_loader)) return false; + } + if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; + if (!write_u4(out, r.header_size)) return false; + if (r.header_size > 0) { + if (!write_exact(out, header_bytes, r.header_size)) return false; + } + if (!write_u4(out, r.mc_size)) return false; + if (r.mc_size > 0) { + if (!write_exact(out, mc_bytes, r.mc_size)) return false; + } + return true; +} + +static bool read_record(FILE* in, ProfileCheckpoint::Record& r, ProfileCheckpoint::Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) { + if (!read_u4(in, r.key.klass.id)) return false; + if (!read_u4(in, r.key.name.id)) return false; + if (!read_u4(in, r.key.sig.id)) return false; + u1 loader = 0; + if (!read_exact(in, &loader, sizeof(loader))) return false; + r.key.loader = (ProfileCheckpoint::LoaderId)loader; + if (!read_exact(in, &r.comp_level, sizeof(r.comp_level))) return false; + if (!read_u4(in, r.mdo_size)) return false; + if (!read_u4(in, r.fixup_count)) return false; + fixups = nullptr; + if (r.fixup_count > 0) { + fixups = (ProfileCheckpoint::Fixup*)os::malloc(sizeof(ProfileCheckpoint::Fixup) * r.fixup_count, mtInternal); + if (fixups == nullptr) return false; + for (u4 i = 0; i < r.fixup_count; i++) { + if (!read_u4(in, fixups[i].offset_in_mdo)) { os::free(fixups); return false; } + if (!read_u4(in, fixups[i].target.id)) { os::free(fixups); return false; } + u1 fx_loader = 0; + if (!read_u1(in, fx_loader)) { os::free(fixups); return false; } + fixups[i].loader = (ProfileCheckpoint::LoaderId)fx_loader; + } + } + mdo_bytes = nullptr; + if (r.mdo_size > 0) { + mdo_bytes = (char*)os::malloc(r.mdo_size, mtInternal); + if (mdo_bytes == nullptr) { if (fixups) os::free(fixups); return false; } + if (!read_exact(in, mdo_bytes, r.mdo_size)) { os::free(mdo_bytes); if (fixups) os::free(fixups); return false; } + } + header_bytes = nullptr; + if (!read_u4(in, r.header_size)) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + if (r.header_size > 0) { + header_bytes = (char*)os::malloc(r.header_size, mtInternal); + if (header_bytes == nullptr) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + if (!read_exact(in, header_bytes, r.header_size)) { + os::free(header_bytes); + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + return false; + } + } + if (!read_u4(in, r.mc_size)) return false; + mc_bytes = nullptr; + if (r.mc_size > 0) { + mc_bytes = (char*)os::malloc(r.mc_size, mtInternal); + if (mc_bytes == nullptr) { + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (header_bytes) os::free(header_bytes); + return false; + } + if (!read_exact(in, mc_bytes, r.mc_size)) { + os::free(mc_bytes); + if (fixups) os::free(fixups); + if (mdo_bytes) os::free(mdo_bytes); + if (header_bytes) os::free(header_bytes); + return false; + } + } return true; } @@ -102,6 +320,9 @@ static void print_mdo_header(MethodData* mdo, outputStream* st = tty) { st->cr(); } +// ============================================================================ +// Symbolic resolution helpers (LoaderId / klass / method) +// ============================================================================ static ProfileCheckpoint::LoaderId loader_id_from_klass(InstanceKlass* ik) { if (ik != nullptr && ik->is_hidden()) { return ProfileCheckpoint::LoaderId::HIDDEN; @@ -114,6 +335,15 @@ static ProfileCheckpoint::LoaderId loader_id_from_klass(InstanceKlass* ik) { return ProfileCheckpoint::LoaderId::UNDEFINED; } +static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, TRAPS) { + switch (loader_id) { + case ProfileCheckpoint::LoaderId::BOOT: return Handle(); + case ProfileCheckpoint::LoaderId::PLATFORM: return Handle(THREAD, SystemDictionary::java_platform_loader()); + case ProfileCheckpoint::LoaderId::SYSTEM: return Handle(THREAD, SystemDictionary::java_system_loader()); + default: return Handle(); + } +} + static const char* get_klassname_utf8(InstanceKlass* ik) { if (ik == nullptr) { return ""; @@ -128,17 +358,6 @@ static const char* get_klassname_utf8(InstanceKlass* ik) { return ik->name()->as_utf8(); } -static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, TRAPS) { - switch (loader_id) { - case ProfileCheckpoint::LoaderId::BOOT: return Handle(); - case ProfileCheckpoint::LoaderId::PLATFORM: return Handle(THREAD, SystemDictionary::java_platform_loader()); - case ProfileCheckpoint::LoaderId::SYSTEM: return Handle(THREAD, SystemDictionary::java_system_loader()); - default: return Handle(); - } -} - - - static InstanceKlass* resolve_klass_utf8(const char* name, ProfileCheckpoint::LoaderId loader_id, TRAPS) { if (name != nullptr && name[0] == '@') { log_debug(compilation)("resolving %s", name); @@ -159,6 +378,9 @@ static Method* resolve_method_utf8(InstanceKlass* ik, const char* mname, const c return ik->find_method(mn, sg); } +// ============================================================================ +// Type-profile fixups (collect/sanitize/apply) +// ============================================================================ static void sanitize_type_entries(MethodData* mdo) { for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { @@ -261,277 +483,99 @@ static void collect_type_fixups(MethodData* mdo, fx.target.id = stb.intern(cname); fx.loader = loader_id_from_klass(ik); out_fixups.append(fx); - } - } - } - if (pd->is_VirtualCallTypeData()) { - VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); - if (vctd->has_arguments()) { - for (int ai = 0; ai < vctd->number_of_arguments(); ai++) { - int off_b = in_bytes(vctd->argument_type_offset(ai)); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = TypeEntries::valid_klass(*cell); - if (k != nullptr && k->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); - } - } - } - if (vctd->has_return()) { - int off_b = in_bytes(vctd->return_type_offset()); - intptr_t* cell = (intptr_t*)(pd->dp() + off_b); - Klass* k = TypeEntries::valid_klass(*cell); - if (k != nullptr && k->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); - } - } - } - } - if (ParametersTypeData* p = mdo->parameters_type_data()) { - address p_dp = p->dp(); - for (int pi = 0; pi < p->number_of_parameters(); pi++) { - int off_b = in_bytes(ParametersTypeData::type_offset(pi)); - intptr_t* cell = (intptr_t*)(p_dp + off_b); - Klass* k = TypeEntries::valid_klass(*cell); - if (k != nullptr && k->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); - } - } - } -} - -static void apply_fixups(MethodData* mdo, - const ProfileCheckpoint::Fixup* fixups, - u4 fixup_count, - GrowableArray& symtab, - TRAPS) { - for (u4 fi = 0; fi < fixup_count; fi++) { - const ProfileCheckpoint::Fixup& fx = fixups[fi]; - if ((int)fx.target.id < 0 || (int)fx.target.id >= symtab.length()) { - log_debug(compilation)("MDO checkpoint: invalid sym_id=%u (symtab_len=%d) at off=%u", - fx.target.id, symtab.length(), fx.offset_in_mdo); - continue; - } - const char* cname = symtab.at((int)fx.target.id); - InstanceKlass* k = resolve_klass_utf8(cname, fx.loader, THREAD); - if (k != nullptr) { - address cell_addr = (address)mdo + fx.offset_in_mdo; - intptr_t* cell = (intptr_t*)cell_addr; - *cell = TypeEntries::with_status(InstanceKlass::cast(k), *cell); - } else { - log_debug(compilation)("MDO checkpoint: fixup unresolved %s (loader=%d) at off=%u", - cname, (int)fx.loader, fx.offset_in_mdo); - } - } -} - -static bool write_exact(fileStream* out, const void* p, size_t n) { - if (n == 0) return true; - out->write((const char*)p, n); - return true; -} - -static bool read_exact(FILE* in, void* p, size_t n) { - return ::fread(p, 1, n, in) == n; -} - -static bool write_u4(fileStream* out, u4 v) { - return write_exact(out, &v, sizeof(v)); -} - -static bool write_u1(fileStream* out, u1 v) { - return write_exact(out, &v, sizeof(v)); -} - -static bool read_u4(FILE* in, u4& v) { - return read_exact(in, &v, sizeof(v)); -} - -static bool read_u1(FILE* in, u1& v) { - return read_exact(in, &v, sizeof(v)); -} - -static char* read_str(FILE* in) { - u4 len = 0; - if (!read_u4(in, len)) return nullptr; - char* buf = (char*)os::malloc(len + 1, mtInternal); - if (buf == nullptr) return nullptr; - if (len > 0 && !read_exact(in, buf, len)) { os::free(buf); return nullptr; } - buf[len] = '\0'; - return buf; -} - -static bool write_u2(fileStream* out, u2 v) { return write_exact(out, &v, sizeof(v)); } -static bool read_u2(FILE* in, u2& v) { return read_exact(in, &v, sizeof(v)); } - -bool ProfileCheckpoint::Header::write(fileStream* out, const Header& h) { - if (!write_exact(out, h.magic, sizeof(h.magic))) return false; - if (!write_u2(out, h.pointer_size)) return false; - if (!write_u2(out, h.endianness)) return false; - if (!write_exact(out, &h.layout, sizeof(h.layout))) return false; - if (!write_u4(out, h.sym_count)) return false; - if (!write_u4(out, h.rec_count)) return false; - if (!write_u4(out, h.class_count)) return false; - return true; -} - -bool ProfileCheckpoint::Header::read(FILE* in, Header& h) { - if (!read_exact(in, h.magic, sizeof(h.magic))) return false; - if (h.magic[0] != 'M' || h.magic[1] != 'D' || h.magic[2] != 'O' || h.magic[3] != 'X') return false; - if (!read_u2(in, h.pointer_size)) return false; - if (!read_u2(in, h.endianness)) return false; - if (!read_exact(in, &h.layout, sizeof(h.layout))) return false; - if (!read_u4(in, h.sym_count)) return false; - if (!read_u4(in, h.rec_count)) return false; - if (!read_u4(in, h.class_count)) return false; - return true; -} - -static u2 detect_endianness() { - union { u4 v; u1 b[4]; } u; u.v = 1; - return (u.b[0] == 1) ? (u2)0 : (u2)1; -} - -void ProfileCheckpoint::Header::init(Header& h, u4 sym_count, u4 rec_count, u4 class_count) { - h.magic[0] = 'M'; h.magic[1] = 'D'; h.magic[2] = 'O'; h.magic[3] = 'X'; - h.pointer_size = (u2)sizeof(void*); - h.endianness = detect_endianness(); - h.layout.type_profile_level = (uint32_t)TypeProfileLevel; - h.layout.type_profile_args_limit = (int32_t)TypeProfileArgsLimit; - h.layout.type_profile_parms_limit = (int32_t)TypeProfileParmsLimit; - h.layout.type_profile_width = (int64_t)TypeProfileWidth; - h.layout.profile_traps = (uint8_t)(ProfileTraps ? 1 : 0); - h.layout.type_profile_casts = (uint8_t)(TypeProfileCasts ? 1 : 0); - h.layout.spec_trap_limit_extra_entries = (int32_t)SpecTrapLimitExtraEntries; - h.sym_count = sym_count; - h.rec_count = rec_count; - h.class_count = class_count; -} - -bool ProfileCheckpoint::Record::write(fileStream* out, const Record& r, const void* mdo_bytes, const Fixup* fixups, const void* mc_bytes, const void* header_bytes) { - if (!write_u4(out, r.key.klass.id)) return false; - if (!write_u4(out, r.key.name.id)) return false; - if (!write_u4(out, r.key.sig.id)) return false; - u1 loader = (u1)r.key.loader; - if (!write_exact(out, &loader, sizeof(loader))) return false; - if (!write_exact(out, &r.comp_level, sizeof(r.comp_level))) return false; - if (!write_u4(out, r.mdo_size)) return false; - if (!write_u4(out, r.fixup_count)) return false; - for (u4 i = 0; i < r.fixup_count; i++) { - if (!write_u4(out, fixups[i].offset_in_mdo)) return false; - if (!write_u4(out, fixups[i].target.id)) return false; - } - if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; - if (!write_u4(out, r.header_size)) return false; - if (r.header_size > 0) { - if (!write_exact(out, header_bytes, r.header_size)) return false; - } - if (!write_u4(out, r.mc_size)) return false; - if (r.mc_size > 0) { - if (!write_exact(out, mc_bytes, r.mc_size)) return false; - } - return true; -} - -bool ProfileCheckpoint::Record::read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) { - if (!read_u4(in, r.key.klass.id)) return false; - if (!read_u4(in, r.key.name.id)) return false; - if (!read_u4(in, r.key.sig.id)) return false; - u1 loader = 0; - if (!read_exact(in, &loader, sizeof(loader))) return false; - r.key.loader = (LoaderId)loader; - if (!read_exact(in, &r.comp_level, sizeof(r.comp_level))) return false; - if (!read_u4(in, r.mdo_size)) return false; - if (!read_u4(in, r.fixup_count)) return false; - fixups = nullptr; - if (r.fixup_count > 0) { - fixups = (Fixup*)os::malloc(sizeof(Fixup) * r.fixup_count, mtInternal); - if (fixups == nullptr) return false; - for (u4 i = 0; i < r.fixup_count; i++) { - if (!read_u4(in, fixups[i].offset_in_mdo)) { os::free(fixups); return false; } - if (!read_u4(in, fixups[i].target.id)) { os::free(fixups); return false; } + } + } } - } - mdo_bytes = nullptr; - if (r.mdo_size > 0) { - mdo_bytes = (char*)os::malloc(r.mdo_size, mtInternal); - if (mdo_bytes == nullptr) { if (fixups) os::free(fixups); return false; } - if (!read_exact(in, mdo_bytes, r.mdo_size)) { os::free(mdo_bytes); if (fixups) os::free(fixups); return false; } - } - header_bytes = nullptr; - if (!read_u4(in, r.header_size)) { - if (fixups) os::free(fixups); - if (mdo_bytes) os::free(mdo_bytes); - return false; - } - if (r.header_size > 0) { - header_bytes = (char*)os::malloc(r.header_size, mtInternal); - if (header_bytes == nullptr) { - if (fixups) os::free(fixups); - if (mdo_bytes) os::free(mdo_bytes); - return false; + if (pd->is_VirtualCallTypeData()) { + VirtualCallTypeData* vctd = pd->as_VirtualCallTypeData(); + if (vctd->has_arguments()) { + for (int ai = 0; ai < vctd->number_of_arguments(); ai++) { + int off_b = in_bytes(vctd->argument_type_offset(ai)); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_klass(ik); + out_fixups.append(fx); + } + } + } + if (vctd->has_return()) { + int off_b = in_bytes(vctd->return_type_offset()); + intptr_t* cell = (intptr_t*)(pd->dp() + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); + fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_klass(ik); + out_fixups.append(fx); + } + } } - if (!read_exact(in, header_bytes, r.header_size)) { - os::free(header_bytes); - if (fixups) os::free(fixups); - if (mdo_bytes) os::free(mdo_bytes); - return false; + } + if (ParametersTypeData* p = mdo->parameters_type_data()) { + address p_dp = p->dp(); + for (int pi = 0; pi < p->number_of_parameters(); pi++) { + int off_b = in_bytes(ParametersTypeData::type_offset(pi)); + intptr_t* cell = (intptr_t*)(p_dp + off_b); + Klass* k = TypeEntries::valid_klass(*cell); + if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + const char* cname = get_klassname_utf8(ik); + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); + fx.target.id = stb.intern(cname); + fx.loader = loader_id_from_klass(ik); + out_fixups.append(fx); + } } } - if (!read_u4(in, r.mc_size)) return false; - mc_bytes = nullptr; - if (r.mc_size > 0) { - mc_bytes = (char*)os::malloc(r.mc_size, mtInternal); - if (mc_bytes == nullptr) { - if (fixups) os::free(fixups); - if (mdo_bytes) os::free(mdo_bytes); - if (header_bytes) os::free(header_bytes); - return false; +} + +static void apply_fixups(MethodData* mdo, + const ProfileCheckpoint::Fixup* fixups, + u4 fixup_count, + GrowableArray& symtab, + TRAPS) { + for (u4 fi = 0; fi < fixup_count; fi++) { + const ProfileCheckpoint::Fixup& fx = fixups[fi]; + if ((int)fx.target.id < 0 || (int)fx.target.id >= symtab.length()) { + log_debug(compilation)("MDO checkpoint: invalid sym_id=%u (symtab_len=%d) at off=%u", + fx.target.id, symtab.length(), fx.offset_in_mdo); + continue; } - if (!read_exact(in, mc_bytes, r.mc_size)) { - os::free(mc_bytes); - if (fixups) os::free(fixups); - if (mdo_bytes) os::free(mdo_bytes); - if (header_bytes) os::free(header_bytes); - return false; + const char* cname = symtab.at((int)fx.target.id); + InstanceKlass* k = resolve_klass_utf8(cname, fx.loader, THREAD); + if (k != nullptr) { + address cell_addr = (address)mdo + fx.offset_in_mdo; + intptr_t* cell = (intptr_t*)cell_addr; + *cell = TypeEntries::with_status(InstanceKlass::cast(k), *cell); + } else { + log_debug(compilation)("MDO checkpoint: fixup unresolved %s (loader=%d) at off=%u", + cname, (int)fx.loader, fx.offset_in_mdo); } } - return true; } -ProfileCheckpoint::Loader::Loader(JavaThread* thread) - : _thread(thread), - _records_read(0), - _records_installed(0), - _size_mismatch(0) {} - -const char* ProfileCheckpoint::Loader::load_status_name(LoadStatus status) { - switch (status) { - case LoadStatus::Success: return "success"; - case LoadStatus::MissingPath: return "missing_path"; - case LoadStatus::FileOpenFailed: return "file_open_failed"; - case LoadStatus::HeaderInvalid: return "header_invalid"; - case LoadStatus::SymtabReadFailed: return "symtab_read_failed"; - case LoadStatus::RecordReadFailed: return "record_read_failed"; - default: return "unknown"; - } +// ============================================================================ +// Load/install path (validate header, install records into live MDOs) +// ============================================================================ +static bool copy_mdo_payload(MethodData* dst_mdo, const char* src_bytes, u4 src_size) { + if (dst_mdo == nullptr) return false; + if ((u4)dst_mdo->size_in_bytes() != src_size) return false; + address dst_base = dst_mdo->data_base(); + const size_t data_sz = (size_t)dst_mdo->data_size(); + const size_t data_off = (size_t)(dst_base - (address)dst_mdo); + const char* src = src_bytes + data_off; + Copy::conjoint_jbytes(src, (char*)dst_base, (jlong)data_sz); + return true; } static void trigger_eager_compile(Method* target, u1 stored_level, JavaThread* thread) { @@ -552,6 +596,70 @@ static void trigger_eager_compile(Method* target, u1 stored_level, JavaThread* t if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } } +static bool header_matches_current_vm(const ProfileCheckpoint::Header& hdr) { + bool ok = true; + + const u2 expected_ptr_size = (u2)sizeof(void*); + const u2 expected_endian = detect_endianness(); + if (hdr.pointer_size != expected_ptr_size) { + log_warning(compilation)("MDO checkpoint: header mismatch: pointer_size file=%u vm=%u", + (unsigned)hdr.pointer_size, (unsigned)expected_ptr_size); + ok = false; + } + if (hdr.endianness != expected_endian) { + log_warning(compilation)("MDO checkpoint: header mismatch: endianness file=%u vm=%u", + (unsigned)hdr.endianness, (unsigned)expected_endian); + ok = false; + } + + const auto& lf = hdr.layout; + const uint32_t exp_tpl = (uint32_t)TypeProfileLevel; + const int32_t exp_args = (int32_t)TypeProfileArgsLimit; + const int32_t exp_parms = (int32_t)TypeProfileParmsLimit; + const int64_t exp_width = (int64_t)TypeProfileWidth; + const uint8_t exp_traps = (uint8_t)(ProfileTraps ? 1 : 0); + const uint8_t exp_casts = (uint8_t)(TypeProfileCasts ? 1 : 0); + const int32_t exp_spec_extra = (int32_t)SpecTrapLimitExtraEntries; + + if (lf.type_profile_level != exp_tpl) { + log_warning(compilation)("MDO checkpoint: header mismatch: TypeProfileLevel file=%u vm=%u", + (unsigned)lf.type_profile_level, (unsigned)exp_tpl); + ok = false; + } + if (lf.type_profile_args_limit != exp_args) { + log_warning(compilation)("MDO checkpoint: header mismatch: TypeProfileArgsLimit file=%d vm=%d", + (int)lf.type_profile_args_limit, (int)exp_args); + ok = false; + } + if (lf.type_profile_parms_limit != exp_parms) { + log_warning(compilation)("MDO checkpoint: header mismatch: TypeProfileParmsLimit file=%d vm=%d", + (int)lf.type_profile_parms_limit, (int)exp_parms); + ok = false; + } + if (lf.type_profile_width != exp_width) { + log_warning(compilation)("MDO checkpoint: header mismatch: TypeProfileWidth file=" INT64_FORMAT " vm=" INT64_FORMAT, + (int64_t)lf.type_profile_width, (int64_t)exp_width); + ok = false; + } + if (lf.profile_traps != exp_traps) { + log_warning(compilation)("MDO checkpoint: header mismatch: ProfileTraps file=%u vm=%u", + (unsigned)lf.profile_traps, (unsigned)exp_traps); + ok = false; + } + if (lf.type_profile_casts != exp_casts) { + log_warning(compilation)("MDO checkpoint: header mismatch: TypeProfileCasts file=%u vm=%u", + (unsigned)lf.type_profile_casts, (unsigned)exp_casts); + ok = false; + } + if (lf.spec_trap_limit_extra_entries != exp_spec_extra) { + log_warning(compilation)("MDO checkpoint: header mismatch: SpecTrapLimitExtraEntries file=%d vm=%d", + (int)lf.spec_trap_limit_extra_entries, (int)exp_spec_extra); + ok = false; + } + + return ok; +} + void ProfileCheckpoint::wait_for_compile_completion(JavaThread* THREAD) { if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) { return; @@ -570,6 +678,25 @@ void ProfileCheckpoint::wait_for_compile_completion(JavaThread* THREAD) { log_info(compilation)("Eager compilation completed"); } +ProfileCheckpoint::Loader::Loader(JavaThread* thread) + : _thread(thread), + _records_read(0), + _records_installed(0), + _size_mismatch(0) {} + +const char* ProfileCheckpoint::Loader::load_status_name(LoadStatus status) { + switch (status) { + case LoadStatus::Success: return "success"; + case LoadStatus::MissingPath: return "missing_path"; + case LoadStatus::FileOpenFailed: return "file_open_failed"; + case LoadStatus::HeaderInvalid: return "header_invalid"; + case LoadStatus::HeaderMismatch: return "header_mismatch"; + case LoadStatus::SymtabReadFailed: return "symtab_read_failed"; + case LoadStatus::RecordReadFailed: return "record_read_failed"; + default: return "unknown"; + } +} + bool ProfileCheckpoint::Loader::install_record(const Record& rec, Fixup* fixups, char* mdo_bytes, @@ -690,24 +817,27 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( } ResourceMark rm; - BinaryStreamReader reader(f); - Header hdr; - if (!reader.read_header(hdr)) { + if (!read_header(f, hdr)) { fclose(f); result.status = LoadStatus::HeaderInvalid; return result; } + if (!header_matches_current_vm(hdr)) { + fclose(f); + result.status = LoadStatus::HeaderMismatch; + return result; + } u4 sym_count = hdr.sym_count; u4 rec_total = hdr.rec_count; GrowableArray symtab((int)sym_count); - if (!reader.read_symtab(symtab, sym_count)) { + if (!read_symtab(f, symtab, sym_count)) { fclose(f); result.status = LoadStatus::SymtabReadFailed; return result; } GrowableArray classes((int)hdr.class_count); - if (!reader.read_classes(classes, hdr.class_count)) { + if (!read_classes(f, classes, hdr.class_count)) { fclose(f); result.status = LoadStatus::RecordReadFailed; return result; @@ -738,7 +868,7 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( char* mdo_bytes = nullptr; char* mc_bytes = nullptr; char* header_bytes = nullptr; - if (!reader.read_record(rec, fixups, mdo_bytes, mc_bytes, header_bytes)) { + if (!read_record(f, rec, fixups, mdo_bytes, mc_bytes, header_bytes)) { log_debug(compilation)("MDO checkpoint: record read failed at %u", i); result.status = LoadStatus::RecordReadFailed; if (fixups) os::free(fixups); @@ -775,6 +905,9 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( return result; } +// ============================================================================ +// Dump enumeration helpers (collect classes/methods, build symtab) +// ============================================================================ // Build RecMeta (holder/name/sig/mdo size/pointer) for a method with an MDO static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { ProfileCheckpoint::RecMeta r{}; @@ -862,75 +995,9 @@ u4 ProfileCheckpoint::SymtabBuilder::id_of(const char* s) const { return (u4)UINT_MAX; } -bool ProfileCheckpoint::BinaryStreamWriter::write_header(u4 sym_count, u4 rec_count, u4 class_count) { - Header h; - Header::init(h, sym_count, rec_count, class_count); - return Header::write(_out, h); -} - -bool ProfileCheckpoint::BinaryStreamWriter::write_symtab(const GrowableArray& symbols) const { - for (int i = 0; i < symbols.length(); i++) { - const char* s = symbols.at(i); - u4 len = (u4)strlen(s); - if (!write_u4(_out, len)) return false; - if (!write_exact(_out, s, len)) return false; - } - return true; -} - -bool ProfileCheckpoint::BinaryStreamWriter::write_classes(const GrowableArray& classes) const { - for (int i = 0; i < classes.length(); i++) { - const Class& c = classes.at(i); - if (!write_u1(_out, (u1)c.loader)) return false; - if (!write_u4(_out, c.klass.id)) return false; - } - return true; -} - -bool ProfileCheckpoint::BinaryStreamWriter::write_record(const Record& r, const void* mdo_bytes, - const Fixup* fixups, const void* mc_bytes, const void* header_bytes) const { - return Record::write(_out, r, mdo_bytes, fixups, mc_bytes, header_bytes); -} - -bool ProfileCheckpoint::BinaryStreamReader::read_header(Header& h) const { - return Header::read(_in, h); -} - -bool ProfileCheckpoint::BinaryStreamReader::read_symtab(GrowableArray& symbols, u4 expected) const { - for (u4 si = 0; si < expected; si++) { - char* s = read_str(_in); - if (s == nullptr) { - return false; - } - symbols.append(s); - } - return true; -} - -bool ProfileCheckpoint::BinaryStreamReader::read_classes(GrowableArray& classes, u4 expected) const { - for (u4 ci = 0; ci < expected; ci++) { - u1 loader_raw = 0; - if (!read_u1(_in, loader_raw)) { - return false; - } - u4 klass_id = 0; - if (!read_u4(_in, klass_id)) { - return false; - } - Class c; - c.loader = (LoaderId)loader_raw; - c.klass.id = klass_id; - classes.append(c); - } - return true; -} - -bool ProfileCheckpoint::BinaryStreamReader::read_record(Record& r, Fixup*& fixups, - char*& mdo_bytes, char*& mc_bytes, - char*& header_bytes) const { - return Record::read(_in, r, fixups, mdo_bytes, mc_bytes, header_bytes); -} - +// ============================================================================ +// Public entrypoints (ProfileCheckpoint::load / dump_to_stream) +// ============================================================================ void ProfileCheckpoint::load(JavaThread* THREAD) { if (!LoadMDOAtStartup || MDOReplayLoadFile == nullptr) return; Loader loader(THREAD); @@ -949,126 +1016,124 @@ void ProfileCheckpoint::load(JavaThread* THREAD) { } } -bool ProfileCheckpoint::Loader::dump_to_stream(fileStream* out) { - if (out == nullptr) { - return false; - } - - ResourceMark rm; - SymtabBuilder stb; - GrowableArray classes(1024); - collect_classes(stb, classes); - GrowableArray methods = get_methods(); - GrowableArray recs(1024); - - for (int i = 0; i < methods.length(); i++) { - Method* m = methods.at(i); - if (m->method_data() == nullptr) continue; - recs.append(make_rec_meta_for_method(m)); - } - - GrowableArray< GrowableArray* > fixups_per_rec(recs.length()); - for (int ri = 0; ri < recs.length(); ri++) { - Method* m = methods.at(ri); - MethodData* mdo = m->method_data(); - GrowableArray* fx = new GrowableArray(16); - collect_type_fixups(mdo, stb, *fx); - fixups_per_rec.append(fx); - } - - for (int ri = 0; ri < recs.length(); ri++) { - stb.intern(recs.at(ri).kname); - stb.intern(recs.at(ri).mname); - stb.intern(recs.at(ri).sig); - } - stb.freeze(); +void ProfileCheckpoint::dump_to_stream(fileStream* out) { + // Opportunistically scan resolved indy/invokehandle sites to harvest locators + // for hidden classes before dumping. + DynoLocatorScan::scan_all_classes(); - BinaryStreamWriter writer(out); - if (!writer.write_header(stb.length(), (u4)recs.length(), (u4)classes.length())) { - return false; - } - if (!writer.write_symtab(stb.symbols())) { - return false; - } - if (!writer.write_classes(classes)) { - return false; + if (out == nullptr) { + log_warning(compilation)("MDO checkpoint: dump failed (null output stream)"); + return; } + bool ok = true; u4 emitted = 0; - for (int ri = 0; ri < recs.length(); ri++) { - const RecMeta& rn = recs.at(ri); - Method* m = methods.at(ri); - MethodData* mdo = m->method_data(); - Record rec; - rec.key.loader = loader_id_from_klass(m->method_holder()); - rec.key.klass.id = stb.id_of(rn.kname); - rec.key.name.id = stb.id_of(rn.mname); - rec.key.sig.id = stb.id_of(rn.sig); - rec.key.bytecode_crc32 = 0; - rec.mdo_size = rn.mdo_size; - rec.comp_level = rn.comp_level; - GrowableArray* fx_entries = fixups_per_rec.at(ri); - rec.fixup_count = (u4)fx_entries->length(); - - const void* mc_bytes = nullptr; - MethodData::HeaderSnapshot header_snapshot; - mdo->snapshot_header(&header_snapshot); - rec.header_size = sizeof(header_snapshot); - const void* header_bytes = &header_snapshot; - GrowableArray mc_buf(0); - MethodCounters* mc = m->method_counters(); - if (mc != nullptr) { - const size_t ic_sz = sizeof(InvocationCounter); - const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint); - for (size_t fill = 0; fill < mc_sz; fill++) mc_buf.append((char)0); - char* p = mc_buf.adr_at(0); - Copy::conjoint_jbytes((char*)mc->invocation_counter(), p, (jlong)ic_sz); - p += ic_sz; - Copy::conjoint_jbytes((char*)mc->backedge_counter(), p, (jlong)ic_sz); - p += ic_sz; - *(jlong*)p = mc->prev_time(); p += sizeof(jlong); - *(float*)p = mc->rate(); p += sizeof(float); - *(jint*)p = mc->prev_event_count(); p += sizeof(jint); - rec.mc_size = (u4)mc_sz; - mc_bytes = mc_buf.adr_at(0); - } else { - rec.mc_size = 0; + do { + ResourceMark rm; + SymtabBuilder stb; + GrowableArray classes(1024); + collect_classes(stb, classes); + GrowableArray methods = get_methods(); + GrowableArray rec_metas(1024); + + for (int i = 0; i < methods.length(); i++) { + Method* m = methods.at(i); + if (m->method_data() == nullptr) continue; + rec_metas.append(make_rec_meta_for_method(m)); } - Fixup* fixup_buf = rec.fixup_count ? fx_entries->adr_at(0) : nullptr; - if (!writer.write_record(rec, rn.mdo_ptr, fixup_buf, mc_bytes, header_bytes)) { - return false; + + GrowableArray< GrowableArray* > fixups_per_rec(rec_metas.length()); + for (int ri = 0; ri < rec_metas.length(); ri++) { + Method* m = methods.at(ri); + MethodData* mdo = m->method_data(); + GrowableArray* fx = new GrowableArray(16); + collect_type_fixups(mdo, stb, *fx); + fixups_per_rec.append(fx); + } + + for (int ri = 0; ri < rec_metas.length(); ri++) { + stb.intern(rec_metas.at(ri).kname); + stb.intern(rec_metas.at(ri).mname); + stb.intern(rec_metas.at(ri).sig); } - if (PrintMDOAtDump) { - const char* kname = rn.kname; - const char* mname = rn.mname; - const char* sig = rn.sig; - u1 comp_level = rn.comp_level; - tty->print_cr("[Dump] %s %s %s %u", kname, mname, sig, comp_level); - tty->print_cr("[MethodDataHeader]"); - print_mdo_header(mdo); - tty->print_cr("[MethodData]"); - mdo->print_data_on(tty); - tty->print_cr("[MethodCounters]"); - MethodCounters* mc_print = m->method_counters(); - if (mc_print != nullptr) { - mc_print->print_data_on(tty); + stb.freeze(); + + Header h; + init_header(h, stb.length(), (u4)rec_metas.length(), (u4)classes.length()); + if (!write_header(out, h)) { ok = false; break; } + if (!write_symtab(out, stb.symbols())) { ok = false; break; } + if (!write_classes(out, classes)) { ok = false; break; } + + for (int ri = 0; ri < rec_metas.length(); ri++) { + const RecMeta& rec_meta = rec_metas.at(ri); + Method* m = methods.at(ri); + MethodData* mdo = m->method_data(); + Record rec; + + rec.key.loader = loader_id_from_klass(m->method_holder()); + rec.key.klass.id = stb.id_of(rec_meta.kname); + rec.key.name.id = stb.id_of(rec_meta.mname); + rec.key.sig.id = stb.id_of(rec_meta.sig); + rec.key.bytecode_crc32 = 0; + rec.mdo_size = rec_meta.mdo_size; + rec.comp_level = rec_meta.comp_level; + GrowableArray* fx_entries = fixups_per_rec.at(ri); + rec.fixup_count = (u4)fx_entries->length(); + + const void* mc_bytes = nullptr; + MethodData::HeaderSnapshot header_snapshot; + mdo->snapshot_header(&header_snapshot); + rec.header_size = sizeof(header_snapshot); + const void* header_bytes = &header_snapshot; + GrowableArray mc_buf(0); + MethodCounters* mc = m->method_counters(); + if (mc != nullptr) { + const size_t ic_sz = sizeof(InvocationCounter); + const size_t mc_sz = ic_sz * 2 + sizeof(jlong) + sizeof(float) + sizeof(jint); + for (size_t fill = 0; fill < mc_sz; fill++) mc_buf.append((char)0); + char* p = mc_buf.adr_at(0); + Copy::conjoint_jbytes((char*)mc->invocation_counter(), p, (jlong)ic_sz); + p += ic_sz; + Copy::conjoint_jbytes((char*)mc->backedge_counter(), p, (jlong)ic_sz); + p += ic_sz; + *(jlong*)p = mc->prev_time(); p += sizeof(jlong); + *(float*)p = mc->rate(); p += sizeof(float); + *(jint*)p = mc->prev_event_count(); p += sizeof(jint); + rec.mc_size = (u4)mc_sz; + mc_bytes = mc_buf.adr_at(0); } else { - tty->print_cr(" (none)"); + rec.mc_size = 0; } + Fixup* fixup_buf = rec.fixup_count ? fx_entries->adr_at(0) : nullptr; + if (!write_record(out, rec, rec_meta.mdo_ptr, fixup_buf, mc_bytes, header_bytes)) { ok = false; break; } + + if (PrintMDOAtDump) { + const char* kname = rec_meta.kname; + const char* mname = rec_meta.mname; + const char* sig = rec_meta.sig; + u1 comp_level = rec_meta.comp_level; + tty->print_cr("[Dump] %s %s %s %u", kname, mname, sig, comp_level); + tty->print_cr("[MethodDataHeader]"); + print_mdo_header(mdo); + tty->print_cr("[MethodData]"); + mdo->print_data_on(tty); + tty->print_cr("[MethodCounters]"); + MethodCounters* mc_print = m->method_counters(); + if (mc_print != nullptr) { + mc_print->print_data_on(tty); + } else { + tty->print_cr(" (none)"); + } + } + emitted++; } - emitted++; - } - log_info(compilation)("MDO checkpoint: dumped %u MDOs (sym=%u)", emitted, stb.length()); - return true; -} - -void ProfileCheckpoint::dump_to_stream(fileStream* out) { - // Opportunistically scan resolved indy/invokehandle sites to harvest locators - // for hidden classes before dumping. - DynoLocatorScan::scan_all_classes(); + } while (false); - if (!Loader::dump_to_stream(out)) { + if (!ok) { log_warning(compilation)("MDO checkpoint: dump failed"); + return; } + + log_info(compilation)("MDO checkpoint: dumped %u MDOs", emitted); } \ No newline at end of file diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 4b13ffa4dbc..0ab6eea8ab1 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -9,7 +9,7 @@ class fileStream; class ProfileCheckpoint { public: - // Binary format v3 (File = [HEADER][SYMTAB][RECORDS]) + // Binary format (File = [HEADER][SYMTAB][CLASSES][RECORDS]) // HEADER: // magic[4] = 'M','D','O','X' // pointer_size: u16 (e.g., 8) @@ -22,12 +22,15 @@ class ProfileCheckpoint { // SYMTAB: repeated sym_count times // [u32 len][len bytes utf8] // - // CLASS: repeated class_count times: - // [u4 loader_id][u4 klass_sym_id] + // CLASSES: repeated class_count times: + // [u1 loader_id][u4 klass_sym_id] // // RECORD: repeated rec_count times - // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, mdo_size:u32, - // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes] + // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, comp_level:u8, mdo_size:u32, + // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes], header_size:u32, [header bytes], mc_size:u32, [mc bytes] + // + // Fixup: repeated fixup_count times + // [u4 offset_in_mdo][u4 target_sym_id][u1 loader_id] enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED, HIDDEN }; @@ -85,10 +88,6 @@ class ProfileCheckpoint { u4 sym_count; u4 rec_count; u4 class_count; - - static void init(Header& h, u4 sym_count, u4 rec_count, u4 class_count); - static bool write(fileStream* out, const Header& h); - static bool read(FILE* in, Header& h); }; struct Class { @@ -103,31 +102,6 @@ class ProfileCheckpoint { u4 header_size; u4 mc_size; // bytes of MethodCounters snapshot (may be 0) u1 comp_level; - - static bool write(fileStream* out, const Record& r, const void* mdo_bytes, - const Fixup* fixups, const void* mc_bytes, const void* header_bytes); - static bool read(FILE* in, Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes); - }; - - class BinaryStreamWriter { - fileStream* _out; - public: - explicit BinaryStreamWriter(fileStream* out) : _out(out) {} - bool write_header(u4 sym_count, u4 rec_count, u4 class_count); - bool write_symtab(const GrowableArray& symbols) const; - bool write_classes(const GrowableArray& classes) const; - bool write_record(const Record& r, const void* mdo_bytes, - const Fixup* fixups, const void* mc_bytes, const void* header_bytes) const; - }; - - class BinaryStreamReader { - FILE* _in; - public: - explicit BinaryStreamReader(FILE* in) : _in(in) {} - bool read_header(Header& h) const; - bool read_symtab(GrowableArray& symbols, u4 expected) const; - bool read_classes(GrowableArray& classes, u4 expected) const; - bool read_record(Record& r, Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) const; }; class SymtabBuilder { @@ -150,6 +124,7 @@ class ProfileCheckpoint { MissingPath, FileOpenFailed, HeaderInvalid, + HeaderMismatch, SymtabReadFailed, RecordReadFailed }; @@ -165,7 +140,6 @@ class ProfileCheckpoint { explicit Loader(class JavaThread* thread); LoadResult load_from_file(const char* path); static const char* load_status_name(LoadStatus status); - static bool dump_to_stream(fileStream* out); private: class JavaThread* _thread; int _records_read; From c574914da9170af2929359e9315b5a798c25c8aa Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Tue, 6 Jan 2026 14:11:04 -0800 Subject: [PATCH 17/23] java api --- src/hotspot/share/include/jvm.h | 9 +++ src/hotspot/share/prims/jvm.cpp | 51 ++++++++++++++ .../share/services/profileCheckpoint.cpp | 46 +++++++++---- .../profilecheckpoint/ProfileCheckpoint.java | 66 +++++++++++++++++++ .../share/native/libjava/ProfileCheckpoint.c | 43 ++++++++++++ 5 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/profilecheckpoint/ProfileCheckpoint.java create mode 100644 src/java.base/share/native/libjava/ProfileCheckpoint.c diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 73f60765a70..7b81a3a3c7a 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1091,6 +1091,15 @@ JVM_InitAgentProperties(JNIEnv *env, jobject agent_props); JNIEXPORT jstring JNICALL JVM_GetTemporaryDirectory(JNIEnv *env); +/* + * HotSpot profile checkpoint support. + */ +JNIEXPORT void JNICALL +JVM_ProfileCheckpointDump(JNIEnv *env, jstring path); + +JNIEXPORT void JNICALL +JVM_ProfileCheckpointLoad(JNIEnv *env, jstring path); + /* Generics reflection support. * * Returns information about the given class's EnclosingMethod diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index c6f1172ca08..7a18eaa2228 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -93,9 +93,11 @@ #include "runtime/threadSMR.hpp" #include "runtime/vframe.inline.hpp" #include "runtime/vmOperations.hpp" +#include "runtime/vmThread.hpp" #include "runtime/vm_version.hpp" #include "services/attachListener.hpp" #include "services/management.hpp" +#include "services/profileCheckpoint.hpp" #include "services/threadService.hpp" #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" @@ -3625,6 +3627,55 @@ JVM_ENTRY(jobject, JVM_InitAgentProperties(JNIEnv *env, jobject properties)) return properties; JVM_END +JVM_ENTRY(void, JVM_ProfileCheckpointDump(JNIEnv* env, jstring path)) + if (path == nullptr) { + THROW(vmSymbols::java_lang_NullPointerException()); + } + + ResourceMark rm(THREAD); + Handle hpath(THREAD, JNIHandles::resolve_non_null(path)); + const char* path_utf8 = java_lang_String::as_utf8_string(hpath()); + + fileStream fs(path_utf8, "wb"); + if (!fs.is_open()) { + THROW_MSG(vmSymbols::java_io_IOException(), err_msg("Failed to open profile checkpoint dump file: %s", path_utf8)); + } + + // Run at a safepoint to avoid concurrent MDO mutations during dump. + class VM_ProfileCheckpointDump : public VM_Operation { + fileStream* _out; + public: + explicit VM_ProfileCheckpointDump(fileStream* out) : _out(out) {} + virtual VMOp_Type type() const { return VMOp_GC_HeapInspection; } + virtual void doit() { ProfileCheckpoint::dump_to_stream(_out); } + } op(&fs); + + VMThread::execute(&op); +JVM_END + +JVM_ENTRY(void, JVM_ProfileCheckpointLoad(JNIEnv* env, jstring path)) + if (path == nullptr) { + THROW(vmSymbols::java_lang_NullPointerException()); + } + + ResourceMark rm(THREAD); + Handle hpath(THREAD, JNIHandles::resolve_non_null(path)); + const char* path_utf8 = java_lang_String::as_utf8_string(hpath()); + + ProfileCheckpoint::Loader loader(THREAD); + ProfileCheckpoint::Loader::LoadResult res = loader.load_from_file(path_utf8); + if (res.status != ProfileCheckpoint::Loader::LoadStatus::Success) { + const char* status = ProfileCheckpoint::Loader::load_status_name(res.status); + THROW_MSG(vmSymbols::java_io_IOException(), + err_msg("Failed to load profile checkpoint (%s): %s", status, path_utf8)); + } + + // Preserve existing behavior for "eager compile after load" when enabled. + if (EagerCompileAfterLoad) { + ProfileCheckpoint::wait_for_compile_completion(THREAD); + } +JVM_END + JVM_ENTRY(jobjectArray, JVM_GetEnclosingMethodInfo(JNIEnv *env, jclass ofClass)) { JvmtiVMObjectAllocEventCollector oam; diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 931cdc874bc..875c52b1abf 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -836,6 +836,17 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( result.status = LoadStatus::SymtabReadFailed; return result; } + struct SymtabCleanup { + GrowableArray& _syms; + explicit SymtabCleanup(GrowableArray& syms) : _syms(syms) {} + ~SymtabCleanup() { + for (int i = 0; i < _syms.length(); i++) { + if (_syms.at(i) != nullptr) { + os::free(_syms.at(i)); + } + } + } + } symtab_cleanup(symtab); GrowableArray classes((int)hdr.class_count); if (!read_classes(f, classes, hdr.class_count)) { fclose(f); @@ -844,6 +855,8 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( } JavaThread* THREAD = _thread; // For exception macros. + + // preload all classes for (int ci = 0; ci < classes.length(); ci++) { const ProfileCheckpoint::Class& cls = classes.at(ci); if ((int)cls.klass.id >= symtab.length()) { @@ -861,13 +874,15 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( } result.status = LoadStatus::Success; - + + // process each record for (u4 i = 0; i < rec_total; i++) { Record rec; Fixup* fixups = nullptr; char* mdo_bytes = nullptr; char* mc_bytes = nullptr; char* header_bytes = nullptr; + if (!read_record(f, rec, fixups, mdo_bytes, mc_bytes, header_bytes)) { log_debug(compilation)("MDO checkpoint: record read failed at %u", i); result.status = LoadStatus::RecordReadFailed; @@ -912,7 +927,7 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( static ProfileCheckpoint::RecMeta make_rec_meta_for_method(Method* m) { ProfileCheckpoint::RecMeta r{}; MethodData* mdo = m->method_data(); - // Caller guarantees mdo != nullptr + // caller guarantees mdo != nullptr MutexLocker ml(mdo->extra_data_lock(), Mutex::_no_safepoint_check_flag); InstanceKlass* holder = m->method_holder(); r.kname = get_klassname_utf8(holder); @@ -964,7 +979,7 @@ static void collect_classes(ProfileCheckpoint::SymtabBuilder& stb, ClassLoaderDataGraph::classes_do(&closure); } -GrowableArray get_methods() { +GrowableArray get_methods_with_mdo() { GrowableArray methods(1024); static GrowableArray* g_methods; g_methods = &methods; @@ -1034,24 +1049,30 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { SymtabBuilder stb; GrowableArray classes(1024); collect_classes(stb, classes); - GrowableArray methods = get_methods(); + GrowableArray methods_with_mdo = get_methods_with_mdo(); GrowableArray rec_metas(1024); - for (int i = 0; i < methods.length(); i++) { - Method* m = methods.at(i); - if (m->method_data() == nullptr) continue; + // create rec_metas for methods with MDO + for (int i = 0; i < methods_with_mdo.length(); i++) { + Method* m = methods_with_mdo.at(i); + if (m->method_data() == nullptr) { + log_warning(compilation)("MDO checkpoint: method %s %s %s has no MDO", m->name()->as_utf8(), m->signature()->as_utf8(), m->method_holder()->name()->as_utf8()); + ok = false; break; + } rec_metas.append(make_rec_meta_for_method(m)); } + // create fixups for each rec_meta GrowableArray< GrowableArray* > fixups_per_rec(rec_metas.length()); for (int ri = 0; ri < rec_metas.length(); ri++) { - Method* m = methods.at(ri); + Method* m = methods_with_mdo.at(ri); MethodData* mdo = m->method_data(); GrowableArray* fx = new GrowableArray(16); collect_type_fixups(mdo, stb, *fx); fixups_per_rec.append(fx); } - + + // store all symbols in symtab for (int ri = 0; ri < rec_metas.length(); ri++) { stb.intern(rec_metas.at(ri).kname); stb.intern(rec_metas.at(ri).mname); @@ -1059,15 +1080,17 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { } stb.freeze(); + // write header, symtab, classes Header h; init_header(h, stb.length(), (u4)rec_metas.length(), (u4)classes.length()); if (!write_header(out, h)) { ok = false; break; } if (!write_symtab(out, stb.symbols())) { ok = false; break; } if (!write_classes(out, classes)) { ok = false; break; } + // write each record for (int ri = 0; ri < rec_metas.length(); ri++) { const RecMeta& rec_meta = rec_metas.at(ri); - Method* m = methods.at(ri); + Method* m = methods_with_mdo.at(ri); MethodData* mdo = m->method_data(); Record rec; @@ -1080,7 +1103,8 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { rec.comp_level = rec_meta.comp_level; GrowableArray* fx_entries = fixups_per_rec.at(ri); rec.fixup_count = (u4)fx_entries->length(); - + + // create mc_bytes and header_bytes const void* mc_bytes = nullptr; MethodData::HeaderSnapshot header_snapshot; mdo->snapshot_header(&header_snapshot); diff --git a/src/java.base/share/classes/jdk/internal/profilecheckpoint/ProfileCheckpoint.java b/src/java.base/share/classes/jdk/internal/profilecheckpoint/ProfileCheckpoint.java new file mode 100644 index 00000000000..49c6f59f18a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/profilecheckpoint/ProfileCheckpoint.java @@ -0,0 +1,66 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.internal.profilecheckpoint; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Objects; + +/** + * JVM-internal API to explicitly dump and load a HotSpot profile checkpoint. + * + *

This is intended for tightly-controlled environments (e.g., serverless + * platforms) to manage JIT profiling state. + */ +public final class ProfileCheckpoint { + private ProfileCheckpoint() {} + + /** + * Dumps the current profile checkpoint to {@code path}. + * + * @throws NullPointerException if {@code path} is null + * @throws IOException if the dump cannot be written + */ + public static void dump(Path path) throws IOException { + Objects.requireNonNull(path, "path"); + dump0(path.toString()); + } + + /** + * Loads a profile checkpoint from {@code path}. + * + * @throws NullPointerException if {@code path} is null + * @throws IOException if the checkpoint cannot be read/loaded + */ + public static void load(Path path) throws IOException { + Objects.requireNonNull(path, "path"); + load0(path.toString()); + } + + private static native void dump0(String path) throws IOException; + private static native void load0(String path) throws IOException; +} + + diff --git a/src/java.base/share/native/libjava/ProfileCheckpoint.c b/src/java.base/share/native/libjava/ProfileCheckpoint.c new file mode 100644 index 00000000000..6671d31613a --- /dev/null +++ b/src/java.base/share/native/libjava/ProfileCheckpoint.c @@ -0,0 +1,43 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +#include "jni.h" +#include "jvm.h" + +#include "jdk_internal_profilecheckpoint_ProfileCheckpoint.h" + +JNIEXPORT void JNICALL +Java_jdk_internal_profilecheckpoint_ProfileCheckpoint_dump0(JNIEnv* env, jclass cls, jstring path) +{ + JVM_ProfileCheckpointDump(env, path); +} + +JNIEXPORT void JNICALL +Java_jdk_internal_profilecheckpoint_ProfileCheckpoint_load0(JNIEnv* env, jclass cls, jstring path) +{ + JVM_ProfileCheckpointLoad(env, path); +} + + From e632ddef6ce55f7dd209c6ff5cb2e13e5b152c3c Mon Sep 17 00:00:00 2001 From: Hedge-Lord Date: Sun, 11 Jan 2026 22:24:53 -0800 Subject: [PATCH 18/23] resolve custom loader by name --- .../share/services/profileCheckpoint.cpp | 201 ++++++++++++++---- .../share/services/profileCheckpoint.hpp | 18 +- 2 files changed, 171 insertions(+), 48 deletions(-) diff --git a/src/hotspot/share/services/profileCheckpoint.cpp b/src/hotspot/share/services/profileCheckpoint.cpp index 875c52b1abf..60c69b8f4c1 100644 --- a/src/hotspot/share/services/profileCheckpoint.cpp +++ b/src/hotspot/share/services/profileCheckpoint.cpp @@ -17,6 +17,7 @@ #include "runtime/os.hpp" #include "runtime/globals.hpp" #include "runtime/deoptimization.hpp" +#include "runtime/mutexLocker.hpp" #include "compiler/compilationPolicy.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerDefinitions.hpp" @@ -140,26 +141,36 @@ static bool read_symtab(FILE* in, GrowableArray& symbols, u4 expected) { } static bool write_classes(fileStream* out, const GrowableArray& classes) { + // MDOX format: [u1 loader_id][u4 loader_name_sym_id][u4 klass_sym_id] + const u4 NO_LOADER_NAME = 0xFFFFFFFF; for (int i = 0; i < classes.length(); i++) { const ProfileCheckpoint::Class& c = classes.at(i); if (!write_u1(out, (u1)c.loader)) return false; + u4 loader_name_id = (c.loader == ProfileCheckpoint::LoaderId::NAMED) ? c.loader_name.id : NO_LOADER_NAME; + if (!write_u4(out, loader_name_id)) return false; if (!write_u4(out, c.klass.id)) return false; } return true; } static bool read_classes(FILE* in, GrowableArray& classes, u4 expected) { + const u4 NO_LOADER_NAME = 0xFFFFFFFF; for (u4 ci = 0; ci < expected; ci++) { u1 loader_raw = 0; if (!read_u1(in, loader_raw)) { return false; } + u4 loader_name_id = NO_LOADER_NAME; + if (!read_u4(in, loader_name_id)) { + return false; + } u4 klass_id = 0; if (!read_u4(in, klass_id)) { return false; } ProfileCheckpoint::Class c; c.loader = (ProfileCheckpoint::LoaderId)loader_raw; + c.loader_name.id = loader_name_id; c.klass.id = klass_id; classes.append(c); } @@ -167,11 +178,15 @@ static bool read_classes(FILE* in, GrowableArray& clas } static bool write_record(fileStream* out, const ProfileCheckpoint::Record& r, const void* mdo_bytes, const ProfileCheckpoint::Fixup* fixups, const void* mc_bytes, const void* header_bytes) { + const u4 NO_LOADER_NAME = 0xFFFFFFFF; if (!write_u4(out, r.key.klass.id)) return false; if (!write_u4(out, r.key.name.id)) return false; if (!write_u4(out, r.key.sig.id)) return false; u1 loader = (u1)r.key.loader; if (!write_exact(out, &loader, sizeof(loader))) return false; + // MDOX: write loader_name_id after loader + u4 loader_name_id = (r.key.loader == ProfileCheckpoint::LoaderId::NAMED) ? r.key.loader_name.id : NO_LOADER_NAME; + if (!write_u4(out, loader_name_id)) return false; if (!write_exact(out, &r.comp_level, sizeof(r.comp_level))) return false; if (!write_u4(out, r.mdo_size)) return false; if (!write_u4(out, r.fixup_count)) return false; @@ -180,6 +195,9 @@ static bool write_record(fileStream* out, const ProfileCheckpoint::Record& r, co if (!write_u4(out, fixups[i].target.id)) return false; u1 fx_loader = (u1)fixups[i].loader; if (!write_u1(out, fx_loader)) return false; + // MDOX: write fixup loader_name_id + u4 fx_loader_name_id = (fixups[i].loader == ProfileCheckpoint::LoaderId::NAMED) ? fixups[i].loader_name.id : NO_LOADER_NAME; + if (!write_u4(out, fx_loader_name_id)) return false; } if (!write_exact(out, mdo_bytes, r.mdo_size)) return false; if (!write_u4(out, r.header_size)) return false; @@ -194,12 +212,15 @@ static bool write_record(fileStream* out, const ProfileCheckpoint::Record& r, co } static bool read_record(FILE* in, ProfileCheckpoint::Record& r, ProfileCheckpoint::Fixup*& fixups, char*& mdo_bytes, char*& mc_bytes, char*& header_bytes) { + const u4 NO_LOADER_NAME = 0xFFFFFFFF; if (!read_u4(in, r.key.klass.id)) return false; if (!read_u4(in, r.key.name.id)) return false; if (!read_u4(in, r.key.sig.id)) return false; u1 loader = 0; if (!read_exact(in, &loader, sizeof(loader))) return false; r.key.loader = (ProfileCheckpoint::LoaderId)loader; + r.key.loader_name.id = NO_LOADER_NAME; + if (!read_u4(in, r.key.loader_name.id)) return false; if (!read_exact(in, &r.comp_level, sizeof(r.comp_level))) return false; if (!read_u4(in, r.mdo_size)) return false; if (!read_u4(in, r.fixup_count)) return false; @@ -213,6 +234,8 @@ static bool read_record(FILE* in, ProfileCheckpoint::Record& r, ProfileCheckpoin u1 fx_loader = 0; if (!read_u1(in, fx_loader)) { os::free(fixups); return false; } fixups[i].loader = (ProfileCheckpoint::LoaderId)fx_loader; + fixups[i].loader_name.id = NO_LOADER_NAME; + if (!read_u4(in, fixups[i].loader_name.id)) { os::free(fixups); return false; } } } mdo_bytes = nullptr; @@ -323,6 +346,18 @@ static void print_mdo_header(MethodData* mdo, outputStream* st = tty) { // ============================================================================ // Symbolic resolution helpers (LoaderId / klass / method) // ============================================================================ + +// Get the stable loader name for a non-built-in, non-hidden class loader. +// Returns nullptr if the loader doesn't have an explicit name set. +// Caller needs ResourceMark. +static const char* get_loader_name_for_klass(InstanceKlass* ik) { + if (ik == nullptr || ik->is_hidden()) return nullptr; + ClassLoaderData* cld = ik->class_loader_data(); + if (cld == nullptr || cld->is_builtin_class_loader_data()) return nullptr; + // Use loader_name() which returns the explicit name or class name + return cld->loader_name(); +} + static ProfileCheckpoint::LoaderId loader_id_from_klass(InstanceKlass* ik) { if (ik != nullptr && ik->is_hidden()) { return ProfileCheckpoint::LoaderId::HIDDEN; @@ -331,15 +366,68 @@ static ProfileCheckpoint::LoaderId loader_id_from_klass(InstanceKlass* ik) { if (cld == nullptr || cld->is_boot_class_loader_data()) return ProfileCheckpoint::LoaderId::BOOT; if (cld->is_platform_class_loader_data()) return ProfileCheckpoint::LoaderId::PLATFORM; if (cld->is_system_class_loader_data()) return ProfileCheckpoint::LoaderId::SYSTEM; + // Non-built-in loader: use NAMED if we can get a name log_debug(compilation)("Non-builtin loader encountered: %s", cld->loader_name_and_id()); - return ProfileCheckpoint::LoaderId::UNDEFINED; + return ProfileCheckpoint::LoaderId::NAMED; +} + +// Look up a user-defined class loader by its name. +// Returns a Handle to the loader oop if found, or null Handle if not found or multiple matches. +static Handle loader_handle_by_name(const char* loader_name, TRAPS) { + if (loader_name == nullptr) return Handle(); + + oop candidate = nullptr; + int candidate_count = 0; + + { + MutexLocker ml(ClassLoaderDataGraph_lock, Mutex::_no_safepoint_check_flag); + + class FindLoaderByName : public CLDClosure { + const char* _name; + oop* _candidate; + int* _count; + public: + FindLoaderByName(const char* name, oop* candidate, int* count) + : _name(name), _candidate(candidate), _count(count) {} + void do_cld(ClassLoaderData* cld) override { + if (cld == nullptr) return; + if (cld->is_builtin_class_loader_data()) return; + // Skip CLDs dedicated to non-strong hidden classes. + if (cld->has_class_mirror_holder()) return; + oop loader = cld->class_loader_no_keepalive(); + if (loader == nullptr) return; + // Compare loader name + const char* cld_name = cld->loader_name(); + if (cld_name != nullptr && strcmp(cld_name, _name) == 0) { + (*_count)++; + if (*_candidate == nullptr) { + *_candidate = loader; + } + } + } + } finder(loader_name, &candidate, &candidate_count); + + ClassLoaderDataGraph::loaded_cld_do(&finder); + } + + log_debug(compilation)("loader_handle_by_name('%s'): found %d matches", loader_name, candidate_count); + if (candidate_count == 1 && candidate != nullptr) { + return Handle(THREAD, candidate); + } + if (candidate_count > 1) { + log_debug(compilation)("loader_handle_by_name('%s'): multiple loaders with same name, failing closed", loader_name); + } + return Handle(); } -static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, TRAPS) { +// Get a Handle to the class loader for resolution. +// For NAMED loaders, uses the loader_name to look up the loader. +static Handle loader_handle_from_loader(ProfileCheckpoint::LoaderId loader_id, const char* loader_name, TRAPS) { switch (loader_id) { case ProfileCheckpoint::LoaderId::BOOT: return Handle(); case ProfileCheckpoint::LoaderId::PLATFORM: return Handle(THREAD, SystemDictionary::java_platform_loader()); case ProfileCheckpoint::LoaderId::SYSTEM: return Handle(THREAD, SystemDictionary::java_system_loader()); + case ProfileCheckpoint::LoaderId::NAMED: return loader_handle_by_name(loader_name, THREAD); default: return Handle(); } } @@ -358,14 +446,14 @@ static const char* get_klassname_utf8(InstanceKlass* ik) { return ik->name()->as_utf8(); } -static InstanceKlass* resolve_klass_utf8(const char* name, ProfileCheckpoint::LoaderId loader_id, TRAPS) { +static InstanceKlass* resolve_klass_utf8(const char* name, ProfileCheckpoint::LoaderId loader_id, const char* loader_name, TRAPS) { if (name != nullptr && name[0] == '@') { log_debug(compilation)("resolving %s", name); InstanceKlass* hk = DynoLocatorScan::resolve_locator(name, THREAD); if (hk != nullptr) return hk; } Symbol* sym = SymbolTable::new_symbol(name); - Handle loader = loader_handle_from_loader(loader_id, THREAD); + Handle loader = loader_handle_from_loader(loader_id, loader_name, THREAD); Klass* k = SystemDictionary::resolve_or_fail(sym, loader, true, THREAD); if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; return nullptr; } return (k != nullptr && k->is_instance_klass()) ? InstanceKlass::cast(k) : nullptr; @@ -435,6 +523,21 @@ static void sanitize_type_entries(MethodData* mdo) { static void collect_type_fixups(MethodData* mdo, ProfileCheckpoint::SymtabBuilder& stb, GrowableArray& out_fixups) { + // Helper lambda to create a fixup with loader_name properly set + auto make_fixup = [&stb](InstanceKlass* ik, u4 offset) -> ProfileCheckpoint::Fixup { + ProfileCheckpoint::Fixup fx; + fx.offset_in_mdo = offset; + fx.target.id = stb.intern(get_klassname_utf8(ik)); + fx.loader = loader_id_from_klass(ik); + if (fx.loader == ProfileCheckpoint::LoaderId::NAMED) { + const char* lname = get_loader_name_for_klass(ik); + fx.loader_name.id = (lname != nullptr) ? stb.intern(lname) : 0xFFFFFFFF; + } else { + fx.loader_name.id = 0xFFFFFFFF; + } + return fx; + }; + for (ProfileData* pd = mdo->first_data(); mdo->is_valid(pd); pd = mdo->next_data(pd)) { if (pd->is_VirtualCallData() || pd->is_ReceiverTypeData()) { ReceiverTypeData* rtd = pd->as_ReceiverTypeData(); @@ -444,12 +547,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((pd->dp() + off_b) - (address)mdo))); } } } @@ -462,12 +560,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((pd->dp() + off_b) - (address)mdo))); } } } @@ -477,12 +570,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((pd->dp() + off_b) - (address)mdo))); } } } @@ -495,12 +583,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((pd->dp() + off_b) - (address)mdo))); } } } @@ -510,12 +593,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((pd->dp() + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((pd->dp() + off_b) - (address)mdo))); } } } @@ -528,12 +606,7 @@ static void collect_type_fixups(MethodData* mdo, Klass* k = TypeEntries::valid_klass(*cell); if (k != nullptr && k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - const char* cname = get_klassname_utf8(ik); - ProfileCheckpoint::Fixup fx; - fx.offset_in_mdo = (u4)((p_dp + off_b) - (address)mdo); - fx.target.id = stb.intern(cname); - fx.loader = loader_id_from_klass(ik); - out_fixups.append(fx); + out_fixups.append(make_fixup(ik, (u4)((p_dp + off_b) - (address)mdo))); } } } @@ -552,7 +625,14 @@ static void apply_fixups(MethodData* mdo, continue; } const char* cname = symtab.at((int)fx.target.id); - InstanceKlass* k = resolve_klass_utf8(cname, fx.loader, THREAD); + // Get loader name from symtab if it's a NAMED loader + const char* loader_name = nullptr; + if (fx.loader == ProfileCheckpoint::LoaderId::NAMED && fx.loader_name.id != 0xFFFFFFFF) { + if ((int)fx.loader_name.id >= 0 && (int)fx.loader_name.id < symtab.length()) { + loader_name = symtab.at((int)fx.loader_name.id); + } + } + InstanceKlass* k = resolve_klass_utf8(cname, fx.loader, loader_name, THREAD); if (k != nullptr) { address cell_addr = (address)mdo + fx.offset_in_mdo; intptr_t* cell = (intptr_t*)cell_addr; @@ -707,8 +787,15 @@ bool ProfileCheckpoint::Loader::install_record(const Record& rec, const char* kname = symtab.at((int)rec.key.klass.id); const char* mname = symtab.at((int)rec.key.name.id); const char* msig = symtab.at((int)rec.key.sig.id); + // Get loader name from symtab if it's a NAMED loader + const char* loader_name = nullptr; + if (rec.key.loader == LoaderId::NAMED && rec.key.loader_name.id != 0xFFFFFFFF) { + if ((int)rec.key.loader_name.id >= 0 && (int)rec.key.loader_name.id < symtab.length()) { + loader_name = symtab.at((int)rec.key.loader_name.id); + } + } - InstanceKlass* holder = resolve_klass_utf8(kname, rec.key.loader, THREAD); + InstanceKlass* holder = resolve_klass_utf8(kname, rec.key.loader, loader_name, THREAD); if (holder == nullptr) { log_debug(compilation)("MDO checkpoint: resolve class failed for %s", kname); return false; @@ -863,7 +950,14 @@ ProfileCheckpoint::Loader::LoadResult ProfileCheckpoint::Loader::load_from_file( continue; } const char* cname = symtab.at((int)cls.klass.id); - InstanceKlass* holder = resolve_klass_utf8(cname, cls.loader, THREAD); + // Get loader name from symtab if it's a NAMED loader + const char* loader_name = nullptr; + if (cls.loader == LoaderId::NAMED && cls.loader_name.id != 0xFFFFFFFF) { + if ((int)cls.loader_name.id >= 0 && (int)cls.loader_name.id < symtab.length()) { + loader_name = symtab.at((int)cls.loader_name.id); + } + } + InstanceKlass* holder = resolve_klass_utf8(cname, cls.loader, loader_name, THREAD); if (holder != nullptr) { holder->link_class(THREAD); if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } @@ -968,6 +1062,13 @@ class CollectClassesClosure : public KlassClosure { InstanceKlass* ik = InstanceKlass::cast(k); ProfileCheckpoint::Class c; c.loader = loader_id_from_klass(ik); + // For NAMED loaders, intern the loader name + if (c.loader == ProfileCheckpoint::LoaderId::NAMED) { + const char* lname = get_loader_name_for_klass(ik); + c.loader_name.id = (lname != nullptr) ? _stb->intern(lname) : 0xFFFFFFFF; + } else { + c.loader_name.id = 0xFFFFFFFF; + } c.klass.id = _stb->intern(get_klassname_utf8(ik)); _classes->append(c); } @@ -1077,6 +1178,13 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { stb.intern(rec_metas.at(ri).kname); stb.intern(rec_metas.at(ri).mname); stb.intern(rec_metas.at(ri).sig); + // Also intern loader names for NAMED loaders + Method* m = methods_with_mdo.at(ri); + LoaderId lid = loader_id_from_klass(m->method_holder()); + if (lid == LoaderId::NAMED) { + const char* lname = get_loader_name_for_klass(m->method_holder()); + if (lname != nullptr) stb.intern(lname); + } } stb.freeze(); @@ -1095,6 +1203,13 @@ void ProfileCheckpoint::dump_to_stream(fileStream* out) { Record rec; rec.key.loader = loader_id_from_klass(m->method_holder()); + // For NAMED loaders, set loader_name + if (rec.key.loader == ProfileCheckpoint::LoaderId::NAMED) { + const char* lname = get_loader_name_for_klass(m->method_holder()); + rec.key.loader_name.id = (lname != nullptr) ? stb.id_of(lname) : 0xFFFFFFFF; + } else { + rec.key.loader_name.id = 0xFFFFFFFF; + } rec.key.klass.id = stb.id_of(rec_meta.kname); rec.key.name.id = stb.id_of(rec_meta.mname); rec.key.sig.id = stb.id_of(rec_meta.sig); diff --git a/src/hotspot/share/services/profileCheckpoint.hpp b/src/hotspot/share/services/profileCheckpoint.hpp index 0ab6eea8ab1..984b282032b 100644 --- a/src/hotspot/share/services/profileCheckpoint.hpp +++ b/src/hotspot/share/services/profileCheckpoint.hpp @@ -10,6 +10,7 @@ class fileStream; class ProfileCheckpoint { public: // Binary format (File = [HEADER][SYMTAB][CLASSES][RECORDS]) + // // HEADER: // magic[4] = 'M','D','O','X' // pointer_size: u16 (e.g., 8) @@ -23,16 +24,20 @@ class ProfileCheckpoint { // [u32 len][len bytes utf8] // // CLASSES: repeated class_count times: - // [u1 loader_id][u4 klass_sym_id] + // [u1 loader_id][u4 loader_name_sym_id][u4 klass_sym_id] + // (loader_name_sym_id is 0xFFFFFFFF if loader != NAMED) // // RECORD: repeated rec_count times - // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, comp_level:u8, mdo_size:u32, - // fixup_count:u32, Fixup[fixup_count], [mdo_size bytes], header_size:u32, [header bytes], mc_size:u32, [mc bytes] + // klass_id:u32, name_id:u32, sig_id:u32, loader:u8, loader_name_id:u32, comp_level:u8, + // mdo_size:u32, fixup_count:u32, Fixup[fixup_count], [mdo_size bytes], + // header_size:u32, [header bytes], mc_size:u32, [mc bytes] // // Fixup: repeated fixup_count times - // [u4 offset_in_mdo][u4 target_sym_id][u1 loader_id] + // [u4 offset_in_mdo][u4 target_sym_id][u1 loader_id][u4 loader_name_sym_id] - enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED, HIDDEN }; + // LoaderId values: BOOT=0, PLATFORM=1, SYSTEM=2, UNDEFINED=3, HIDDEN=4, NAMED=5 + // NAMED is used for user-defined loaders with a stable name (ClassLoader.getName()) + enum class LoaderId : u1 { BOOT, PLATFORM, SYSTEM, UNDEFINED, HIDDEN, NAMED }; struct SymbolId { uint32_t id; }; @@ -40,6 +45,7 @@ class ProfileCheckpoint { uint32_t offset_in_mdo; SymbolId target; LoaderId loader; + SymbolId loader_name; // valid only if loader == NAMED; 0xFFFFFFFF otherwise }; struct ByteRange { uint64_t off; uint32_t size; }; @@ -56,6 +62,7 @@ class ProfileCheckpoint { struct MethodKey { LoaderId loader; + SymbolId loader_name; // valid only if loader == NAMED; 0xFFFFFFFF otherwise SymbolId klass; // "a/b/C" SymbolId name; // "foo" SymbolId sig; // "(I)Ljava/lang/String;" @@ -92,6 +99,7 @@ class ProfileCheckpoint { struct Class { LoaderId loader; + SymbolId loader_name; // valid only if loader == NAMED; 0xFFFFFFFF otherwise SymbolId klass; }; From 87adb9183ca291a44736138626b4502d80c3fe55 Mon Sep 17 00:00:00 2001 From: Daniel Zhou Date: Wed, 1 Apr 2026 11:24:56 -0700 Subject: [PATCH 19/23] phase 1 --- doc/portable-mdo-design.md | 870 +++++++++++++++++++++++++ src/hotspot/share/oops/portableMDO.hpp | 426 ++++++++++++ 2 files changed, 1296 insertions(+) create mode 100644 doc/portable-mdo-design.md create mode 100644 src/hotspot/share/oops/portableMDO.hpp diff --git a/doc/portable-mdo-design.md b/doc/portable-mdo-design.md new file mode 100644 index 00000000000..3546c604292 --- /dev/null +++ b/doc/portable-mdo-design.md @@ -0,0 +1,870 @@ +# Portable MDO Export/Import — Implementation Design + +## Overview + +Export `MethodData` (MDO) profiling information from one JVM instance and import it +into another instance of the same JDK version, enabling warmup-free optimized compilation. + +--- + +## Phase 1: Portable Serialization Format + +### 1.1 File-Level Structure + +``` +┌─────────────────────────────────────┐ +│ GlobalFileHeader │ +│ - magic: "JMDO" │ +│ - format_version: uint16 │ +│ - jdk_version_hash: uint32 │ +│ - pointer_size: uint8 (4 or 8) │ +│ - endianness: uint8 │ +│ - flags_snapshot: │ +│ TypeProfileWidth: uint16 │ +│ MethodProfileWidth: uint16 │ +│ BciProfileWidth: uint16 │ +│ ProfileTraps: bool │ +│ ProfileExceptionHandlers: bool │ +│ - klass_ref_table_offset: uint32 │ +│ - method_ref_table_offset: uint32 │ +│ - mdo_entry_count: uint32 │ +├─────────────────────────────────────┤ +│ KlassRefTable │ +│ Array of: │ +│ - utf8_length: uint16 │ +│ - class_name: utf8 bytes │ +│ - classloader_tag: uint8 │ +│ (0=boot, 1=platform, 2=app) │ +│ - is_hidden: bool (always false — │ +│ hidden classes are not stored) │ +├─────────────────────────────────────┤ +│ MethodRefTable │ +│ Array of: │ +│ - klass_ref_index: uint16 │ +│ - name_utf8_length: uint16 │ +│ - name: utf8 bytes │ +│ - sig_utf8_length: uint16 │ +│ - sig: utf8 bytes │ +├─────────────────────────────────────┤ +│ PortableMDOEntry[0] │ +│ PortableMDOEntry[1] │ +│ ... │ +│ PortableMDOEntry[N-1] │ +└─────────────────────────────────────┘ +``` + +### 1.2 PortableMDOEntry + +Each entry represents one method's complete profile. + +``` +┌──────────────────────────────────────────┐ +│ MethodIdentity │ +│ - klass_ref_index: uint16 │ +│ - method_name_utf8: length-prefixed │ +│ - method_sig_utf8: length-prefixed │ +│ - bytecode_fingerprint: uint32 (CRC32) │ +├──────────────────────────────────────────┤ +│ HeaderFields │ +│ - invocation_count: int32 │ +│ - backedge_count: int32 │ +│ - nof_decompiles: uint16 (capped) │ +│ - nof_overflow_recompiles: uint16 │ +│ - nof_overflow_traps: uint16 │ +│ - trap_hist: uint8[Reason_LIMIT] │ +│ - tenure_traps: uint16 │ +│ - num_loops: int16 │ +│ - num_blocks: int16 │ +│ - would_profile: uint8 │ +│ - arg_modified: uint8[] (ArgInfoData) │ +├──────────────────────────────────────────┤ +│ ProfileRecords │ +│ - record_count: uint16 │ +│ - records: PortableProfileRecord[] │ +├──────────────────────────────────────────┤ +│ ExtraDataRecords │ +│ - extra_count: uint16 │ +│ - extras: PortableExtraRecord[] │ +├──────────────────────────────────────────┤ +│ ParameterTypeData (optional) │ +│ - param_count: uint16 │ +│ - param_types: SymbolicTypeEntry[] │ +├──────────────────────────────────────────┤ +│ ExceptionHandlerData (optional) │ +│ - handler_count: uint16 │ +│ - handlers: { bci: uint16, │ +│ entered: bool }[] │ +└──────────────────────────────────────────┘ +``` + +### 1.3 PortableProfileRecord (per-BCI) + +Tagged union, discriminated by `tag` field: + +``` +Common header: + - tag: uint8 (matches DataLayout tag enum) + - bci: uint16 + - trap_state: uint32 + - flags: uint8 + +Tag-specific payloads: + +BitData (tag=1): + (no extra fields — all info is in flags/trap_state) + +CounterData (tag=2): + - count: int32 + +JumpData (tag=3): + - taken: uint32 + (displacement is NOT serialized — recomputed on import) + +ReceiverTypeData (tag=4): + - count: uint32 (overflow/total counter) + - num_rows: uint8 + - rows[]: + - klass_ref_index: int16 (-1 if null/discarded) + - count: uint32 + +VirtualCallData (tag=5): + (same layout as ReceiverTypeData) + +RetData (tag=6): + - count: uint32 + - num_rows: uint8 + - rows[]: + - bci: int16 + - count: uint32 + (displacement NOT serialized) + +BranchData (tag=7): + - taken: uint32 + - not_taken: uint32 + (displacement NOT serialized) + +MultiBranchData (tag=8): + - default_count: uint32 + - num_cases: uint16 + - cases[]: + - count: uint32 + (displacements NOT serialized) + +CallTypeData (tag=10): + - count: int32 + - num_args: uint8 + - args[]: + - stack_slot: uint16 + - type_klass_ref_index: int16 (-1 if null) + - null_seen: bool + - type_unknown: bool + - has_return: bool + - return_type: + - klass_ref_index: int16 + - null_seen: bool + - type_unknown: bool + +VirtualCallTypeData (tag=11): + (ReceiverTypeData fields + CallTypeData arg/return fields) + +ArgInfoData (tag=9): + (serialized in HeaderFields.arg_modified instead) +``` + +### 1.4 PortableExtraRecord + +``` +SpeculativeTrapData: + - bci: uint16 + - trap_state: uint32 + - method_ref_index: int16 (-1 to discard) + +BitData (stray trap): + - bci: uint16 + - trap_state: uint32 + - flags: uint8 +``` + +### 1.5 SymbolicTypeEntry (for ParametersTypeData) + +``` + - klass_ref_index: int16 (-1 if null) + - null_seen: bool + - type_unknown: bool + - stack_slot: uint16 +``` + +--- + +## Phase 2: MDO Exporter + +### New files: +- `src/hotspot/share/oops/portableMDO.hpp` — format structs, constants +- `src/hotspot/share/oops/portableMDO.cpp` — export/import implementation + +### 2.1 Bytecode Fingerprint + +```cpp +// In portableMDO.cpp +uint32_t PortableMDO::compute_bytecode_fingerprint(const Method* method) { + uint32_t crc = 0; + const address code_base = method->code_base(); + const int code_size = method->code_size(); + // CRC32 over raw bytecodes (including operands) + crc = ClassLoader::crc32(crc, (const char*)code_base, code_size); + return crc; +} +``` + +Uses the existing `ClassLoader::crc32()` utility already in the JDK. + +### 2.2 Klass* Serialization Logic + +``` +For each Klass* encountered in any profile record: + 1. If klass == nullptr → write klass_ref_index = -1 + 2. If klass->is_hidden() → write klass_ref_index = -1 + (discard hidden/lambda classes; preserve the row's count value + so the overflow counter remains accurate) + 3. Otherwise → look up or insert into KlassRefTable, + write the table index +``` + +### 2.3 Deopt History Decay on Export + +```cpp +void PortableMDO::export_compiler_counters( + const MethodData* mdo, PortableMDOHeaderFields* out, float decay) { + // Trap histogram: apply decay + for (uint i = 0; i < MethodData::trap_reason_limit(); i++) { + uint raw = mdo->trap_count(i); + out->trap_hist[i] = (uint8_t)MIN2((uint)(raw * decay), (uint)255); + } + // Cap decompile count at PerMethodRecompilationCutoff / 4 + out->nof_decompiles = MIN2(mdo->decompile_count(), + (uint)(PerMethodRecompilationCutoff / 4)); + // Reset overflow counters — less meaningful across runs + out->nof_overflow_recompiles = 0; + out->nof_overflow_traps = 0; +} +``` + +### 2.4 Escape Analysis Fields + +Do NOT export `_eflags`, `_arg_local`, `_arg_stack`, `_arg_returned`. +Write zeros. EA recomputes during the first C2 compilation in the target JVM. +Cost: one compilation without EA hints, negligible. + +### 2.5 Hidden Class Handling (invokedynamic / Lambda) + +During per-BCI export of ReceiverTypeData / VirtualCallData / TypeEntries: + +```cpp +int16_t PortableMDO::export_klass_ref(Klass* k, KlassRefTable* table) { + if (k == nullptr) return -1; + if (k->is_hidden()) { + // Lambda/anonymous class — non-deterministic name across runs. + // Discard the type, but the caller preserves the count. + return -1; + } + // Check loader is alive (concurrent unloading safety) + if (!k->is_loader_present_and_alive()) return -1; + // Add to or find in table + return table->add_or_find(k->name(), classloader_tag(k)); +} +``` + +### 2.6 Export Trigger + +Two modes: +- **Shutdown export:** VM_Operation at JVM exit, iterates `ClassLoaderDataGraph`, + exports all methods with mature MDOs. +- **On-demand export:** Callable via `jcmd VM.exportMDO ` + (diagnostic command, runs at safepoint). + +### 2.7 Maturity Filter + +Only export MDOs where `MethodData::is_mature()` returns true. +Immature profiles add noise and aren't worth persisting. + +--- + +## Phase 3: MDO Importer + +### 3.1 File Loading & Validation + +On VM startup (if `-XX:ImportMDOFile=` is set): +1. Memory-map the file +2. Validate `GlobalFileHeader`: + - Magic number matches + - Format version compatible + - JDK version hash matches `VM_Version::vm_build_id()` or similar + - Pointer size and endianness match current platform + - **Flag snapshot does NOT need to match** — we serialize at the logical + level, so different `TypeProfileWidth` is fine (import will create + the correct number of rows for the *current* VM's configuration) +3. Parse `KlassRefTable` and `MethodRefTable` into memory +4. Build a `HashMap` keyed by + `(class_name, method_name, method_sig)` for O(1) lookup + +### 3.2 Bytecode Fingerprint Validation + +On each import attempt (when `build_profiling_method_data` is called): +```cpp +bool PortableMDO::validate_fingerprint(const Method* method, + const PortableMDOEntry* entry) { + uint32_t current = compute_bytecode_fingerprint(method); + if (current != entry->bytecode_fingerprint) { + log_info(mdo, import)("Rejected MDO for %s: fingerprint mismatch " + "(expected 0x%08x, got 0x%08x)", + method->name_and_sig_as_C_string(), + entry->bytecode_fingerprint, current); + return false; + } + return true; +} +``` + +### 3.3 Klass* Resolution + +```cpp +Klass* PortableMDO::resolve_klass_ref(int16_t index, KlassRefTable* table) { + if (index < 0) return nullptr; // null or discarded hidden class + + KlassRefEntry* entry = table->at(index); + ClassLoaderData* loader = classloader_from_tag(entry->loader_tag); + + // Attempt resolution without triggering class loading + // (we don't want import to force-load classes) + Symbol* name = SymbolTable::probe(entry->name, entry->name_len); + if (name == nullptr) return nullptr; + + Klass* k = SystemDictionary::find_instance_klass( + Thread::current(), name, Handle(), Handle()); + + if (k == nullptr) { + log_debug(mdo, import)("Unresolved klass: %s (will re-profile)", + entry->name); + _unresolved_klass_count++; + } + return k; +} +``` + +Key: use `find_instance_klass` (lookup only) rather than `resolve_or_fail` +(which would trigger class loading). If a class isn't loaded yet, the +profile slot stays null and the interpreter re-profiles it naturally. + +### 3.4 Counter Normalization + +Three modes controlled by `-XX:MDOImportPolicy`: + +**`raw`** — Use counters as-is. Good for same-app training runs. + +**`scaled` (default)** — Scale to a configurable mature target: +```cpp +void PortableMDO::normalize_counters(PortableMDOEntry* entry, + int target_invocations) { + int original = entry->header.invocation_count; + if (original <= 0) original = 1; + double scale = (double)target_invocations / original; + + // Scale all per-BCI counters proportionally + for (int i = 0; i < entry->record_count; i++) { + PortableProfileRecord* rec = &entry->records[i]; + switch (rec->tag) { + case counter_data_tag: + rec->count = clamp_scale(rec->count, scale); + break; + case branch_data_tag: + rec->taken = clamp_scale(rec->taken, scale); + rec->not_taken = clamp_scale(rec->not_taken, scale); + break; + case receiver_type_data_tag: + case virtual_call_data_tag: + rec->overflow_count = clamp_scale(rec->overflow_count, scale); + for (int r = 0; r < rec->num_rows; r++) { + rec->rows[r].count = clamp_scale(rec->rows[r].count, scale); + } + break; + // ... other tags ... + } + } + entry->header.invocation_count = target_invocations; + entry->header.backedge_count = + clamp_scale(entry->header.backedge_count, scale); +} + +static int clamp_scale(int value, double scale) { + double result = value * scale; + return (int)MIN2(result, (double)INT_MAX); +} +``` + +**`binary`** — Collapse to hot/cold. All counts become either 0 or +`target_invocations`. Simplest; useful when only branching decisions matter. + +### 3.5 MDO Reconstruction (the core import routine) + +```cpp +MethodData* PortableMDO::reconstruct(const methodHandle& method, + PortableMDOEntry* entry, + TRAPS) { + // Step 1: Allocate MDO normally — this computes correct layout, + // displacements, and initializes all cells to zero. + ClassLoaderData* loader = method->method_holder()->class_loader_data(); + MethodData* mdo = MethodData::allocate(loader, method, CHECK_NULL); + + // Step 2: Patch header fields + patch_header(mdo, &entry->header); + + // Step 3: Walk per-BCI data in parallel — both the freshly allocated + // MDO (which has correct displacements) and the imported logical records + // (which have correct counters/types). + ProfileData* live_data = mdo->first_data(); + int import_idx = 0; + + while (mdo->is_valid(live_data) && import_idx < entry->record_count) { + PortableProfileRecord* rec = &entry->records[import_idx]; + + if (live_data->bci() == rec->bci && + live_data->data()->tag() == rec->tag) { + patch_profile_record(mdo, live_data, rec); + import_idx++; + } + // If BCIs don't match, the imported record may have been for a + // bytecode that no longer has profile data (flag change). + // Skip the live_data entry — it stays zero-initialized. + live_data = mdo->next_data(live_data); + } + + // Step 4: Patch extra data (trap entries) + patch_extra_data(mdo, entry); + + // Step 5: Patch parameter type data + if (entry->has_parameter_types && mdo->parameters_type_data() != nullptr) { + patch_parameter_types(mdo, entry); + } + + // Step 6: Patch exception handler data + if (entry->has_exception_handlers) { + patch_exception_handlers(mdo, entry); + } + + // Step 7: Fix up runtime state + mdo->post_import_fixup(); + + return mdo; +} + +void PortableMDO::patch_header(MethodData* mdo, + PortableMDOHeaderFields* hdr) { + // Compiler counters (with decay already applied during export) + mdo->set_trap_count_from_import(hdr->trap_hist); + mdo->set_decompile_count(hdr->nof_decompiles); + + // Counters — use imported values (already normalized) + mdo->invocation_counter()->set_count(hdr->invocation_count); + mdo->backedge_counter()->set_count(hdr->backedge_count); + mdo->set_invocation_counter_start(0); // reset baselines + mdo->set_backedge_counter_start(0); + + // Structural info + mdo->set_num_loops(hdr->num_loops); + mdo->set_num_blocks(hdr->num_blocks); + mdo->set_would_profile(hdr->would_profile); + + // Escape analysis — zeroed, recomputed by C2 + // (already zero from allocation, nothing to do) + + // ArgInfoData — patch modified-arg bits + ArgInfoData* args = mdo->arg_info(); + if (args != nullptr) { + for (int i = 0; i < MIN2(hdr->arg_modified_count, + args->number_of_args()); i++) { + args->set_arg_modified(i, hdr->arg_modified[i]); + } + } +} +``` + +### 3.6 Per-Record Patching + +```cpp +void PortableMDO::patch_profile_record(MethodData* mdo, + ProfileData* live, + PortableProfileRecord* rec) { + // Copy trap state (always safe — same bci) + live->set_trap_state(rec->trap_state); + + switch (rec->tag) { + case DataLayout::counter_data_tag: { + CounterData* cd = live->as_CounterData(); + cd->set_count(rec->count); + break; + } + case DataLayout::jump_data_tag: { + JumpData* jd = live->as_JumpData(); + jd->set_taken(rec->taken); + // displacement is ALREADY CORRECT from initialize() — don't touch it + break; + } + case DataLayout::branch_data_tag: { + BranchData* bd = live->as_BranchData(); + bd->set_taken(rec->taken); + bd->set_not_taken(rec->not_taken); + // displacement already correct + break; + } + case DataLayout::receiver_type_data_tag: + case DataLayout::virtual_call_data_tag: { + ReceiverTypeData* rtd = live->as_ReceiverTypeData(); + rtd->set_count(rec->overflow_count); + uint rows_to_patch = MIN2((uint)rec->num_rows, rtd->row_limit()); + for (uint r = 0; r < rows_to_patch; r++) { + Klass* k = resolve_klass_ref(rec->rows[r].klass_ref_index, + _klass_table); + rtd->set_receiver(r, k); + rtd->set_receiver_count(r, k != nullptr ? rec->rows[r].count : 0); + } + break; + } + case DataLayout::multi_branch_data_tag: { + MultiBranchData* mbd = live->as_MultiBranchData(); + mbd->set_default_count(rec->default_count); + for (int i = 0; i < rec->num_cases; i++) { + mbd->set_count_at(i, rec->cases[i].count); + // displacement already correct + } + break; + } + case DataLayout::ret_data_tag: { + RetData* rd = live->as_RetData(); + // RetData rows: just patch counts, displacements recomputed + break; + } + case DataLayout::call_type_data_tag: + case DataLayout::virtual_call_type_data_tag: { + // Patch call counter, argument types, return type + patch_call_type_data(live, rec); + break; + } + default: + // BitData, ArgInfoData — flags/trap_state already copied above + break; + } +} +``` + +### 3.7 Runtime State Fixup + +```cpp +void MethodData::post_import_fixup() { + // Recreate the extra data lock + _extra_data_lock = new Mutex(Mutex::nosafepoint, "MDOExtraData_lock"); + + // Clear JVMCI-specific state + JVMCI_ONLY(_failed_speculations = nullptr); + JVMCI_ONLY(_jvmci_ir_size = 0); + + // Reset hint to beginning + _hint_di = first_di(); +} +``` + +--- + +## Phase 4: JVM Integration + +### 4.1 Primary Import Hook (Option A — during class linking) + +Modify `Method::build_profiling_method_data()` in `method.cpp`: + +```cpp +void Method::build_profiling_method_data(const methodHandle& method, TRAPS) { + // Existing: check CDS training data + if (install_training_method_data(method)) { + return; + } + + // NEW: check for imported portable MDO + if (PortableMDO::has_import_data()) { + MethodData* imported = PortableMDO::try_import(method, THREAD); + if (imported != nullptr) { + if (Atomic::replace_if_null(&method->_method_data, imported)) { + log_info(mdo, import)("Installed imported MDO for %s", + method->name_and_sig_as_C_string()); + return; + } else { + // Someone else installed first — free ours + ClassLoaderData* ld = method->method_holder()->class_loader_data(); + MetadataFactory::free_metadata(ld, imported); + return; + } + } + // Import failed (fingerprint mismatch, etc.) — fall through + } + + // ... existing allocation path ... +} +``` + +This is safe because `build_profiling_method_data` is called before the +method's MDO is visible to interpreting threads. The `Atomic::replace_if_null` +CAS provides the same safety guarantee as the existing path. + +### 4.2 Late Import Path (Option C — for already-running methods) + +```cpp +bool PortableMDO::late_import(const methodHandle& method, TRAPS) { + PortableMDOEntry* entry = lookup(method); + if (entry == nullptr) return false; + + MethodData* new_mdo = reconstruct(method, entry, CHECK_false); + if (new_mdo == nullptr) return false; + + MethodData* old_mdo = method->method_data(); + if (old_mdo == nullptr) { + // Easy case: no existing MDO + if (!Atomic::replace_if_null(&method->_method_data, new_mdo)) { + MetadataFactory::free_metadata(..., new_mdo); + } + } else { + // Replace existing MDO. + // The interpreter caches mdp at method entry, so after the store: + // - New invocations see new_mdo + // - Existing activations safely finish with old_mdo + Atomic::store(&method->_method_data, new_mdo); + // old_mdo must be freed after all existing activations complete. + // Defer to next safepoint via ServiceThread or GC cycle. + DeferredMetadataFree::enqueue(old_mdo); + } + return true; +} +``` + +### 4.3 VM Flags + +``` +New product flags (in globals.hpp): + + product(ccstr, ExportMDOFile, nullptr, \ + "Export mature MDO profiles to this file on JVM shutdown") \ + + product(ccstr, ImportMDOFile, nullptr, \ + "Import MDO profiles from this file on JVM startup") \ + + product(intx, MDOImportScaleTarget, 10000, \ + "Target invocation count for scaled MDO import") \ + range(100, 1000000) \ + + product(uint, MDOImportDeoptDecayPercent, 50, \ + "Percentage to decay deopt trap counts on import") \ + range(0, 100) \ + + product(uint, MDOImportDecompileCap, 0, \ + "Max decompile count to import (0 = auto-cap at " \ + "PerMethodRecompilationCutoff/4)") \ + + develop(bool, MDOImportVerbose, false, \ + "Verbose logging for MDO import/export") \ +``` + +### 4.4 Export Trigger + +```cpp +// Register during VM initialization: +// In Threads::create_vm() or similar: +if (ExportMDOFile != nullptr) { + // Register a before_exit hook + register_before_exit_function(PortableMDO::export_all); +} + +// The export itself: +void PortableMDO::export_all() { + ResourceMark rm; + fileStream out(ExportMDOFile, "wb"); + if (!out.is_open()) { + log_error(mdo, export)("Failed to open %s for writing", ExportMDOFile); + return; + } + + int count = 0; + // Iterate all loaded classes and their methods + ClassLoaderDataGraph::classes_do([&](Klass* k) { + if (!k->is_instance_klass()) return; + InstanceKlass* ik = InstanceKlass::cast(k); + for (int i = 0; i < ik->methods()->length(); i++) { + Method* m = ik->methods()->at(i); + MethodData* mdo = m->method_data(); + if (mdo != nullptr && mdo->is_mature()) { + export_single_mdo(mdo, &out); + count++; + } + } + }); + log_info(mdo, export)("Exported %d mature MDO profiles to %s", + count, ExportMDOFile); +} +``` + +### 4.5 Import Trigger + +```cpp +// Called early in VM init, after SystemDictionary is ready: +void PortableMDO::initialize_import() { + if (ImportMDOFile == nullptr) return; + + _import_file = os::map_memory(ImportMDOFile, ...); + if (_import_file == nullptr) { + log_error(mdo, import)("Failed to mmap %s", ImportMDOFile); + return; + } + + if (!validate_global_header()) { + log_error(mdo, import)("Header validation failed for %s", ImportMDOFile); + os::unmap_memory(_import_file, ...); + _import_file = nullptr; + return; + } + + // Parse tables and build lookup map + parse_klass_ref_table(); + parse_method_ref_table(); + build_entry_lookup_map(); // HashMap + + log_info(mdo, import)("Loaded %d MDO profiles from %s", + _entry_count, ImportMDOFile); +} +``` + +The lookup table stays in memory. As methods are linked and +`build_profiling_method_data` is called, each method checks the +table for an available imported profile. This is lazy — no class loading +is forced, no upfront resolution. + +--- + +## Phase 5: Testing & Diagnostics + +### 5.1 Logging Tags + +``` +-Xlog:mdo+export=info — one line per exported method +-Xlog:mdo+export=debug — per-BCI record details +-Xlog:mdo+import=info — one line per imported method + rejection reasons +-Xlog:mdo+import=debug — per-BCI patching details, unresolved klasses +``` + +### 5.2 WhiteBox API + +```java +// In sun.hotspot.WhiteBox: +public native boolean exportMethodMDO(Executable method, String path); +public native boolean importMethodMDO(Executable method, String path); +public native int getMethodBytecodeFingerprint(Executable method); +public native boolean hasImportedMDO(Executable method); +``` + +### 5.3 gtest (C++ unit tests) + +File: `test/hotspot/gtest/oops/test_portableMDO.cpp` + +``` +TEST(PortableMDO, bytecode_fingerprint_stable) + - Compute fingerprint twice for same method → same result + - Redefine method → different result + +TEST(PortableMDO, export_import_round_trip) + - Create a method, build MDO, run interpreter to populate counters + - Export to buffer + - Allocate fresh MDO for same method + - Import from buffer + - Verify counters, receiver types, branch ratios match (within scaling) + +TEST(PortableMDO, hidden_class_discarded) + - Create MDO with a hidden-class receiver + - Export → verify hidden class not in KlassRefTable + - Import → receiver slot is null, overflow count preserved + +TEST(PortableMDO, fingerprint_mismatch_rejects) + - Export MDO for method M + - Redefine M (different bytecodes) + - Import → returns nullptr, log message emitted + +TEST(PortableMDO, counter_scaling) + - Export MDO with invocation_count=500 + - Import with MDOImportScaleTarget=10000 + - All counters scaled by 20x, ratios preserved within rounding + +TEST(PortableMDO, deopt_decay) + - Export MDO with trap_hist[Reason_class_check]=10, decompiles=8 + - Import with 50% decay + - trap_hist[Reason_class_check]=5, decompiles=min(4, cutoff/4) +``` + +### 5.4 jtreg Tests + +File: `test/hotspot/jtreg/runtime/PortableMDO/` + +``` +TestExportImportRoundTrip.java + - Run app with -XX:ExportMDOFile=profile.mdo + - Restart with -XX:ImportMDOFile=profile.mdo + - Verify method compiles at C2 earlier (check compilation log) + +TestStaleFingerprint.java + - Export profiles for version 1 of a class + - Load version 2 (different bytecodes) with import + - Verify stale profiles rejected (check log output) + +TestHiddenClassDiscard.java + - Lambda-heavy workload + - Export → import → verify no crashes, lambdas re-profile + +TestConcurrentImport.java + - Start threads executing a method + - Concurrently trigger late MDO import via WhiteBox + - Verify no crashes, no data corruption (run under -XX:+DeoptimizeALot) +``` + +### 5.5 Benchmarking + +``` +Metrics to collect (Renaissance / DaCapo): + - Time to first C2 compilation (with vs without import) + - Total compilation count in first 30 seconds + - Peak throughput (should be equivalent) + - Number of deoptimizations (imported deopt history should reduce these) + - Number of unresolved klass refs (expect <5% for typical apps) +``` + +--- + +## Implementation Order + +| Step | Description | Files Modified/Created | Depends On | +|------|------------|----------------------|-----------| +| 1 | Format structs & constants | NEW: `portableMDO.hpp` | — | +| 2 | Bytecode fingerprint | `portableMDO.cpp` | 1 | +| 3 | KlassRefTable + MethodRefTable | `portableMDO.cpp` | 1 | +| 4 | Per-record export (all tag types) | `portableMDO.cpp` | 1, 3 | +| 5 | Header export + deopt decay | `portableMDO.cpp` | 1 | +| 6 | Bulk export + file I/O | `portableMDO.cpp` | 4, 5 | +| 7 | VM flags | `globals.hpp` | — | +| 8 | Export trigger (shutdown hook) | `portableMDO.cpp`, `thread.cpp` | 6, 7 | +| 9 | File parser + validation | `portableMDO.cpp` | 1 | +| 10 | Klass resolution | `portableMDO.cpp` | 3, 9 | +| 11 | Counter normalization | `portableMDO.cpp` | 9 | +| 12 | MDO reconstruction + patching | `portableMDO.cpp`, `methodData.hpp` | 9, 10, 11 | +| 13 | Import hook in build_profiling_method_data | `method.cpp` | 12 | +| 14 | Import trigger (startup) | `portableMDO.cpp`, `init.cpp` | 9, 13 | +| 15 | Late import (Option C) | `portableMDO.cpp` | 12 | +| 16 | Logging | `portableMDO.cpp` | 6, 12 | +| 17 | WhiteBox API | `whitebox.cpp` | 6, 12 | +| 18 | gtests | NEW: `test_portableMDO.cpp` | 6, 12 | +| 19 | jtreg tests | NEW: `test/hotspot/jtreg/runtime/PortableMDO/` | 8, 14, 17 | +| 20 | Benchmarking | — | 14 | + +Recommended: implement steps 1–8 (export path) first as a standalone +deliverable, then 9–14 (import path), then 15–20 (late import, tests, polish). diff --git a/src/hotspot/share/oops/portableMDO.hpp b/src/hotspot/share/oops/portableMDO.hpp new file mode 100644 index 00000000000..afb89c0833e --- /dev/null +++ b/src/hotspot/share/oops/portableMDO.hpp @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +#ifndef SHARE_OOPS_PORTABLEMDO_HPP +#define SHARE_OOPS_PORTABLEMDO_HPP + +#include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" + +// PortableMDO — Portable MethodData serialization format +// +// This file defines the on-disk binary format for exporting and importing +// MethodData (MDO) profiling information across JVM instances of the same +// JDK version. +// +// Design principles: +// - All metaspace pointers (Klass*, Method*) are serialized as symbolic +// references (class name + classloader identity, method name + signature) +// stored in shared reference tables. +// - Profile data is serialized at the LOGICAL level (tag + bci + counters + +// symbolic refs), NOT as raw DataLayout binary cells. This decouples the +// format from flag-dependent layout (TypeProfileWidth, BciProfileWidth, etc.). +// - Data displacements (used by the interpreter's mdp) are NOT serialized. +// They are recomputed from bytecodes during import via the normal +// MethodData::initialize() + post_initialize() path. +// - Hidden/anonymous classes (lambdas, invokedynamic-generated classes) are +// intentionally discarded during export. The overflow count is preserved +// so the compiler still knows the site is hot. +// - Deoptimization history is exported with configurable decay to avoid +// overly conservative compilation in the target JVM. +// - Escape analysis fields are NOT exported (zeroed). EA recomputes cheaply +// during the first C2 compilation. + +// -------------------------------------------------------------------------- +// Constants +// -------------------------------------------------------------------------- + +class PortableMDO : AllStatic { +public: + // File magic number: "JMDO" in ASCII + static constexpr uint32_t MAGIC = 0x4A4D444F; + + // Format version — increment when the on-disk layout changes + static constexpr uint16_t FORMAT_VERSION = 1; + + // Maximum lengths for sanity checking during parsing + static constexpr uint16_t MAX_UTF8_LENGTH = 4096; + static constexpr uint16_t MAX_RECORD_COUNT = UINT16_MAX; + static constexpr uint16_t MAX_EXTRA_RECORD_COUNT = UINT16_MAX; + static constexpr uint16_t MAX_KLASS_REF_COUNT = UINT16_MAX; + static constexpr uint16_t MAX_METHOD_REF_COUNT = UINT16_MAX; + static constexpr uint32_t MAX_MDO_ENTRY_COUNT = 1000000; + static constexpr uint8_t MAX_RECEIVER_ROWS = 16; + static constexpr uint8_t MAX_RET_ROWS = 16; + static constexpr uint16_t MAX_SWITCH_CASES = 65535; + static constexpr uint8_t MAX_TYPE_ARGS = 255; + static constexpr uint16_t MAX_ARG_MODIFIED = 256; + static constexpr uint16_t MAX_EXCEPTION_HANDLERS = 65535; + static constexpr uint16_t MAX_PARAM_TYPES = 256; + + // Sentinel value for "no klass" or "discarded hidden class" + static constexpr int16_t NULL_KLASS_REF = -1; + + // Sentinel value for "no method" + static constexpr int16_t NULL_METHOD_REF = -1; + + // Maximum trap history array size — must accommodate + // Deoptimization::Reason_TRAP_HISTORY_LENGTH (and 2x for JVMCI OSR split). + // We use a fixed-size array in the portable format large enough for any + // configuration. The actual number of valid entries is recorded in the header. + static constexpr uint8_t MAX_TRAP_HIST_LENGTH = 64; +}; + +// -------------------------------------------------------------------------- +// Classloader identity tags +// -------------------------------------------------------------------------- + +enum class PortableClassLoaderTag : uint8_t { + BOOT = 0, // null / bootstrap classloader + PLATFORM = 1, // platform (ext) classloader + APP = 2, // application (system) classloader + CUSTOM = 3 // user-defined classloader (identified by name hash) +}; + +// -------------------------------------------------------------------------- +// Counter normalization policy +// -------------------------------------------------------------------------- + +enum class PortableMDOImportPolicy : uint8_t { + RAW = 0, // Use imported counters as-is + SCALED = 1, // Scale to a target invocation count, preserving ratios + BINARY = 2 // Collapse to hot/cold thresholds +}; + +// -------------------------------------------------------------------------- +// Extra data record types +// -------------------------------------------------------------------------- + +enum class PortableExtraTag : uint8_t { + BIT_DATA = 0, // Stray trap at a BCI without dedicated profile data + SPECULATIVE_TRAP = 1 // SpeculativeTrapData — failed type speculation record +}; + +// -------------------------------------------------------------------------- +// Global file header (written once at the start of the file) +// -------------------------------------------------------------------------- + +struct PortableMDOFileHeader { + uint32_t magic; // Must be PortableMDO::MAGIC + uint16_t format_version; // Must be PortableMDO::FORMAT_VERSION + uint16_t _padding0; + + // JDK identity — used to reject profiles from a different build + uint32_t jdk_version_hash; // Hash of JDK version string + + // Platform characteristics + uint8_t pointer_size; // sizeof(void*): 4 or 8 + uint8_t endianness; // 0 = little-endian, 1 = big-endian + uint16_t _padding1; + + // VM flag snapshot at export time. + // These are recorded for diagnostics. Because the format is logical + // (not binary DataLayout cells), different flag values between export + // and import are handled gracefully — rows are capped to the importing + // VM's configured width. + uint16_t type_profile_width; // TypeProfileWidth at export + uint16_t method_profile_width; // MethodProfileWidth at export + uint16_t bci_profile_width; // BciProfileWidth at export + uint8_t profile_traps; // ProfileTraps at export (0 or 1) + uint8_t profile_exception_handlers; // ProfileExceptionHandlers at export + + // Table offsets (byte offsets from start of file) + uint32_t klass_ref_table_offset; + uint32_t klass_ref_count; + uint32_t method_ref_table_offset; + uint32_t method_ref_count; + + // MDO entries + uint32_t mdo_entries_offset; + uint32_t mdo_entry_count; + + // Trap history dimension — actual number of valid entries in trap_hist arrays + uint8_t trap_hist_length; // Deoptimization::Reason_TRAP_HISTORY_LENGTH + uint8_t _padding2[3]; +}; + +// -------------------------------------------------------------------------- +// Klass reference table entry +// +// Symbolic representation of a Klass*. Hidden classes are never stored; +// they are discarded at export time and the referencing profile slot is +// set to NULL_KLASS_REF. +// -------------------------------------------------------------------------- + +struct PortableKlassRefEntry { + PortableClassLoaderTag loader_tag; + uint8_t _padding0; + uint16_t name_length; // Length of class name in bytes (modified UTF-8) + // Followed by: uint8_t name[name_length] + // Name is in internal form: "java/util/HashMap" +}; + +// -------------------------------------------------------------------------- +// Method reference table entry +// +// Symbolic representation of a Method*. Used by SpeculativeTrapData +// to identify the compilation root that caused the failed speculation. +// -------------------------------------------------------------------------- + +struct PortableMethodRefEntry { + uint16_t klass_ref_index; // Index into KlassRefTable for the holder + uint16_t name_length; // Method name length + uint16_t sig_length; // Method signature length + uint16_t _padding0; + // Followed by: uint8_t name[name_length] + // Followed by: uint8_t sig[sig_length] +}; + +// -------------------------------------------------------------------------- +// Per-method MDO entry header +// -------------------------------------------------------------------------- + +struct PortableMDOMethodIdentity { + uint16_t klass_ref_index; // Index into KlassRefTable for the holder + uint16_t name_length; // Method name length (modified UTF-8) + uint16_t sig_length; // Method signature length (modified UTF-8) + uint16_t _padding0; + uint32_t bytecode_fingerprint; // CRC32 over the method's bytecodes + // Followed by: uint8_t name[name_length] + // Followed by: uint8_t sig[sig_length] +}; + +// -------------------------------------------------------------------------- +// MDO header fields — scalar profiling metadata for the whole method +// -------------------------------------------------------------------------- + +struct PortableMDOHeaderFields { + // Invocation / backedge counters (already normalized/scaled at export) + int32_t invocation_count; + int32_t backedge_count; + + // Compiler counters (with decay applied at export) + uint16_t nof_decompiles; + uint16_t nof_overflow_recompiles; + uint16_t nof_overflow_traps; + uint16_t tenure_traps; + + // Trap histogram — per deoptimization reason counts (with decay) + // Only trap_hist_length entries are valid (from PortableMDOFileHeader). + uint8_t trap_hist[PortableMDO::MAX_TRAP_HIST_LENGTH]; + + // Structural info (computed by C1) + int16_t num_loops; + int16_t num_blocks; + + // Would this method benefit from profiling? + // 0 = unknown, 1 = no_profile, 2 = profile + uint8_t would_profile; + uint8_t _padding0[3]; + + // ArgInfoData — per-argument modified flags + uint16_t arg_modified_count; + // Followed by: uint8_t arg_modified[arg_modified_count] (after this struct) +}; + +// -------------------------------------------------------------------------- +// Symbolic type entry — used for ParametersTypeData and CallTypeData +// argument/return type profiling +// -------------------------------------------------------------------------- + +struct PortableSymbolicTypeEntry { + int16_t klass_ref_index; // Index into KlassRefTable, or NULL_KLASS_REF + uint16_t stack_slot; // Stack slot number (for arguments) + uint8_t null_seen; // Whether a null reference was observed + uint8_t type_unknown; // Whether conflicting types were seen + uint16_t _padding0; +}; + +// -------------------------------------------------------------------------- +// Exception handler coverage entry +// -------------------------------------------------------------------------- + +struct PortableExceptionHandlerEntry { + uint16_t handler_bci; // BCI of the exception handler + uint8_t entered; // Whether this handler was entered (0 or 1) + uint8_t _padding0; +}; + +// -------------------------------------------------------------------------- +// Receiver type row — used by ReceiverTypeData and VirtualCallData +// -------------------------------------------------------------------------- + +struct PortableReceiverRow { + int16_t klass_ref_index; // Index into KlassRefTable, or NULL_KLASS_REF + uint16_t _padding0; + uint32_t count; // Number of times this receiver type was seen +}; + +// -------------------------------------------------------------------------- +// Ret data row — used by RetData for jsr/ret profiling +// -------------------------------------------------------------------------- + +struct PortableRetRow { + int16_t target_bci; // Target BCI, or -1 if unused + uint16_t _padding0; + uint32_t count; // Number of times this target was taken + // Note: displacement is NOT stored — recomputed on import +}; + +// -------------------------------------------------------------------------- +// Per-BCI profile record header +// +// Each record starts with this common header, followed by tag-specific +// payload data. The tag values match DataLayout::tag enum. +// -------------------------------------------------------------------------- + +struct PortableProfileRecordHeader { + uint8_t tag; // DataLayout tag enum value + uint8_t flags; // DataLayout flags byte + uint16_t bci; // Bytecode index + uint32_t trap_state; // 32-bit trap state from DataLayout header +}; + +// Tag-specific payload structs follow the header in the serialized stream. +// The tag field determines which payload is present. + +// BitData payload (tag = bit_data_tag = 1) +// No additional fields — all info is in flags and trap_state. +struct PortableBitDataPayload { + // intentionally empty +}; + +// CounterData payload (tag = counter_data_tag = 2) +struct PortableCounterDataPayload { + int32_t count; +}; + +// JumpData payload (tag = jump_data_tag = 3) +// Displacement is NOT stored — recomputed on import. +struct PortableJumpDataPayload { + uint32_t taken; +}; + +// ReceiverTypeData payload (tag = receiver_type_data_tag = 4) +// Also used for VirtualCallData (tag = virtual_call_data_tag = 5) +struct PortableReceiverTypeDataPayload { + uint32_t count; // Overflow / total counter + uint8_t num_rows; // Number of (klass, count) rows + uint8_t _padding0[3]; + // Followed by: PortableReceiverRow rows[num_rows] +}; + +// RetData payload (tag = ret_data_tag = 6) +struct PortableRetDataPayload { + uint32_t count; // Total ret execution count + uint8_t num_rows; // Number of (bci, count) target rows + uint8_t _padding0[3]; + // Followed by: PortableRetRow rows[num_rows] +}; + +// BranchData payload (tag = branch_data_tag = 7) +// Displacement is NOT stored — recomputed on import. +struct PortableBranchDataPayload { + uint32_t taken; + uint32_t not_taken; +}; + +// MultiBranchData payload (tag = multi_branch_data_tag = 8) +// Displacements are NOT stored — recomputed on import. +struct PortableMultiBranchDataPayload { + uint32_t default_count; + uint16_t num_cases; + uint16_t _padding0; + // Followed by: uint32_t case_counts[num_cases] +}; + +// CallTypeData payload (tag = call_type_data_tag = 10) +struct PortableCallTypeDataPayload { + int32_t count; // Call counter + uint8_t num_args; // Number of profiled argument types + uint8_t has_return; // Whether return type is profiled (0 or 1) + uint16_t _padding0; + // Followed by: PortableSymbolicTypeEntry args[num_args] + // Followed by: PortableSymbolicTypeEntry return_type (if has_return) +}; + +// VirtualCallTypeData payload (tag = virtual_call_type_data_tag = 11) +// Combines ReceiverTypeData rows with CallTypeData argument/return types. +struct PortableVirtualCallTypeDataPayload { + uint32_t count; // Overflow / total counter + uint8_t num_receiver_rows; // Number of receiver (klass, count) rows + uint8_t num_args; // Number of profiled argument types + uint8_t has_return; // Whether return type is profiled (0 or 1) + uint8_t _padding0; + // Followed by: PortableReceiverRow receiver_rows[num_receiver_rows] + // Followed by: PortableSymbolicTypeEntry args[num_args] + // Followed by: PortableSymbolicTypeEntry return_type (if has_return) +}; + +// -------------------------------------------------------------------------- +// Extra data records (from the extra_data section of the MDO) +// -------------------------------------------------------------------------- + +struct PortableExtraRecordHeader { + PortableExtraTag tag; + uint8_t flags; + uint16_t bci; + uint32_t trap_state; +}; + +// SpeculativeTrapData extra record payload +struct PortableSpeculativeTrapPayload { + int16_t method_ref_index; // Index into MethodRefTable, or NULL_METHOD_REF + uint16_t _padding0; +}; + +// BitData extra record payload (stray trap) +// No additional fields beyond the header. + +// -------------------------------------------------------------------------- +// Complete per-method MDO entry (variable length, serialized sequentially) +// +// On-disk layout of a single PortableMDOEntry: +// +// PortableMDOMethodIdentity (+ inline name/sig bytes) +// PortableMDOHeaderFields (+ inline arg_modified bytes) +// uint16_t record_count +// uint16_t extra_record_count +// uint16_t param_type_count +// uint16_t exception_handler_count +// [record_count x (PortableProfileRecordHeader + tag-specific payload)] +// [extra_record_count x (PortableExtraRecordHeader + tag-specific payload)] +// [param_type_count x PortableSymbolicTypeEntry] +// [exception_handler_count x PortableExceptionHandlerEntry] +// +// -------------------------------------------------------------------------- + +struct PortableMDOEntryCounts { + uint16_t record_count; // Number of per-BCI profile records + uint16_t extra_record_count; // Number of extra data records + uint16_t param_type_count; // Number of parameter type entries + uint16_t exception_handler_count; // Number of exception handler entries +}; + +#endif // SHARE_OOPS_PORTABLEMDO_HPP From 640a177e92bd87a3c467f1a18aa013b64db95dcf Mon Sep 17 00:00:00 2001 From: Daniel Zhou Date: Wed, 1 Apr 2026 11:41:15 -0700 Subject: [PATCH 20/23] phase 2 --- src/hotspot/share/oops/portableMDO.cpp | 844 +++++++++++++++++++++++++ src/hotspot/share/oops/portableMDO.hpp | 5 + 2 files changed, 849 insertions(+) create mode 100644 src/hotspot/share/oops/portableMDO.cpp diff --git a/src/hotspot/share/oops/portableMDO.cpp b/src/hotspot/share/oops/portableMDO.cpp new file mode 100644 index 00000000000..d7d6b074932 --- /dev/null +++ b/src/hotspot/share/oops/portableMDO.cpp @@ -0,0 +1,844 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +#include "classfile/classLoader.hpp" +#include "classfile/classLoaderDataGraph.hpp" +#include "classfile/symbolTable.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.hpp" +#include "oops/method.hpp" +#include "oops/methodData.hpp" +#include "oops/portableMDO.hpp" +#include "runtime/globals.hpp" +#include "runtime/os.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/vm_version.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/resourceHash.hpp" + +// -------------------------------------------------------------------------- +// PortableMDOWriter — binary stream writer +// -------------------------------------------------------------------------- + +class PortableMDOWriter : public StackObj { + FILE* _file; + bool _error; + size_t _bytes_written; + +public: + PortableMDOWriter(FILE* file) : _file(file), _error(false), _bytes_written(0) {} + + bool error() const { return _error; } + size_t bytes_written() const { return _bytes_written; } + + void write_raw(const void* data, size_t len) { + if (_error) return; + size_t written = fwrite(data, 1, len, _file); + if (written != len) { + _error = true; + } + _bytes_written += written; + } + + void write_u1(uint8_t v) { write_raw(&v, sizeof(v)); } + void write_u2(uint16_t v) { write_raw(&v, sizeof(v)); } + void write_u4(uint32_t v) { write_raw(&v, sizeof(v)); } + void write_i2(int16_t v) { write_raw(&v, sizeof(v)); } + void write_i4(int32_t v) { write_raw(&v, sizeof(v)); } + + void write_struct(const void* s, size_t len) { write_raw(s, len); } + + void write_utf8(const char* str, uint16_t len) { + write_u2(len); + write_raw(str, len); + } + + // Pad to 4-byte alignment + void align4() { + size_t rem = _bytes_written % 4; + if (rem != 0) { + uint8_t pad[4] = {0}; + write_raw(pad, 4 - rem); + } + } + + size_t position() const { return _bytes_written; } + + // Seek back to a position and write, then return to current position + void patch_u4_at(size_t offset, uint32_t value) { + if (_error) return; + long current = ftell(_file); + if (fseek(_file, (long)offset, SEEK_SET) != 0) { + _error = true; + return; + } + fwrite(&value, sizeof(value), 1, _file); + if (fseek(_file, current, SEEK_SET) != 0) { + _error = true; + } + } +}; + +// -------------------------------------------------------------------------- +// Reference table builders +// -------------------------------------------------------------------------- + +class KlassRefTableBuilder : public StackObj { + struct Entry { + const Symbol* name; + PortableClassLoaderTag loader_tag; + }; + + GrowableArray _entries; + + // Simple linear search is fine — tables are small (hundreds of entries max) + int find(const Symbol* name, PortableClassLoaderTag tag) const { + for (int i = 0; i < _entries.length(); i++) { + if (_entries.at(i).name == name && _entries.at(i).loader_tag == tag) { + return i; + } + } + return -1; + } + +public: + KlassRefTableBuilder() : _entries() {} + + static PortableClassLoaderTag classloader_tag(const Klass* k) { + ClassLoaderData* cld = k->class_loader_data(); + // is_the_null_class_loader_data() covers the boot classloader + if (cld == nullptr || cld->is_the_null_class_loader_data()) { + return PortableClassLoaderTag::BOOT; + } + if (cld->is_platform_class_loader_data()) { + return PortableClassLoaderTag::PLATFORM; + } + if (cld->is_system_class_loader_data()) { + return PortableClassLoaderTag::APP; + } + return PortableClassLoaderTag::CUSTOM; + } + + // Returns index into table, or PortableMDO::NULL_KLASS_REF if discarded. + int16_t add_or_discard(const Klass* k) { + if (k == nullptr) return PortableMDO::NULL_KLASS_REF; + if (k->is_hidden()) return PortableMDO::NULL_KLASS_REF; + if (k->class_loader_data() == nullptr || !k->class_loader_data()->is_alive()) { + return PortableMDO::NULL_KLASS_REF; + } + + PortableClassLoaderTag tag = classloader_tag(k); + const Symbol* name = k->name(); + + int idx = find(name, tag); + if (idx >= 0) return (int16_t)idx; + + if (_entries.length() >= PortableMDO::MAX_KLASS_REF_COUNT) { + return PortableMDO::NULL_KLASS_REF; + } + + Entry e; + e.name = name; + e.loader_tag = tag; + idx = _entries.length(); + _entries.append(e); + return (int16_t)idx; + } + + int length() const { return _entries.length(); } + + void write_to(PortableMDOWriter* writer) const { + for (int i = 0; i < _entries.length(); i++) { + const Entry& e = _entries.at(i); + PortableKlassRefEntry hdr; + hdr.loader_tag = e.loader_tag; + hdr._padding0 = 0; + hdr.name_length = (uint16_t)e.name->utf8_length(); + writer->write_struct(&hdr, sizeof(hdr)); + writer->write_raw((const char*)e.name->bytes(), hdr.name_length); + writer->align4(); + } + } +}; + +class MethodRefTableBuilder : public StackObj { + struct Entry { + int16_t klass_ref_index; + const Symbol* name; + const Symbol* sig; + }; + + GrowableArray _entries; + + int find(int16_t klass_idx, const Symbol* name, const Symbol* sig) const { + for (int i = 0; i < _entries.length(); i++) { + if (_entries.at(i).klass_ref_index == klass_idx && + _entries.at(i).name == name && + _entries.at(i).sig == sig) { + return i; + } + } + return -1; + } + +public: + MethodRefTableBuilder() : _entries() {} + + int16_t add_or_discard(const Method* m, KlassRefTableBuilder* klass_table) { + if (m == nullptr) return PortableMDO::NULL_METHOD_REF; + + int16_t klass_idx = klass_table->add_or_discard(m->method_holder()); + if (klass_idx == PortableMDO::NULL_KLASS_REF) { + return PortableMDO::NULL_METHOD_REF; + } + + const Symbol* name = m->name(); + const Symbol* sig = m->signature(); + + int idx = find(klass_idx, name, sig); + if (idx >= 0) return (int16_t)idx; + + if (_entries.length() >= PortableMDO::MAX_METHOD_REF_COUNT) { + return PortableMDO::NULL_METHOD_REF; + } + + Entry e; + e.klass_ref_index = klass_idx; + e.name = name; + e.sig = sig; + idx = _entries.length(); + _entries.append(e); + return (int16_t)idx; + } + + int length() const { return _entries.length(); } + + void write_to(PortableMDOWriter* writer) const { + for (int i = 0; i < _entries.length(); i++) { + const Entry& e = _entries.at(i); + PortableMethodRefEntry hdr; + hdr.klass_ref_index = (uint16_t)e.klass_ref_index; + hdr.name_length = (uint16_t)e.name->utf8_length(); + hdr.sig_length = (uint16_t)e.sig->utf8_length(); + hdr._padding0 = 0; + writer->write_struct(&hdr, sizeof(hdr)); + writer->write_raw((const char*)e.name->bytes(), hdr.name_length); + writer->write_raw((const char*)e.sig->bytes(), hdr.sig_length); + writer->align4(); + } + } +}; + +// -------------------------------------------------------------------------- +// Bytecode fingerprint +// -------------------------------------------------------------------------- + +static uint32_t compute_bytecode_fingerprint(const Method* method) { + int code_size = method->code_size(); + if (code_size == 0) return 0; + address code_base = method->constMethod()->code_base(); + return (uint32_t)ClassLoader::crc32(0, (const char*)code_base, code_size); +} + +// -------------------------------------------------------------------------- +// Type entry export helpers +// -------------------------------------------------------------------------- + +static void export_symbolic_type_entry(PortableMDOWriter* writer, + intptr_t raw_type, + uint16_t stack_slot, + KlassRefTableBuilder* klass_table) { + PortableSymbolicTypeEntry entry; + entry.stack_slot = stack_slot; + entry.null_seen = TypeEntries::was_null_seen(raw_type) ? 1 : 0; + entry.type_unknown = TypeEntries::is_type_unknown(raw_type) ? 1 : 0; + entry._padding0 = 0; + + Klass* k = TypeEntries::valid_klass(raw_type); + entry.klass_ref_index = klass_table->add_or_discard(k); + + writer->write_struct(&entry, sizeof(entry)); +} + +static void export_type_stack_slot_entries(PortableMDOWriter* writer, + const TypeStackSlotEntries* args, + KlassRefTableBuilder* klass_table) { + for (int i = 0; i < args->number_of_entries(); i++) { + export_symbolic_type_entry(writer, args->type(i), + (uint16_t)args->stack_slot(i), klass_table); + } +} + +static void export_return_type_entry(PortableMDOWriter* writer, + const ReturnTypeEntry* ret, + KlassRefTableBuilder* klass_table) { + export_symbolic_type_entry(writer, ret->type(), 0, klass_table); +} + +// -------------------------------------------------------------------------- +// Per-BCI profile record export +// -------------------------------------------------------------------------- + +static void export_profile_record(PortableMDOWriter* writer, + ProfileData* data, + KlassRefTableBuilder* klass_table) { + // Write common header + DataLayout* dl = (DataLayout*)data->dp(); + PortableProfileRecordHeader hdr; + hdr.tag = dl->tag(); + hdr.flags = dl->flags(); + hdr.bci = (uint16_t)data->bci(); + hdr.trap_state = data->trap_state(); + writer->write_struct(&hdr, sizeof(hdr)); + + switch (hdr.tag) { + case DataLayout::bit_data_tag: { + // No payload beyond header + break; + } + + case DataLayout::counter_data_tag: { + CounterData* cd = data->as_CounterData(); + PortableCounterDataPayload payload; + payload.count = (int32_t)cd->count(); + writer->write_struct(&payload, sizeof(payload)); + break; + } + + case DataLayout::jump_data_tag: { + JumpData* jd = data->as_JumpData(); + PortableJumpDataPayload payload; + payload.taken = jd->taken(); + writer->write_struct(&payload, sizeof(payload)); + break; + } + + case DataLayout::receiver_type_data_tag: + case DataLayout::virtual_call_data_tag: { + ReceiverTypeData* rtd = data->as_ReceiverTypeData(); + uint rows = rtd->row_limit(); + uint actual_rows = 0; + // Count non-null rows (but we write all rows for positional stability) + for (uint r = 0; r < rows; r++) { + if (rtd->receiver(r) != nullptr) actual_rows++; + } + (void)actual_rows; // used only for diagnostics + + PortableReceiverTypeDataPayload payload; + payload.count = rtd->count(); + payload.num_rows = (uint8_t)rows; + payload._padding0[0] = payload._padding0[1] = payload._padding0[2] = 0; + writer->write_struct(&payload, sizeof(payload)); + + for (uint r = 0; r < rows; r++) { + PortableReceiverRow row; + Klass* k = rtd->receiver(r); + row.klass_ref_index = klass_table->add_or_discard(k); + row._padding0 = 0; + row.count = rtd->receiver_count(r); + // If klass was discarded (hidden class), zero the per-row count + // but preserve the overflow count in the payload header + if (k != nullptr && row.klass_ref_index == PortableMDO::NULL_KLASS_REF) { + row.count = 0; + } + writer->write_struct(&row, sizeof(row)); + } + + // If this is VirtualCallTypeData, also export arg/return types + if (hdr.tag == DataLayout::virtual_call_type_data_tag) { + // Handled below in virtual_call_type_data_tag case + } + break; + } + + case DataLayout::ret_data_tag: { + RetData* rd = data->as_RetData(); + PortableRetDataPayload payload; + payload.count = rd->count(); + uint rows = RetData::row_limit(); + payload.num_rows = (uint8_t)rows; + payload._padding0[0] = payload._padding0[1] = payload._padding0[2] = 0; + writer->write_struct(&payload, sizeof(payload)); + + for (uint r = 0; r < rows; r++) { + PortableRetRow row; + row.target_bci = (int16_t)rd->bci(r); + row._padding0 = 0; + row.count = rd->bci_count(r); + writer->write_struct(&row, sizeof(row)); + } + break; + } + + case DataLayout::branch_data_tag: { + BranchData* bd = data->as_BranchData(); + PortableBranchDataPayload payload; + payload.taken = bd->taken(); + payload.not_taken = bd->not_taken(); + writer->write_struct(&payload, sizeof(payload)); + break; + } + + case DataLayout::multi_branch_data_tag: { + MultiBranchData* mbd = data->as_MultiBranchData(); + int ncases = mbd->number_of_cases(); + PortableMultiBranchDataPayload payload; + payload.default_count = mbd->default_count(); + payload.num_cases = (uint16_t)ncases; + payload._padding0 = 0; + writer->write_struct(&payload, sizeof(payload)); + + for (int i = 0; i < ncases; i++) { + uint32_t cnt = mbd->count_at(i); + writer->write_u4(cnt); + } + break; + } + + case DataLayout::call_type_data_tag: { + CallTypeData* ctd = data->as_CallTypeData(); + PortableCallTypeDataPayload payload; + payload.count = (int32_t)ctd->count(); + payload.num_args = ctd->has_arguments() ? (uint8_t)ctd->number_of_arguments() : 0; + payload.has_return = ctd->has_return() ? 1 : 0; + payload._padding0 = 0; + writer->write_struct(&payload, sizeof(payload)); + + if (ctd->has_arguments()) { + export_type_stack_slot_entries(writer, ctd->args(), klass_table); + } + if (ctd->has_return()) { + export_return_type_entry(writer, ctd->ret(), klass_table); + } + break; + } + + case DataLayout::virtual_call_type_data_tag: { + VirtualCallTypeData* vctd = data->as_VirtualCallTypeData(); + + // Receiver type portion + uint rows = vctd->row_limit(); + PortableVirtualCallTypeDataPayload payload; + payload.count = vctd->count(); + payload.num_receiver_rows = (uint8_t)rows; + payload.num_args = vctd->has_arguments() ? (uint8_t)vctd->number_of_arguments() : 0; + payload.has_return = vctd->has_return() ? 1 : 0; + payload._padding0 = 0; + writer->write_struct(&payload, sizeof(payload)); + + for (uint r = 0; r < rows; r++) { + PortableReceiverRow row; + Klass* k = vctd->receiver(r); + row.klass_ref_index = klass_table->add_or_discard(k); + row._padding0 = 0; + row.count = vctd->receiver_count(r); + if (k != nullptr && row.klass_ref_index == PortableMDO::NULL_KLASS_REF) { + row.count = 0; + } + writer->write_struct(&row, sizeof(row)); + } + + if (vctd->has_arguments()) { + export_type_stack_slot_entries(writer, vctd->args(), klass_table); + } + if (vctd->has_return()) { + export_return_type_entry(writer, vctd->ret(), klass_table); + } + break; + } + + case DataLayout::parameters_type_data_tag: { + // Parameters are handled separately in export_parameters + // This tag shouldn't appear in the main data walk + break; + } + + default: { + // Unknown tag — skip (write nothing beyond the header) + break; + } + } +} + +// -------------------------------------------------------------------------- +// Extra data export +// -------------------------------------------------------------------------- + +static int export_extra_data(PortableMDOWriter* writer, + MethodData* mdo, + KlassRefTableBuilder* klass_table, + MethodRefTableBuilder* method_table) { + int count = 0; + Mutex* lock = mdo->extra_data_lock(); + MutexLocker ml(lock, Mutex::_no_safepoint_check_flag); + + DataLayout* dp = mdo->extra_data_base(); + DataLayout* end = mdo->extra_data_limit(); + + for (; dp < end; dp = MethodData::next_extra(dp)) { + if (dp->tag() == DataLayout::no_tag || + dp->tag() == DataLayout::arg_info_data_tag) { + break; + } + + PortableExtraRecordHeader ehdr; + ehdr.bci = (uint16_t)dp->bci(); + ehdr.trap_state = dp->trap_state(); + ehdr.flags = dp->flags(); + + if (dp->tag() == DataLayout::speculative_trap_data_tag) { + SpeculativeTrapData* data = new SpeculativeTrapData(dp); + Method* m = data->method(); + int16_t method_idx = method_table->add_or_discard(m, klass_table); + + ehdr.tag = PortableExtraTag::SPECULATIVE_TRAP; + writer->write_struct(&ehdr, sizeof(ehdr)); + + PortableSpeculativeTrapPayload payload; + payload.method_ref_index = method_idx; + payload._padding0 = 0; + writer->write_struct(&payload, sizeof(payload)); + count++; + } else if (dp->tag() == DataLayout::bit_data_tag) { + ehdr.tag = PortableExtraTag::BIT_DATA; + writer->write_struct(&ehdr, sizeof(ehdr)); + count++; + } + } + return count; +} + +// -------------------------------------------------------------------------- +// Parameters type data export +// -------------------------------------------------------------------------- + +static int export_parameters(PortableMDOWriter* writer, + MethodData* mdo, + KlassRefTableBuilder* klass_table) { + ParametersTypeData* params = mdo->parameters_type_data(); + if (params == nullptr) return 0; + + const TypeStackSlotEntries* entries = params->parameters(); + int count = params->number_of_parameters(); + for (int i = 0; i < count; i++) { + intptr_t raw_type = entries->type(i); + uint16_t stack_slot = (uint16_t)entries->stack_slot(i); + export_symbolic_type_entry(writer, raw_type, stack_slot, klass_table); + } + return count; +} + +// -------------------------------------------------------------------------- +// Exception handler data export +// -------------------------------------------------------------------------- + +static int export_exception_handlers(PortableMDOWriter* writer, + MethodData* mdo) { + DataLayout* dp = mdo->exception_handler_data_base(); + DataLayout* end = mdo->exception_handler_data_limit(); + // Each exception handler entry is a fixed-size BitData + int entry_size = DataLayout::compute_size_in_bytes(BitData::static_cell_count()); + int count = 0; + while (dp < end) { + BitData data(dp); + PortableExceptionHandlerEntry entry; + entry.handler_bci = (uint16_t)dp->bci(); + entry.entered = data.exception_handler_entered() ? 1 : 0; + entry._padding0 = 0; + writer->write_struct(&entry, sizeof(entry)); + count++; + dp = (DataLayout*)((address)dp + entry_size); + } + return count; +} + +// -------------------------------------------------------------------------- +// ArgInfoData export +// -------------------------------------------------------------------------- + +static int export_arg_modified(PortableMDOWriter* writer, + MethodData* mdo) { + int count = mdo->method()->size_of_parameters(); + if (count == 0) return 0; + + for (int i = 0; i < count; i++) { + uint8_t modified = (uint8_t)mdo->arg_modified(i); + writer->write_u1(modified); + } + writer->align4(); + return count; +} + +// -------------------------------------------------------------------------- +// Header fields export +// -------------------------------------------------------------------------- + +static void export_header_fields(PortableMDOWriter* writer, + MethodData* mdo, + float deopt_decay) { + PortableMDOHeaderFields hdr; + memset(&hdr, 0, sizeof(hdr)); + + // Counters + hdr.invocation_count = (int32_t)mdo->invocation_count(); + hdr.backedge_count = (int32_t)mdo->backedge_count(); + + // Compiler counters with decay + uint raw_decompiles = mdo->decompile_count(); + hdr.nof_decompiles = (uint16_t)MIN2((uint)(raw_decompiles * deopt_decay), + (uint)UINT16_MAX); + hdr.nof_overflow_recompiles = 0; // reset — less meaningful across runs + hdr.nof_overflow_traps = 0; // reset + hdr.tenure_traps = (uint16_t)MIN2(mdo->tenure_traps(), (uint)UINT16_MAX); + + // Trap histogram with decay + for (uint i = 0; i < MethodData::trap_reason_limit(); i++) { + if (i >= PortableMDO::MAX_TRAP_HIST_LENGTH) break; + uint raw = mdo->trap_count(i); + if (raw == (uint)-1) { + // Saturated — apply decay to max + hdr.trap_hist[i] = (uint8_t)MIN2((uint)(255 * deopt_decay), (uint)255); + } else { + hdr.trap_hist[i] = (uint8_t)MIN2((uint)(raw * deopt_decay), (uint)255); + } + } + + // Structural info + hdr.num_loops = (int16_t)mdo->num_loops(); + hdr.num_blocks = (int16_t)mdo->num_blocks(); + hdr.would_profile = mdo->would_profile() ? 2 : 1; + + // Escape analysis fields intentionally zeroed — recomputed by C2 + + // arg_modified_count is written separately after this struct + hdr.arg_modified_count = (uint16_t)mdo->method()->size_of_parameters(); + + writer->write_struct(&hdr, sizeof(hdr)); +} + +// -------------------------------------------------------------------------- +// Single MDO entry export +// -------------------------------------------------------------------------- + +static bool export_single_mdo(PortableMDOWriter* writer, + MethodData* mdo, + KlassRefTableBuilder* klass_table, + MethodRefTableBuilder* method_table, + float deopt_decay) { + ResourceMark rm; + Method* method = mdo->method(); + + // Write method identity + PortableMDOMethodIdentity identity; + identity.klass_ref_index = (uint16_t)klass_table->add_or_discard(method->method_holder()); + const Symbol* mname = method->name(); + const Symbol* msig = method->signature(); + identity.name_length = (uint16_t)mname->utf8_length(); + identity.sig_length = (uint16_t)msig->utf8_length(); + identity._padding0 = 0; + identity.bytecode_fingerprint = compute_bytecode_fingerprint(method); + writer->write_struct(&identity, sizeof(identity)); + writer->write_raw((const char*)mname->bytes(), identity.name_length); + writer->write_raw((const char*)msig->bytes(), identity.sig_length); + writer->align4(); + + // Write header fields + export_header_fields(writer, mdo, deopt_decay); + + // Write arg_modified bytes inline after header + int arg_count = export_arg_modified(writer, mdo); + (void)arg_count; + + // We need to write counts before the records, but we don't know them yet. + // Reserve space for the counts struct and patch it later. + size_t counts_offset = writer->position(); + PortableMDOEntryCounts counts; + memset(&counts, 0, sizeof(counts)); + writer->write_struct(&counts, sizeof(counts)); + + // Write per-BCI profile records + uint16_t record_count = 0; + for (ProfileData* data = mdo->first_data(); + mdo->is_valid(data); + data = mdo->next_data(data)) { + export_profile_record(writer, data, klass_table); + record_count++; + } + + // Write extra data records + uint16_t extra_count = (uint16_t)export_extra_data(writer, mdo, klass_table, method_table); + + // Write parameter types + uint16_t param_count = (uint16_t)export_parameters(writer, mdo, klass_table); + + // Write exception handler data + uint16_t handler_count = (uint16_t)export_exception_handlers(writer, mdo); + + // Patch the counts + counts.record_count = record_count; + counts.extra_record_count = extra_count; + counts.param_type_count = param_count; + counts.exception_handler_count = handler_count; + writer->patch_u4_at(counts_offset, *(uint32_t*)&counts); + writer->patch_u4_at(counts_offset + 4, *((uint32_t*)&counts + 1)); + + return !writer->error(); +} + +// -------------------------------------------------------------------------- +// JDK version hash +// -------------------------------------------------------------------------- + +static uint32_t compute_jdk_version_hash() { + const char* version = VM_Version::internal_vm_info_string(); + if (version == nullptr) return 0; + return (uint32_t)ClassLoader::crc32(0, version, (int)strlen(version)); +} + +// -------------------------------------------------------------------------- +// MDO collection helper +// -------------------------------------------------------------------------- + +// File-scope list pointer used by the methods_do callback below. +// Safe without synchronization because export runs at a safepoint +// where only the VM thread is active. +static GrowableArray* _collect_mdo_list = nullptr; + +static void collect_mature_mdo_callback(Method* m) { + MethodData* mdo = m->method_data(); + if (mdo != nullptr && mdo->is_mature()) { + _collect_mdo_list->append(mdo); + } +} + +static void collect_mature_mdos(GrowableArray* list) { + assert_at_safepoint(); + _collect_mdo_list = list; + ClassLoaderDataGraph::methods_do(collect_mature_mdo_callback); + _collect_mdo_list = nullptr; +} + +// -------------------------------------------------------------------------- +// Bulk export — public entry point +// -------------------------------------------------------------------------- + +bool PortableMDO::export_all_to_file(const char* filepath, float deopt_decay) { + assert_at_safepoint(); + ResourceMark rm; + + FILE* file = os::fopen(filepath, "wb"); + if (file == nullptr) { + log_error(aot, training)("PortableMDO: failed to open %s for writing", filepath); + return false; + } + + PortableMDOWriter writer(file); + KlassRefTableBuilder klass_table; + MethodRefTableBuilder method_table; + + // Write a placeholder file header — we'll patch offsets at the end + size_t header_offset = writer.position(); + PortableMDOFileHeader file_header; + memset(&file_header, 0, sizeof(file_header)); + file_header.magic = PortableMDO::MAGIC; + file_header.format_version = PortableMDO::FORMAT_VERSION; + file_header.jdk_version_hash = compute_jdk_version_hash(); + file_header.pointer_size = (uint8_t)sizeof(void*); +#ifdef VM_LITTLE_ENDIAN + file_header.endianness = 0; +#else + file_header.endianness = 1; +#endif + file_header.type_profile_width = (uint16_t)TypeProfileWidth; + file_header.method_profile_width = 0; // placeholder — not currently a tunable + file_header.bci_profile_width = (uint16_t)BciProfileWidth; + file_header.profile_traps = ProfileTraps ? 1 : 0; + file_header.profile_exception_handlers = ProfileExceptionHandlers ? 1 : 0; + file_header.trap_hist_length = (uint8_t)MethodData::trap_reason_limit(); + writer.write_struct(&file_header, sizeof(file_header)); + + // Collect all mature MDOs into a temporary list (to avoid holding + // classloader locks while doing I/O) + GrowableArray mdo_list; + collect_mature_mdos(&mdo_list); + + // Phase 1: Export all MDO entries. This populates the klass/method ref + // tables as a side effect. + size_t mdo_entries_offset = writer.position(); + int exported_count = 0; + for (int i = 0; i < mdo_list.length(); i++) { + MethodData* mdo = mdo_list.at(i); + // Double-check the method is still alive + if (mdo->method() == nullptr) continue; + if (export_single_mdo(&writer, mdo, &klass_table, &method_table, deopt_decay)) { + exported_count++; + } + if (writer.error()) break; + } + + if (writer.error()) { + fclose(file); + log_error(aot, training)("PortableMDO: I/O error writing %s", filepath); + return false; + } + + // Phase 2: Write klass ref table + size_t klass_table_offset = writer.position(); + klass_table.write_to(&writer); + + // Phase 3: Write method ref table + size_t method_table_offset = writer.position(); + method_table.write_to(&writer); + + // Phase 4: Patch the file header with correct offsets and counts + file_header.klass_ref_table_offset = (uint32_t)klass_table_offset; + file_header.klass_ref_count = (uint32_t)klass_table.length(); + file_header.method_ref_table_offset = (uint32_t)method_table_offset; + file_header.method_ref_count = (uint32_t)method_table.length(); + file_header.mdo_entries_offset = (uint32_t)mdo_entries_offset; + file_header.mdo_entry_count = (uint32_t)exported_count; + + // Rewrite the header at offset 0 + if (fseek(file, (long)header_offset, SEEK_SET) != 0) { + fclose(file); + log_error(aot, training)("PortableMDO: failed to seek in %s", filepath); + return false; + } + if (fwrite(&file_header, sizeof(file_header), 1, file) != 1) { + fclose(file); + log_error(aot, training)("PortableMDO: failed to rewrite header in %s", filepath); + return false; + } + + fclose(file); + + log_info(aot, training)("PortableMDO: exported %d mature MDO profiles to %s " + "(%d klass refs, %d method refs, %zu bytes)", + exported_count, filepath, + klass_table.length(), method_table.length(), + writer.bytes_written()); + return true; +} diff --git a/src/hotspot/share/oops/portableMDO.hpp b/src/hotspot/share/oops/portableMDO.hpp index afb89c0833e..4d9d0e1c708 100644 --- a/src/hotspot/share/oops/portableMDO.hpp +++ b/src/hotspot/share/oops/portableMDO.hpp @@ -90,6 +90,11 @@ class PortableMDO : AllStatic { // We use a fixed-size array in the portable format large enough for any // configuration. The actual number of valid entries is recorded in the header. static constexpr uint8_t MAX_TRAP_HIST_LENGTH = 64; + + // Export all mature MDO profiles to a binary file. + // deopt_decay: multiplier (0.0–1.0) applied to deoptimization counts to + // avoid overly conservative compilation in the target JVM. + static bool export_all_to_file(const char* filepath, float deopt_decay = 0.5f); }; // -------------------------------------------------------------------------- From 5ed53d751d01fc9f843bea693dd33bdf25d98f1d Mon Sep 17 00:00:00 2001 From: Daniel Zhou Date: Tue, 7 Apr 2026 01:24:53 -0700 Subject: [PATCH 21/23] finished --- src/hotspot/share/oops/method.cpp | 16 + src/hotspot/share/oops/portableMDO.cpp | 1416 ++++++++++++++++- src/hotspot/share/oops/portableMDO.hpp | 26 + src/hotspot/share/runtime/globals.hpp | 10 + src/hotspot/share/runtime/java.cpp | 7 + src/hotspot/share/runtime/threads.cpp | 4 + src/hotspot/share/runtime/vmOperation.hpp | 3 +- test/hotspot/gtest/oops/test_portableMDO.cpp | 549 +++++++ .../profiling/TestPortableMDORoundTrip.java | 144 ++ 9 files changed, 2166 insertions(+), 9 deletions(-) create mode 100644 test/hotspot/gtest/oops/test_portableMDO.cpp create mode 100644 test/hotspot/jtreg/compiler/profiling/TestPortableMDORoundTrip.java diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index d13458dfa93..77790d20141 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -56,6 +56,7 @@ #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" #include "oops/methodData.hpp" +#include "oops/portableMDO.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" @@ -638,6 +639,21 @@ void Method::build_profiling_method_data(const methodHandle& method, TRAPS) { if (install_training_method_data(method)) { return; } + // Try to import a pre-populated MDO from the portable MDO file. + if (PortableMDO::has_import_data()) { + MethodData* imported = PortableMDO::try_import(method, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + if (imported != nullptr) { + if (!Atomic::replace_if_null(&method->_method_data, imported)) { + ClassLoaderData* ld = method->method_holder()->class_loader_data(); + MetadataFactory::free_metadata(ld, imported); + } + return; + } + } + // Do not profile the method if metaspace has hit an OOM previously // allocating profiling data. Callers clear pending exception so don't // add one here. diff --git a/src/hotspot/share/oops/portableMDO.cpp b/src/hotspot/share/oops/portableMDO.cpp index d7d6b074932..3f386b28664 100644 --- a/src/hotspot/share/oops/portableMDO.cpp +++ b/src/hotspot/share/oops/portableMDO.cpp @@ -25,16 +25,23 @@ #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" #include "logging/log.hpp" +#include "memory/allocation.hpp" +#include "memory/metadataFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.hpp" #include "oops/method.hpp" #include "oops/methodData.hpp" #include "oops/portableMDO.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" +#include "runtime/handles.inline.hpp" #include "runtime/os.hpp" #include "runtime/safepoint.hpp" +#include "runtime/vmOperation.hpp" +#include "runtime/vmThread.hpp" #include "runtime/vm_version.hpp" #include "utilities/growableArray.hpp" #include "utilities/resourceHash.hpp" @@ -100,6 +107,21 @@ class PortableMDOWriter : public StackObj { _error = true; } } + + void patch_struct_at(size_t offset, const void* data, size_t len) { + if (_error) return; + long current = ftell(_file); + if (fseek(_file, (long)offset, SEEK_SET) != 0) { + _error = true; + return; + } + if (fwrite(data, 1, len, _file) != len) { + _error = true; + } + if (fseek(_file, current, SEEK_SET) != 0) { + _error = true; + } + } }; // -------------------------------------------------------------------------- @@ -145,6 +167,12 @@ class KlassRefTableBuilder : public StackObj { // Returns index into table, or PortableMDO::NULL_KLASS_REF if discarded. int16_t add_or_discard(const Klass* k) { if (k == nullptr) return PortableMDO::NULL_KLASS_REF; + // ReceiverTypeData::receiver() returns raw cell values cast to Klass*. + // Some cells may contain small non-pointer values (residual data from + // cleared slots, or tagged intptr values). Reject anything that cannot + // be a valid metaspace pointer. + if ((uintptr_t)k < (uintptr_t)os::vm_page_size()) return PortableMDO::NULL_KLASS_REF; + if (((uintptr_t)k & (alignof(Klass) - 1)) != 0) return PortableMDO::NULL_KLASS_REF; if (k->is_hidden()) return PortableMDO::NULL_KLASS_REF; if (k->class_loader_data() == nullptr || !k->class_loader_data()->is_alive()) { return PortableMDO::NULL_KLASS_REF; @@ -256,7 +284,7 @@ class MethodRefTableBuilder : public StackObj { // Bytecode fingerprint // -------------------------------------------------------------------------- -static uint32_t compute_bytecode_fingerprint(const Method* method) { +uint32_t PortableMDO::compute_bytecode_fingerprint(const Method* method) { int code_size = method->code_size(); if (code_size == 0) return 0; address code_base = method->constMethod()->code_base(); @@ -510,8 +538,8 @@ static int export_extra_data(PortableMDOWriter* writer, ehdr.flags = dp->flags(); if (dp->tag() == DataLayout::speculative_trap_data_tag) { - SpeculativeTrapData* data = new SpeculativeTrapData(dp); - Method* m = data->method(); + SpeculativeTrapData data(dp); + Method* m = data.method(); int16_t method_idx = method_table->add_or_discard(m, klass_table); ehdr.tag = PortableExtraTag::SPECULATIVE_TRAP; @@ -579,13 +607,34 @@ static int export_exception_handlers(PortableMDOWriter* writer, // ArgInfoData export // -------------------------------------------------------------------------- +// Walk extra data to find ArgInfoData. Acquires the extra data lock. +// Returns the DataLayout* for the ArgInfoData entry, or nullptr if not found. +// Caller constructs the ArgInfoData wrapper on the stack. +static DataLayout* find_arg_info_layout(MethodData* mdo) { + Mutex* lock = mdo->extra_data_lock(); + MutexLocker ml(lock, Mutex::_no_safepoint_check_flag); + DataLayout* dp = mdo->extra_data_base(); + DataLayout* end = mdo->args_data_limit(); + for (; dp < end; dp = MethodData::next_extra(dp)) { + if (dp->tag() == DataLayout::arg_info_data_tag) { + return dp; + } + } + return nullptr; +} + static int export_arg_modified(PortableMDOWriter* writer, MethodData* mdo) { int count = mdo->method()->size_of_parameters(); if (count == 0) return 0; + DataLayout* arg_dl = find_arg_info_layout(mdo); for (int i = 0; i < count; i++) { - uint8_t modified = (uint8_t)mdo->arg_modified(i); + uint8_t modified = 0; + if (arg_dl != nullptr) { + ArgInfoData args(arg_dl); + modified = (uint8_t)args.arg_modified(i); + } writer->write_u1(modified); } writer->align4(); @@ -648,7 +697,9 @@ static bool export_single_mdo(PortableMDOWriter* writer, KlassRefTableBuilder* klass_table, MethodRefTableBuilder* method_table, float deopt_decay) { - ResourceMark rm; + // NOTE: no ResourceMark here — the caller's ResourceMark (in + // export_all_to_file) must stay alive so that the GrowableArrays + // backing klass_table and method_table are not freed prematurely. Method* method = mdo->method(); // Write method identity @@ -659,7 +710,7 @@ static bool export_single_mdo(PortableMDOWriter* writer, identity.name_length = (uint16_t)mname->utf8_length(); identity.sig_length = (uint16_t)msig->utf8_length(); identity._padding0 = 0; - identity.bytecode_fingerprint = compute_bytecode_fingerprint(method); + identity.bytecode_fingerprint = PortableMDO::compute_bytecode_fingerprint(method); writer->write_struct(&identity, sizeof(identity)); writer->write_raw((const char*)mname->bytes(), identity.name_length); writer->write_raw((const char*)msig->bytes(), identity.sig_length); @@ -702,8 +753,7 @@ static bool export_single_mdo(PortableMDOWriter* writer, counts.extra_record_count = extra_count; counts.param_type_count = param_count; counts.exception_handler_count = handler_count; - writer->patch_u4_at(counts_offset, *(uint32_t*)&counts); - writer->patch_u4_at(counts_offset + 4, *((uint32_t*)&counts + 1)); + writer->patch_struct_at(counts_offset, &counts, sizeof(counts)); return !writer->error(); } @@ -842,3 +892,1353 @@ bool PortableMDO::export_all_to_file(const char* filepath, float deopt_decay) { writer.bytes_written()); return true; } + +// -------------------------------------------------------------------------- +// VM_ExportMDO — safepoint VM operation for shutdown export +// -------------------------------------------------------------------------- + +class VM_ExportMDO : public VM_Operation { + const char* _filepath; + float _deopt_decay; + bool _result; +public: + VM_ExportMDO(const char* filepath, float deopt_decay) + : _filepath(filepath), _deopt_decay(deopt_decay), _result(false) {} + VMOp_Type type() const { return VMOp_ExportMDO; } + void doit() { + _result = PortableMDO::export_all_to_file(_filepath, _deopt_decay); + } + bool result() const { return _result; } +}; + +void PortableMDO::export_on_shutdown() { + if (ExportMDOFile == nullptr) return; + float decay = (float)MDOExportDeoptDecayPercent / 100.0f; + VM_ExportMDO op(ExportMDOFile, decay); + VMThread::execute(&op); +} + +// ========================================================================== +// +// PHASE 3: MDO IMPORTER +// +// ========================================================================== + +// -------------------------------------------------------------------------- +// PortableMDOReader — binary stream reader with bounds checking +// -------------------------------------------------------------------------- + +class PortableMDOReader : public StackObj { + const uint8_t* _base; + const uint8_t* _end; + const uint8_t* _cursor; + bool _error; + +public: + PortableMDOReader(const uint8_t* base, size_t length) + : _base(base), _end(base + length), _cursor(base), _error(false) {} + + bool error() const { return _error; } + size_t position() const { return (size_t)(_cursor - _base); } + size_t remaining() const { return _error ? 0 : (size_t)(_end - _cursor); } + + void set_position(size_t offset) { + if (offset > (size_t)(_end - _base)) { + _error = true; + return; + } + _cursor = _base + offset; + } + + bool read_raw(void* dest, size_t len) { + if (_error || _cursor + len > _end) { + _error = true; + return false; + } + memcpy(dest, _cursor, len); + _cursor += len; + return true; + } + + uint8_t read_u1() { + uint8_t v = 0; + read_raw(&v, sizeof(v)); + return v; + } + + uint16_t read_u2() { + uint16_t v = 0; + read_raw(&v, sizeof(v)); + return v; + } + + uint32_t read_u4() { + uint32_t v = 0; + read_raw(&v, sizeof(v)); + return v; + } + + int16_t read_i2() { + int16_t v = 0; + read_raw(&v, sizeof(v)); + return v; + } + + int32_t read_i4() { + int32_t v = 0; + read_raw(&v, sizeof(v)); + return v; + } + + bool read_struct(void* dest, size_t len) { + return read_raw(dest, len); + } + + // Skip len bytes + void skip(size_t len) { + if (_error || _cursor + len > _end) { + _error = true; + return; + } + _cursor += len; + } + + // Read raw bytes without copying (returns pointer into the buffer) + const uint8_t* read_bytes(size_t len) { + if (_error || _cursor + len > _end) { + _error = true; + return nullptr; + } + const uint8_t* p = _cursor; + _cursor += len; + return p; + } + + // Advance to 4-byte alignment + void align4() { + size_t pos = position(); + size_t rem = pos % 4; + if (rem != 0) { + skip(4 - rem); + } + } +}; + +// -------------------------------------------------------------------------- +// Imported reference table entries (in-memory parsed form) +// -------------------------------------------------------------------------- + +struct ImportedKlassRef : public CHeapObj { + Symbol* name; // Interned symbol — has refcount + PortableClassLoaderTag loader_tag; + Klass* resolved; // Cached resolution result, or nullptr + bool resolution_attempted; + + ImportedKlassRef() : name(nullptr), loader_tag(PortableClassLoaderTag::BOOT), + resolved(nullptr), resolution_attempted(false) {} +}; + +struct ImportedMethodRef : public CHeapObj { + int16_t klass_ref_index; + Symbol* name; + Symbol* sig; + + ImportedMethodRef() : klass_ref_index(-1), name(nullptr), sig(nullptr) {} +}; + +// -------------------------------------------------------------------------- +// Lookup key for the MDO entry map: (klass_name, method_name, method_sig) +// -------------------------------------------------------------------------- + +struct MDOLookupKey { + const Symbol* klass_name; + const Symbol* method_name; + const Symbol* method_sig; + + bool operator==(const MDOLookupKey& other) const { + return klass_name == other.klass_name && + method_name == other.method_name && + method_sig == other.method_sig; + } +}; + +static unsigned mdo_lookup_key_hash(const MDOLookupKey& key) { + // Combine the Symbol* addresses — symbols are interned so pointer + // identity == value identity. + uintptr_t h = (uintptr_t)key.klass_name; + h = h * 31 + (uintptr_t)key.method_name; + h = h * 31 + (uintptr_t)key.method_sig; + return (unsigned)(h ^ (h >> 16)); +} + +static bool mdo_lookup_key_equals(const MDOLookupKey& a, const MDOLookupKey& b) { + return a == b; +} + +// -------------------------------------------------------------------------- +// Parsed MDO entry — offset into the import buffer for fast reconstruction +// -------------------------------------------------------------------------- + +struct ImportedMDOEntry : public CHeapObj { + // Identity + int16_t klass_ref_index; + Symbol* method_name; // Interned + Symbol* method_sig; // Interned + uint32_t bytecode_fingerprint; + + // File offset where the header fields begin (after the identity block) + size_t header_fields_offset; + + // File offset where the entry counts struct begins + size_t counts_offset; + + // Counts (parsed eagerly for validation) + uint16_t record_count; + uint16_t extra_record_count; + uint16_t param_type_count; + uint16_t exception_handler_count; + + // File offset where the records section begins + size_t records_offset; + + // Total byte length of this entry (for skipping) + size_t total_length; + + ImportedMDOEntry() : klass_ref_index(-1), method_name(nullptr), method_sig(nullptr), + bytecode_fingerprint(0), header_fields_offset(0), + counts_offset(0), record_count(0), extra_record_count(0), + param_type_count(0), exception_handler_count(0), + records_offset(0), total_length(0) {} +}; + +// -------------------------------------------------------------------------- +// Import state — file-scope globals (only one import file at a time) +// -------------------------------------------------------------------------- + +static uint8_t* _import_buffer = nullptr; +static size_t _import_buffer_size = 0; +static PortableMDOFileHeader _import_header; +static ImportedKlassRef* _import_klass_refs = nullptr; +static int _import_klass_ref_count = 0; +static ImportedMethodRef* _import_method_refs = nullptr; +static int _import_method_ref_count = 0; +static ImportedMDOEntry* _import_entries = nullptr; +static int _import_entry_count = 0; + +// Lookup map: (klass_name, method_name, sig) -> entry index +using MDOLookupMap = ResourceHashtable; +static MDOLookupMap* _import_lookup_map = nullptr; +static bool _import_initialized = false; + +// -------------------------------------------------------------------------- +// Header validation +// -------------------------------------------------------------------------- + +static bool validate_import_header(const PortableMDOFileHeader* hdr) { + if (hdr->magic != PortableMDO::MAGIC) { + log_error(aot, training)("PortableMDO import: bad magic 0x%08x (expected 0x%08x)", + hdr->magic, PortableMDO::MAGIC); + return false; + } + if (hdr->format_version != PortableMDO::FORMAT_VERSION) { + log_error(aot, training)("PortableMDO import: unsupported format version %u (expected %u)", + hdr->format_version, PortableMDO::FORMAT_VERSION); + return false; + } + // JDK version hash check + uint32_t current_hash = compute_jdk_version_hash(); + if (hdr->jdk_version_hash != current_hash) { + log_error(aot, training)("PortableMDO import: JDK version mismatch " + "(file=0x%08x, current=0x%08x)", hdr->jdk_version_hash, current_hash); + return false; + } + // Pointer size check + if (hdr->pointer_size != (uint8_t)sizeof(void*)) { + log_error(aot, training)("PortableMDO import: pointer size mismatch " + "(file=%u, current=%u)", hdr->pointer_size, (uint8_t)sizeof(void*)); + return false; + } + // Endianness check + uint8_t current_endianness; +#ifdef VM_LITTLE_ENDIAN + current_endianness = 0; +#else + current_endianness = 1; +#endif + if (hdr->endianness != current_endianness) { + log_error(aot, training)("PortableMDO import: endianness mismatch " + "(file=%u, current=%u)", hdr->endianness, current_endianness); + return false; + } + // Sanity check counts + if (hdr->mdo_entry_count > PortableMDO::MAX_MDO_ENTRY_COUNT) { + log_error(aot, training)("PortableMDO import: too many MDO entries (%u)", + hdr->mdo_entry_count); + return false; + } + return true; +} + +// -------------------------------------------------------------------------- +// Parse klass reference table +// -------------------------------------------------------------------------- + +static bool parse_klass_ref_table(PortableMDOReader* reader, + const PortableMDOFileHeader* hdr) { + reader->set_position(hdr->klass_ref_table_offset); + if (reader->error()) return false; + + int count = (int)hdr->klass_ref_count; + if (count > PortableMDO::MAX_KLASS_REF_COUNT) return false; + + _import_klass_refs = NEW_C_HEAP_ARRAY(ImportedKlassRef, count, mtInternal); + _import_klass_ref_count = count; + + for (int i = 0; i < count; i++) { + PortableKlassRefEntry entry; + if (!reader->read_struct(&entry, sizeof(entry))) return false; + + if (entry.name_length > PortableMDO::MAX_UTF8_LENGTH) return false; + const uint8_t* name_bytes = reader->read_bytes(entry.name_length); + if (name_bytes == nullptr) return false; + reader->align4(); + + ImportedKlassRef* ref = &_import_klass_refs[i]; + ref->name = SymbolTable::new_symbol((const char*)name_bytes, entry.name_length); + ref->loader_tag = entry.loader_tag; + ref->resolved = nullptr; + ref->resolution_attempted = false; + } + return !reader->error(); +} + +// -------------------------------------------------------------------------- +// Parse method reference table +// -------------------------------------------------------------------------- + +static bool parse_method_ref_table(PortableMDOReader* reader, + const PortableMDOFileHeader* hdr) { + reader->set_position(hdr->method_ref_table_offset); + if (reader->error()) return false; + + int count = (int)hdr->method_ref_count; + if (count > PortableMDO::MAX_METHOD_REF_COUNT) return false; + + _import_method_refs = NEW_C_HEAP_ARRAY(ImportedMethodRef, count, mtInternal); + _import_method_ref_count = count; + + for (int i = 0; i < count; i++) { + PortableMethodRefEntry entry; + if (!reader->read_struct(&entry, sizeof(entry))) return false; + + if (entry.name_length > PortableMDO::MAX_UTF8_LENGTH) return false; + if (entry.sig_length > PortableMDO::MAX_UTF8_LENGTH) return false; + + const uint8_t* name_bytes = reader->read_bytes(entry.name_length); + if (name_bytes == nullptr) return false; + const uint8_t* sig_bytes = reader->read_bytes(entry.sig_length); + if (sig_bytes == nullptr) return false; + reader->align4(); + + ImportedMethodRef* ref = &_import_method_refs[i]; + ref->klass_ref_index = (int16_t)entry.klass_ref_index; + ref->name = SymbolTable::new_symbol((const char*)name_bytes, entry.name_length); + ref->sig = SymbolTable::new_symbol((const char*)sig_bytes, entry.sig_length); + } + return !reader->error(); +} + +// -------------------------------------------------------------------------- +// Skip a single profile record (used during entry parsing to compute offsets) +// -------------------------------------------------------------------------- + +static bool skip_profile_record(PortableMDOReader* reader) { + PortableProfileRecordHeader hdr; + if (!reader->read_struct(&hdr, sizeof(hdr))) return false; + + switch (hdr.tag) { + case DataLayout::bit_data_tag: + // No payload + break; + + case DataLayout::counter_data_tag: + reader->skip(sizeof(PortableCounterDataPayload)); + break; + + case DataLayout::jump_data_tag: + reader->skip(sizeof(PortableJumpDataPayload)); + break; + + case DataLayout::receiver_type_data_tag: + case DataLayout::virtual_call_data_tag: { + PortableReceiverTypeDataPayload payload; + if (!reader->read_struct(&payload, sizeof(payload))) return false; + reader->skip(payload.num_rows * sizeof(PortableReceiverRow)); + break; + } + + case DataLayout::ret_data_tag: { + PortableRetDataPayload payload; + if (!reader->read_struct(&payload, sizeof(payload))) return false; + reader->skip(payload.num_rows * sizeof(PortableRetRow)); + break; + } + + case DataLayout::branch_data_tag: + reader->skip(sizeof(PortableBranchDataPayload)); + break; + + case DataLayout::multi_branch_data_tag: { + PortableMultiBranchDataPayload payload; + if (!reader->read_struct(&payload, sizeof(payload))) return false; + reader->skip(payload.num_cases * sizeof(uint32_t)); + break; + } + + case DataLayout::call_type_data_tag: { + PortableCallTypeDataPayload payload; + if (!reader->read_struct(&payload, sizeof(payload))) return false; + reader->skip(payload.num_args * sizeof(PortableSymbolicTypeEntry)); + if (payload.has_return) { + reader->skip(sizeof(PortableSymbolicTypeEntry)); + } + break; + } + + case DataLayout::virtual_call_type_data_tag: { + PortableVirtualCallTypeDataPayload payload; + if (!reader->read_struct(&payload, sizeof(payload))) return false; + reader->skip(payload.num_receiver_rows * sizeof(PortableReceiverRow)); + reader->skip(payload.num_args * sizeof(PortableSymbolicTypeEntry)); + if (payload.has_return) { + reader->skip(sizeof(PortableSymbolicTypeEntry)); + } + break; + } + + default: + // Unknown tag — no payload assumed + break; + } + return !reader->error(); +} + +// -------------------------------------------------------------------------- +// Skip extra records +// -------------------------------------------------------------------------- + +static bool skip_extra_record(PortableMDOReader* reader) { + PortableExtraRecordHeader hdr; + if (!reader->read_struct(&hdr, sizeof(hdr))) return false; + + if (hdr.tag == PortableExtraTag::SPECULATIVE_TRAP) { + reader->skip(sizeof(PortableSpeculativeTrapPayload)); + } + // BIT_DATA has no payload beyond the header + return !reader->error(); +} + +// -------------------------------------------------------------------------- +// Parse MDO entries and build lookup map +// -------------------------------------------------------------------------- + +static bool parse_mdo_entries(PortableMDOReader* reader, + const PortableMDOFileHeader* hdr) { + reader->set_position(hdr->mdo_entries_offset); + if (reader->error()) return false; + + int count = (int)hdr->mdo_entry_count; + _import_entries = NEW_C_HEAP_ARRAY(ImportedMDOEntry, count, mtInternal); + _import_entry_count = count; + + _import_lookup_map = new (mtInternal) MDOLookupMap(); + + for (int i = 0; i < count; i++) { + size_t entry_start = reader->position(); + ImportedMDOEntry* entry = &_import_entries[i]; + + // Read method identity + PortableMDOMethodIdentity identity; + if (!reader->read_struct(&identity, sizeof(identity))) return false; + + if (identity.name_length > PortableMDO::MAX_UTF8_LENGTH) return false; + if (identity.sig_length > PortableMDO::MAX_UTF8_LENGTH) return false; + + const uint8_t* name_bytes = reader->read_bytes(identity.name_length); + if (name_bytes == nullptr) return false; + const uint8_t* sig_bytes = reader->read_bytes(identity.sig_length); + if (sig_bytes == nullptr) return false; + reader->align4(); + + entry->klass_ref_index = (int16_t)identity.klass_ref_index; + entry->method_name = SymbolTable::new_symbol((const char*)name_bytes, identity.name_length); + entry->method_sig = SymbolTable::new_symbol((const char*)sig_bytes, identity.sig_length); + entry->bytecode_fingerprint = identity.bytecode_fingerprint; + + // Record offset of header fields + entry->header_fields_offset = reader->position(); + + // Skip header fields struct + reader->skip(sizeof(PortableMDOHeaderFields)); + if (reader->error()) return false; + + // Skip arg_modified bytes (count is in the header fields we just skipped) + // Re-read arg_modified_count from the header fields + PortableMDOReader hdr_reader(_import_buffer, _import_buffer_size); + hdr_reader.set_position(entry->header_fields_offset); + PortableMDOHeaderFields header_fields; + hdr_reader.read_struct(&header_fields, sizeof(header_fields)); + uint16_t arg_modified_count = header_fields.arg_modified_count; + reader->skip(arg_modified_count); + reader->align4(); + + // Read counts + entry->counts_offset = reader->position(); + PortableMDOEntryCounts counts; + if (!reader->read_struct(&counts, sizeof(counts))) return false; + entry->record_count = counts.record_count; + entry->extra_record_count = counts.extra_record_count; + entry->param_type_count = counts.param_type_count; + entry->exception_handler_count = counts.exception_handler_count; + + // Record where records section begins + entry->records_offset = reader->position(); + + // Skip all profile records + for (uint16_t r = 0; r < counts.record_count; r++) { + if (!skip_profile_record(reader)) return false; + } + + // Skip extra data records + for (uint16_t e = 0; e < counts.extra_record_count; e++) { + if (!skip_extra_record(reader)) return false; + } + + // Skip parameter type entries + reader->skip(counts.param_type_count * sizeof(PortableSymbolicTypeEntry)); + + // Skip exception handler entries + reader->skip(counts.exception_handler_count * sizeof(PortableExceptionHandlerEntry)); + + if (reader->error()) return false; + + entry->total_length = reader->position() - entry_start; + + // Add to lookup map + if (entry->klass_ref_index >= 0 && + entry->klass_ref_index < _import_klass_ref_count) { + MDOLookupKey key; + key.klass_name = _import_klass_refs[entry->klass_ref_index].name; + key.method_name = entry->method_name; + key.method_sig = entry->method_sig; + bool created; + _import_lookup_map->put_if_absent(key, i, &created); + } + } + return !reader->error(); +} + +// -------------------------------------------------------------------------- +// Klass resolution (lookup-only, no class loading triggered) +// -------------------------------------------------------------------------- + +static Handle classloader_handle_from_tag(PortableClassLoaderTag tag, Thread* current) { + switch (tag) { + case PortableClassLoaderTag::BOOT: + return Handle(); // null handle = bootstrap + case PortableClassLoaderTag::PLATFORM: + return Handle(current, SystemDictionary::java_platform_loader()); + case PortableClassLoaderTag::APP: + return Handle(current, SystemDictionary::java_system_loader()); + case PortableClassLoaderTag::CUSTOM: + // Custom classloaders can't be resolved by tag alone — + // would need additional name-based lookup. + return Handle(); + default: + return Handle(); + } +} + +static Klass* resolve_imported_klass_ref(int16_t index, Thread* current) { + if (index < 0 || index >= _import_klass_ref_count) return nullptr; + + ImportedKlassRef* ref = &_import_klass_refs[index]; + + // Return cached result if we already attempted resolution + if (ref->resolution_attempted) return ref->resolved; + ref->resolution_attempted = true; + + // Custom loaders are not resolved + if (ref->loader_tag == PortableClassLoaderTag::CUSTOM) { + log_debug(aot, training)("PortableMDO import: skipping custom-loader klass %s", + ref->name->as_C_string()); + return nullptr; + } + + Handle loader = classloader_handle_from_tag(ref->loader_tag, current); + InstanceKlass* k = SystemDictionary::find_instance_klass(current, ref->name, loader); + + if (k != nullptr) { + ref->resolved = k; + } else { + log_debug(aot, training)("PortableMDO import: unresolved klass %s", + ref->name->as_C_string()); + } + return ref->resolved; +} + +// -------------------------------------------------------------------------- +// Type entry reconstruction helper +// +// Reconstructs the intptr_t encoding for TypeStackSlotEntries and +// ReturnTypeEntry from a PortableSymbolicTypeEntry. +// -------------------------------------------------------------------------- + +static intptr_t reconstruct_type_entry(const PortableSymbolicTypeEntry* entry, + Thread* current) { + intptr_t result = TypeEntries::type_none(); + + if (entry->null_seen) { + result |= TypeEntries::null_seen; + } + + if (entry->type_unknown) { + result |= TypeEntries::type_unknown; + } else if (entry->klass_ref_index != PortableMDO::NULL_KLASS_REF) { + Klass* k = resolve_imported_klass_ref(entry->klass_ref_index, current); + if (k != nullptr) { + result = TypeEntries::with_status((intptr_t)k, result); + } + // If klass not resolved, leave as type_none with null_seen bit preserved + } + + return result; +} + +// -------------------------------------------------------------------------- +// Patch header fields into a freshly-allocated MethodData +// -------------------------------------------------------------------------- + +static void patch_import_header(MethodData* mdo, + PortableMDOReader* reader, + size_t header_offset, + size_t arg_modified_start) { + reader->set_position(header_offset); + PortableMDOHeaderFields hdr; + reader->read_struct(&hdr, sizeof(hdr)); + if (reader->error()) return; + + // Invocation / backedge counters + mdo->invocation_counter()->set(hdr.invocation_count); + mdo->backedge_counter()->set(hdr.backedge_count); + + // Reset baseline counters so invocation_count() / backedge_count() + // return the imported values directly. + mdo->reset_start_counters(); + + // Trap histogram — write directly into CompilerCounters trap history. + // The trap_hist encoding: stored value = count + 1. Value 0 in the + // array means count of (uint)-1 = saturated. Value N means count N-1. + // We just set the raw byte: _trap_hist._array[i] = stored_value. + // The portable format stores decoded counts, so re-encode here. + uint trap_limit = MIN2((uint)_import_header.trap_hist_length, + (uint)MethodData::trap_reason_limit()); + for (uint i = 0; i < trap_limit; i++) { + uint count = hdr.trap_hist[i]; + // Re-encode: the raw array stores count+1 (0 means saturated). + // We clamp to 254 since 255+1=0 would mean saturated. + if (count >= 255) { + // Set as saturated — but this shouldn't happen since export capped at 255 + // To set saturated: call inc_trap_count until it saturates. + // Simpler: just set the raw byte. We need access to the underlying array. + // For now, use inc_trap_count in a loop. + for (uint j = 0; j < 256; j++) { + mdo->inc_trap_count(i); + } + } else { + for (uint j = 0; j < count; j++) { + mdo->inc_trap_count(i); + } + } + } + + // Decompile count — increment one by one since there's only inc_decompile_count(). + // IMPORTANT: MethodData::inc_decompile_count() has a side effect: if the + // count exceeds PerMethodRecompilationCutoff, it marks the method as + // not-compilable. We must cap the imported count to prevent this, since + // the purpose of importing profiles is to *enable* compilation, not block it. + uint decompile_cap = (PerMethodRecompilationCutoff > 0) + ? (uint)PerMethodRecompilationCutoff : (uint)hdr.nof_decompiles; + uint decompiles_to_import = MIN2((uint)hdr.nof_decompiles, decompile_cap); + for (uint d = 0; d < decompiles_to_import; d++) { + mdo->inc_decompile_count(); + } + + // Tenure traps + for (uint t = 0; t < hdr.tenure_traps; t++) { + mdo->inc_tenure_traps(); + } + + // Structural info + mdo->set_num_loops(hdr.num_loops); + mdo->set_num_blocks(hdr.num_blocks); + + // Would profile + if (hdr.would_profile == 2) { + mdo->set_would_profile(true); + } else if (hdr.would_profile == 1) { + mdo->set_would_profile(false); + } + + // Arg modified + reader->set_position(arg_modified_start); + DataLayout* arg_dl = find_arg_info_layout(mdo); + for (int a = 0; a < hdr.arg_modified_count; a++) { + uint8_t modified = reader->read_u1(); + if (!reader->error() && arg_dl != nullptr) { + ArgInfoData args(arg_dl); + args.set_arg_modified(a, modified); + } + } +} + +// -------------------------------------------------------------------------- +// Patch per-BCI profile records +// -------------------------------------------------------------------------- + +static void patch_profile_records(MethodData* mdo, + PortableMDOReader* reader, + size_t records_offset, + uint16_t record_count, + Thread* current) { + reader->set_position(records_offset); + if (reader->error()) return; + + // Walk the live MDO data and the imported records in parallel, + // matching by bci + tag. + ProfileData* live_data = mdo->first_data(); + int import_idx = 0; + + while (mdo->is_valid(live_data) && import_idx < record_count) { + PortableProfileRecordHeader rec_hdr; + size_t rec_start = reader->position(); + if (!reader->read_struct(&rec_hdr, sizeof(rec_hdr))) return; + + // Try to match by bci and tag + if (live_data->bci() == rec_hdr.bci && + ((DataLayout*)live_data->dp())->tag() == rec_hdr.tag) { + // Match — patch this live record + + // Set trap state + ((DataLayout*)live_data->dp())->set_trap_state(rec_hdr.trap_state); + + switch (rec_hdr.tag) { + case DataLayout::bit_data_tag: { + // No payload — flags are already in the DataLayout header. + // The flags byte has been exported; OR it onto the existing flags. + // DataLayout::set_flag_at is per-bit; just set the raw flags byte + // by OR'ing each bit that is set in the imported flags. + for (int bit = 0; bit < 8; bit++) { + if (rec_hdr.flags & (1 << bit)) { + ((DataLayout*)live_data->dp())->set_flag_at(bit); + } + } + break; + } + + case DataLayout::counter_data_tag: { + PortableCounterDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + CounterData* cd = live_data->as_CounterData(); + cd->set_count(payload.count); + break; + } + + case DataLayout::jump_data_tag: { + PortableJumpDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + JumpData* jd = live_data->as_JumpData(); + jd->set_taken(payload.taken); + // displacement already correct from allocate/initialize + break; + } + + case DataLayout::receiver_type_data_tag: + case DataLayout::virtual_call_data_tag: { + PortableReceiverTypeDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + ReceiverTypeData* rtd = live_data->as_ReceiverTypeData(); + rtd->set_count(payload.count); + + uint rows_to_read = payload.num_rows; + uint rows_to_patch = MIN2((uint)payload.num_rows, rtd->row_limit()); + + for (uint r = 0; r < rows_to_read; r++) { + PortableReceiverRow row; + reader->read_struct(&row, sizeof(row)); + if (r < rows_to_patch) { + Klass* k = resolve_imported_klass_ref(row.klass_ref_index, current); + rtd->set_receiver(r, k); + rtd->set_receiver_count(r, k != nullptr ? row.count : 0); + } + } + break; + } + + case DataLayout::ret_data_tag: { + PortableRetDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + RetData* rd = live_data->as_RetData(); + rd->set_count(payload.count); + + uint rows_to_read = payload.num_rows; + uint rows_to_patch = MIN2((uint)payload.num_rows, RetData::row_limit()); + + // RetData layout per row (3 cells each): + // cell[counter_cell_count + row*3 + 0] = bci + // cell[counter_cell_count + row*3 + 1] = count + // cell[counter_cell_count + row*3 + 2] = displacement (don't touch) + // counter_cell_count = 1 (from CounterData: count_off=0, counter_cell_count=1) + DataLayout* ret_dl = (DataLayout*)rd->dp(); + for (uint r = 0; r < rows_to_read; r++) { + PortableRetRow row; + reader->read_struct(&row, sizeof(row)); + if (r < rows_to_patch) { + // bci cell index: header_cells + counter_cell_count + r*3 + 0 + // DataLayout cells start after the header, and ProfileData cell + // indices are relative to cell[0]. + // CounterData: counter_cell_count = 1 + // RetData: bci0_offset = 1, count0_offset = 2, displacement0_offset = 3 + // ret_row_cell_count = 3 + int bci_cell = 1 + r * 3 + 0; // bci0_offset + r * ret_row_cell_count + int cnt_cell = 1 + r * 3 + 1; // count0_offset + r * ret_row_cell_count + ret_dl->set_cell_at(bci_cell, (intptr_t)row.target_bci); + ret_dl->set_cell_at(cnt_cell, (intptr_t)row.count); + // displacement stays as initialized + } + } + break; + } + + case DataLayout::branch_data_tag: { + PortableBranchDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + BranchData* bd = live_data->as_BranchData(); + bd->set_taken(payload.taken); + bd->set_not_taken(payload.not_taken); + // displacement already correct + break; + } + + case DataLayout::multi_branch_data_tag: { + PortableMultiBranchDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + MultiBranchData* mbd = live_data->as_MultiBranchData(); + + // MultiBranchData layout in cells (after array_len): + // cell[array_start + 0] = default_count + // cell[array_start + 1] = default_displacement + // cell[array_start + 2 + i*2 + 0] = case_count[i] + // cell[array_start + 2 + i*2 + 1] = case_displacement[i] + // array_start_off_set = 1 (from ArrayData), so cell indices are offset by 1 + // from the DataLayout cell array. + // Use DataLayout::set_cell_at() which is public. + DataLayout* dl = (DataLayout*)live_data->dp(); + // default_count is at cell index: array_start(1) + 0 + dl->set_cell_at(1 + 0, (intptr_t)payload.default_count); + + int ncases = MIN2((int)payload.num_cases, mbd->number_of_cases()); + for (int c = 0; c < (int)payload.num_cases; c++) { + uint32_t cnt = reader->read_u4(); + if (c < ncases) { + // case_count[c] is at cell index: array_start(1) + 2 + c*2 + 0 + dl->set_cell_at(1 + 2 + c * 2, (intptr_t)cnt); + } + } + // displacements already correct from initialize() + break; + } + + case DataLayout::call_type_data_tag: { + PortableCallTypeDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + CallTypeData* ctd = live_data->as_CallTypeData(); + ctd->set_count(payload.count); + + // Argument types + for (uint8_t a = 0; a < payload.num_args; a++) { + PortableSymbolicTypeEntry ste; + reader->read_struct(&ste, sizeof(ste)); + if (ctd->has_arguments() && a < (uint8_t)ctd->number_of_arguments()) { + intptr_t type_val = reconstruct_type_entry(&ste, current); + const_cast(ctd->args())->set_type(a, type_val); + } + } + + // Return type + if (payload.has_return) { + PortableSymbolicTypeEntry ste; + reader->read_struct(&ste, sizeof(ste)); + if (ctd->has_return()) { + intptr_t type_val = reconstruct_type_entry(&ste, current); + const_cast(ctd->ret())->set_type(type_val); + } + } + break; + } + + case DataLayout::virtual_call_type_data_tag: { + PortableVirtualCallTypeDataPayload payload; + reader->read_struct(&payload, sizeof(payload)); + VirtualCallTypeData* vctd = live_data->as_VirtualCallTypeData(); + vctd->set_count(payload.count); + + // Receiver rows + uint rows_to_patch = MIN2((uint)payload.num_receiver_rows, vctd->row_limit()); + for (uint r = 0; r < payload.num_receiver_rows; r++) { + PortableReceiverRow row; + reader->read_struct(&row, sizeof(row)); + if (r < rows_to_patch) { + Klass* k = resolve_imported_klass_ref(row.klass_ref_index, current); + vctd->set_receiver(r, k); + vctd->set_receiver_count(r, k != nullptr ? row.count : 0); + } + } + + // Argument types + for (uint8_t a = 0; a < payload.num_args; a++) { + PortableSymbolicTypeEntry ste; + reader->read_struct(&ste, sizeof(ste)); + if (vctd->has_arguments() && a < (uint8_t)vctd->number_of_arguments()) { + intptr_t type_val = reconstruct_type_entry(&ste, current); + const_cast(vctd->args())->set_type(a, type_val); + } + } + + // Return type + if (payload.has_return) { + PortableSymbolicTypeEntry ste; + reader->read_struct(&ste, sizeof(ste)); + if (vctd->has_return()) { + intptr_t type_val = reconstruct_type_entry(&ste, current); + const_cast(vctd->ret())->set_type(type_val); + } + } + break; + } + + default: + // Unknown tag at this position — skip its data + // Re-seek past the record by re-skipping from rec_start + reader->set_position(rec_start); + skip_profile_record(reader); + break; + } + + import_idx++; + live_data = mdo->next_data(live_data); + + } else if (live_data->bci() < rec_hdr.bci) { + // Live data is behind the imported record — advance live_data, + // but re-read this imported record on the next iteration. + reader->set_position(rec_start); + live_data = mdo->next_data(live_data); + + } else { + // Imported record BCI is behind live data — this record was for + // a bytecode that no longer has profile data. Skip it. + reader->set_position(rec_start); + skip_profile_record(reader); + import_idx++; + } + } + + // Skip any remaining unmatched imported records + while (import_idx < record_count) { + skip_profile_record(reader); + import_idx++; + } +} + +// -------------------------------------------------------------------------- +// Patch extra data records +// -------------------------------------------------------------------------- + +static void patch_extra_data(MethodData* mdo, + PortableMDOReader* reader, + uint16_t extra_count, + Thread* current) { + if (extra_count == 0) return; + + Mutex* lock = mdo->extra_data_lock(); + MutexLocker ml(lock, Mutex::_no_safepoint_check_flag); + + DataLayout* dp = mdo->extra_data_base(); + DataLayout* end = mdo->args_data_limit(); + + for (uint16_t i = 0; i < extra_count; i++) { + PortableExtraRecordHeader ehdr; + if (!reader->read_struct(&ehdr, sizeof(ehdr))) return; + + if (dp >= end) { + // No more room in extra data — skip remaining + if (ehdr.tag == PortableExtraTag::SPECULATIVE_TRAP) { + reader->skip(sizeof(PortableSpeculativeTrapPayload)); + } + continue; + } + + if (ehdr.tag == PortableExtraTag::SPECULATIVE_TRAP) { + PortableSpeculativeTrapPayload payload; + reader->read_struct(&payload, sizeof(payload)); + + // Resolve the method reference + Method* m = nullptr; + if (payload.method_ref_index >= 0 && + payload.method_ref_index < _import_method_ref_count) { + ImportedMethodRef* mref = &_import_method_refs[payload.method_ref_index]; + Klass* holder = resolve_imported_klass_ref(mref->klass_ref_index, current); + if (holder != nullptr && holder->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(holder); + m = ik->find_method(mref->name, mref->sig); + // Don't install old methods + if (m != nullptr && m->is_old()) m = nullptr; + } + } + + if (m != nullptr) { + // Initialize as a SpeculativeTrapData entry + int cell_count = SpeculativeTrapData::static_cell_count(); + dp->initialize(DataLayout::speculative_trap_data_tag, + ehdr.bci, cell_count); + dp->set_trap_state(ehdr.trap_state); + SpeculativeTrapData data(dp); + data.set_method(m); + dp = MethodData::next_extra(dp); + } + // If method not resolved, just skip this entry + + } else if (ehdr.tag == PortableExtraTag::BIT_DATA) { + // Stray trap — install as a BitData entry + int cell_count = BitData::static_cell_count(); + dp->initialize(DataLayout::bit_data_tag, ehdr.bci, cell_count); + dp->set_trap_state(ehdr.trap_state); + dp = MethodData::next_extra(dp); + } + } +} + +// -------------------------------------------------------------------------- +// Patch parameter type data +// -------------------------------------------------------------------------- + +static void patch_parameter_types(MethodData* mdo, + PortableMDOReader* reader, + uint16_t param_count, + Thread* current) { + ParametersTypeData* params = mdo->parameters_type_data(); + if (params == nullptr || param_count == 0) { + // Skip the data in the reader + reader->skip(param_count * sizeof(PortableSymbolicTypeEntry)); + return; + } + + int live_count = params->number_of_parameters(); + const TypeStackSlotEntries* entries = params->parameters(); + + for (uint16_t i = 0; i < param_count; i++) { + PortableSymbolicTypeEntry ste; + reader->read_struct(&ste, sizeof(ste)); + if (reader->error()) return; + + if ((int)i < live_count) { + intptr_t type_val = reconstruct_type_entry(&ste, current); + // Set the raw intptr_t which includes Klass* | status_bits encoding + const_cast(entries)->set_type(i, type_val); + } + } +} + +// -------------------------------------------------------------------------- +// Patch exception handler data +// -------------------------------------------------------------------------- + +static void patch_exception_handlers(MethodData* mdo, + PortableMDOReader* reader, + uint16_t handler_count) { + if (handler_count == 0) return; + + DataLayout* dp = mdo->exception_handler_data_base(); + DataLayout* end = mdo->exception_handler_data_limit(); + int entry_size = DataLayout::compute_size_in_bytes(BitData::static_cell_count()); + + for (uint16_t i = 0; i < handler_count; i++) { + PortableExceptionHandlerEntry entry; + reader->read_struct(&entry, sizeof(entry)); + if (reader->error()) return; + + if (dp < end) { + // Match by BCI — the live MDO has exception handler entries + // in the same order as the bytecodes + BitData data(dp); + if (dp->bci() == entry.handler_bci && entry.entered) { + data.set_exception_handler_entered(); + } + dp = (DataLayout*)((address)dp + entry_size); + } + } +} + +// -------------------------------------------------------------------------- +// MDO reconstruction — the core import routine +// -------------------------------------------------------------------------- + +static MethodData* reconstruct_mdo(const methodHandle& method, + ImportedMDOEntry* entry, + TRAPS) { + // Step 1: Allocate MDO normally — this computes the correct layout, + // displacements, and initializes all cells to zero. + ClassLoaderData* loader = method->method_holder()->class_loader_data(); + MethodData* mdo = MethodData::allocate(loader, method, CHECK_NULL); + + PortableMDOReader reader(_import_buffer, _import_buffer_size); + + // Step 2: Patch header fields + size_t arg_modified_start = entry->header_fields_offset + sizeof(PortableMDOHeaderFields); + patch_import_header(mdo, &reader, entry->header_fields_offset, arg_modified_start); + + // Step 3: Patch per-BCI profile records + patch_profile_records(mdo, &reader, entry->records_offset, + entry->record_count, THREAD); + + // Step 4: Compute offset of extra records (after all profile records) + // The reader left off after the profile records, so current position + // is the start of extra data. + size_t extra_offset = reader.position(); + + // Step 5: Patch extra data + reader.set_position(extra_offset); + patch_extra_data(mdo, &reader, entry->extra_record_count, THREAD); + + // Step 6: Patch parameter type data + patch_parameter_types(mdo, &reader, entry->param_type_count, THREAD); + + // Step 7: Patch exception handler data + patch_exception_handlers(mdo, &reader, entry->exception_handler_count); + + if (reader.error()) { + log_warning(aot, training)("PortableMDO import: read error during reconstruction of %s.%s%s", + method->method_holder()->external_name(), + method->name()->as_C_string(), + method->signature()->as_C_string()); + MetadataFactory::free_metadata(loader, mdo); + return nullptr; + } + + // Step 8: Fix up runtime state + // _hint_di is initialized to 0 by MethodData::allocate/initialize, + // which is correct (first_di() == 0). No fixup needed. + + log_info(aot, training)("PortableMDO import: reconstructed MDO for %s.%s%s " + "(%d records, %d extra, %d params, %d handlers)", + method->method_holder()->external_name(), + method->name()->as_C_string(), + method->signature()->as_C_string(), + entry->record_count, entry->extra_record_count, + entry->param_type_count, entry->exception_handler_count); + + return mdo; +} + +// -------------------------------------------------------------------------- +// Public API: initialize_import +// -------------------------------------------------------------------------- + +void PortableMDO::initialize_import(const char* filepath) { + if (filepath == nullptr) return; + if (_import_initialized) return; + + // Read the entire file into a C-heap buffer + FILE* file = os::fopen(filepath, "rb"); + if (file == nullptr) { + log_error(aot, training)("PortableMDO import: failed to open %s", filepath); + return; + } + + // Get file size + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + log_error(aot, training)("PortableMDO import: failed to seek in %s", filepath); + return; + } + long file_size = ftell(file); + if (file_size <= 0 || (size_t)file_size < sizeof(PortableMDOFileHeader)) { + fclose(file); + log_error(aot, training)("PortableMDO import: file too small or empty: %s", filepath); + return; + } + if (fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return; + } + + _import_buffer_size = (size_t)file_size; + _import_buffer = NEW_C_HEAP_ARRAY(uint8_t, _import_buffer_size, mtInternal); + size_t read = fread(_import_buffer, 1, _import_buffer_size, file); + fclose(file); + + if (read != _import_buffer_size) { + log_error(aot, training)("PortableMDO import: short read from %s", filepath); + FREE_C_HEAP_ARRAY(uint8_t, _import_buffer); + _import_buffer = nullptr; + return; + } + + // Validate header + memcpy(&_import_header, _import_buffer, sizeof(_import_header)); + if (!validate_import_header(&_import_header)) { + FREE_C_HEAP_ARRAY(uint8_t, _import_buffer); + _import_buffer = nullptr; + return; + } + + PortableMDOReader reader(_import_buffer, _import_buffer_size); + + // Parse reference tables + if (!parse_klass_ref_table(&reader, &_import_header)) { + log_error(aot, training)("PortableMDO import: failed to parse klass ref table"); + shutdown_import(); + return; + } + + if (!parse_method_ref_table(&reader, &_import_header)) { + log_error(aot, training)("PortableMDO import: failed to parse method ref table"); + shutdown_import(); + return; + } + + // Parse MDO entries and build lookup map + if (!parse_mdo_entries(&reader, &_import_header)) { + log_error(aot, training)("PortableMDO import: failed to parse MDO entries"); + shutdown_import(); + return; + } + + _import_initialized = true; + + log_info(aot, training)("PortableMDO import: loaded %d MDO profiles from %s " + "(%d klass refs, %d method refs)", + _import_entry_count, filepath, + _import_klass_ref_count, _import_method_ref_count); +} + +// -------------------------------------------------------------------------- +// Public API: has_import_data +// -------------------------------------------------------------------------- + +bool PortableMDO::has_import_data() { + return _import_initialized; +} + +// -------------------------------------------------------------------------- +// Public API: try_import +// -------------------------------------------------------------------------- + +MethodData* PortableMDO::try_import(const methodHandle& method, TRAPS) { + if (!_import_initialized) return nullptr; + + // Build lookup key from the method + InstanceKlass* holder = method->method_holder(); + Symbol* klass_name = holder->name(); + Symbol* method_name = method->name(); + Symbol* method_sig = method->signature(); + + MDOLookupKey key; + key.klass_name = klass_name; + key.method_name = method_name; + key.method_sig = method_sig; + + int* entry_idx = _import_lookup_map->get(key); + if (entry_idx == nullptr) { + log_trace(aot, training)("PortableMDO import: no entry for %s.%s%s", + klass_name->as_C_string(), + method_name->as_C_string(), + method_sig->as_C_string()); + return nullptr; + } + + ImportedMDOEntry* entry = &_import_entries[*entry_idx]; + + // Validate bytecode fingerprint + uint32_t current_fp = PortableMDO::compute_bytecode_fingerprint(method()); + if (current_fp != entry->bytecode_fingerprint) { + log_info(aot, training)("PortableMDO import: fingerprint mismatch for %s.%s%s " + "(expected 0x%08x, got 0x%08x)", + holder->external_name(), + method_name->as_C_string(), + method_sig->as_C_string(), + entry->bytecode_fingerprint, current_fp); + return nullptr; + } + + // Reconstruct the MDO + return reconstruct_mdo(method, entry, THREAD); +} + +// -------------------------------------------------------------------------- +// Public API: shutdown_import +// -------------------------------------------------------------------------- + +void PortableMDO::shutdown_import() { + if (_import_buffer != nullptr) { + FREE_C_HEAP_ARRAY(uint8_t, _import_buffer); + _import_buffer = nullptr; + } + if (_import_klass_refs != nullptr) { + // Release symbol refcounts + for (int i = 0; i < _import_klass_ref_count; i++) { + if (_import_klass_refs[i].name != nullptr) { + _import_klass_refs[i].name->decrement_refcount(); + } + } + FREE_C_HEAP_ARRAY(ImportedKlassRef, _import_klass_refs); + _import_klass_refs = nullptr; + } + if (_import_method_refs != nullptr) { + for (int i = 0; i < _import_method_ref_count; i++) { + if (_import_method_refs[i].name != nullptr) { + _import_method_refs[i].name->decrement_refcount(); + } + if (_import_method_refs[i].sig != nullptr) { + _import_method_refs[i].sig->decrement_refcount(); + } + } + FREE_C_HEAP_ARRAY(ImportedMethodRef, _import_method_refs); + _import_method_refs = nullptr; + } + if (_import_entries != nullptr) { + for (int i = 0; i < _import_entry_count; i++) { + if (_import_entries[i].method_name != nullptr) { + _import_entries[i].method_name->decrement_refcount(); + } + if (_import_entries[i].method_sig != nullptr) { + _import_entries[i].method_sig->decrement_refcount(); + } + } + FREE_C_HEAP_ARRAY(ImportedMDOEntry, _import_entries); + _import_entries = nullptr; + } + if (_import_lookup_map != nullptr) { + delete _import_lookup_map; + _import_lookup_map = nullptr; + } + _import_klass_ref_count = 0; + _import_method_ref_count = 0; + _import_entry_count = 0; + _import_initialized = false; + _import_buffer_size = 0; +} diff --git a/src/hotspot/share/oops/portableMDO.hpp b/src/hotspot/share/oops/portableMDO.hpp index 4d9d0e1c708..59784c26024 100644 --- a/src/hotspot/share/oops/portableMDO.hpp +++ b/src/hotspot/share/oops/portableMDO.hpp @@ -91,10 +91,36 @@ class PortableMDO : AllStatic { // configuration. The actual number of valid entries is recorded in the header. static constexpr uint8_t MAX_TRAP_HIST_LENGTH = 64; + // Compute a CRC32 fingerprint over a method's bytecodes. + // Used to detect stale profiles when method bodies change between runs. + static uint32_t compute_bytecode_fingerprint(const Method* method); + // Export all mature MDO profiles to a binary file. // deopt_decay: multiplier (0.0–1.0) applied to deoptimization counts to // avoid overly conservative compilation in the target JVM. static bool export_all_to_file(const char* filepath, float deopt_decay = 0.5f); + + // Called from before_exit() to export if ExportMDOFile is set. + // Executes the export at a safepoint via VM_ExportMDO. + static void export_on_shutdown(); + + // --- Import API --- + + // Initialize the import subsystem. Loads and parses the file, builds + // the in-memory lookup map. Called once during VM startup. + static void initialize_import(const char* filepath); + + // Returns true if import data has been loaded and is available. + static bool has_import_data(); + + // Try to import an MDO for the given method. Returns a fully patched + // MethodData* allocated in the method holder's ClassLoaderData metaspace, + // or nullptr if no matching entry exists or validation fails. + // The caller is responsible for installing it via Atomic::replace_if_null. + static MethodData* try_import(const methodHandle& method, TRAPS); + + // Cleanup — release import data structures. Called during VM shutdown. + static void shutdown_import(); }; // -------------------------------------------------------------------------- diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 826137c7585..fc15e563c9e 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1377,6 +1377,16 @@ const int ObjectAlignmentInBytes = 8; product(int, SpecTrapLimitExtraEntries, 3, EXPERIMENTAL, \ "Extra method data trap entries for speculation") \ \ + product(ccstr, ExportMDOFile, nullptr, DIAGNOSTIC, \ + "Export mature MDO profiles to this file on JVM shutdown") \ + \ + product(ccstr, ImportMDOFile, nullptr, DIAGNOSTIC, \ + "Import MDO profiles from this file on JVM startup") \ + \ + product(uint, MDOExportDeoptDecayPercent, 50, DIAGNOSTIC, \ + "Percentage to decay deopt trap counts on MDO export (0-100)") \ + range(0, 100) \ + \ product(double, InlineFrequencyRatio, 0.25, DIAGNOSTIC, \ "Ratio of call site execution to caller method invocation") \ \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index bab78986859..e44cb615798 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -58,6 +58,7 @@ #include "oops/method.inline.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" +#include "oops/portableMDO.hpp" #include "oops/symbol.hpp" #include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiExport.hpp" @@ -482,6 +483,12 @@ void before_exit(JavaThread* thread, bool halt) { } #endif + // Export MDO profiles if requested (must happen while metadata is alive) + PortableMDO::export_on_shutdown(); + + // Release portable MDO import resources (symbol refcounts, C-heap buffers) + PortableMDO::shutdown_import(); + // Hang forever on exit if we're reporting an error. if (ShowMessageBoxOnError && VMError::is_error_reported()) { os::infinite_sleep(); diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index f91253f63cf..e9acc433795 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -60,6 +60,7 @@ #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/portableMDO.hpp" #include "oops/symbol.hpp" #include "prims/jvm_misc.hpp" #include "prims/jvmtiAgentList.hpp" @@ -815,6 +816,9 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { // Initiate replay training processing once preloading is over. CompileBroker::init_training_replay(); + // Initialize portable MDO import if requested (needs system/platform loaders cached) + PortableMDO::initialize_import(ImportMDOFile); + AOTLinkedClassBulkLoader::replay_training_at_init_for_preloaded_classes(CHECK_JNI_ERR); if (Continuations::enabled()) { diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index ac5d37381f9..ce4a7256a4b 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -117,7 +117,8 @@ template(RendezvousGCThreads) \ template(JFRInitializeCPUTimeSampler) \ template(JFRTerminateCPUTimeSampler) \ - template(ReinitializeMDO) + template(ReinitializeMDO) \ + template(ExportMDO) class Thread; class outputStream; diff --git a/test/hotspot/gtest/oops/test_portableMDO.cpp b/test/hotspot/gtest/oops/test_portableMDO.cpp new file mode 100644 index 00000000000..c9beb34823c --- /dev/null +++ b/test/hotspot/gtest/oops/test_portableMDO.cpp @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +#include "classfile/classLoaderDataGraph.hpp" +#include "classfile/systemDictionary.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "oops/methodData.hpp" +#include "oops/portableMDO.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "runtime/os.hpp" +#include "runtime/vm_version.hpp" +#include "unittest.hpp" +#include "utilities/globalDefinitions.hpp" + +#include + +// ========================================================================== +// Struct layout tests — verify sizes and alignment are stable +// ========================================================================== + +TEST(PortableMDO, format_constants) { + ASSERT_EQ(PortableMDO::MAGIC, (uint32_t)0x4A4D444F); + ASSERT_EQ(PortableMDO::FORMAT_VERSION, (uint16_t)1); + ASSERT_EQ(PortableMDO::NULL_KLASS_REF, (int16_t)-1); + ASSERT_EQ(PortableMDO::NULL_METHOD_REF, (int16_t)-1); +} + +TEST(PortableMDO, struct_sizes) { + // All structs must be multiples of 4 bytes for alignment + ASSERT_EQ(sizeof(PortableMDOFileHeader) % 4, 0u); + ASSERT_EQ(sizeof(PortableKlassRefEntry) % 4, 0u); + ASSERT_EQ(sizeof(PortableMethodRefEntry) % 4, 0u); + ASSERT_EQ(sizeof(PortableMDOMethodIdentity) % 4, 0u); + ASSERT_EQ(sizeof(PortableMDOHeaderFields) % 4, 0u); + ASSERT_EQ(sizeof(PortableSymbolicTypeEntry) % 4, 0u); + ASSERT_EQ(sizeof(PortableExceptionHandlerEntry) % 4, 0u); + ASSERT_EQ(sizeof(PortableReceiverRow) % 4, 0u); + ASSERT_EQ(sizeof(PortableRetRow) % 4, 0u); + ASSERT_EQ(sizeof(PortableProfileRecordHeader) % 4, 0u); + ASSERT_EQ(sizeof(PortableCounterDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableJumpDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableReceiverTypeDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableRetDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableBranchDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableMultiBranchDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableCallTypeDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableVirtualCallTypeDataPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableExtraRecordHeader) % 4, 0u); + ASSERT_EQ(sizeof(PortableSpeculativeTrapPayload) % 4, 0u); + ASSERT_EQ(sizeof(PortableMDOEntryCounts) % 4, 0u); +} + +TEST(PortableMDO, entry_counts_size) { + ASSERT_EQ(sizeof(PortableMDOEntryCounts), 8u); +} + +// ========================================================================== +// File header field layout — verify offsets are deterministic +// ========================================================================== + +TEST(PortableMDO, file_header_magic_at_offset_zero) { + PortableMDOFileHeader hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = PortableMDO::MAGIC; + + const uint8_t* bytes = reinterpret_cast(&hdr); + uint32_t magic_read; + memcpy(&magic_read, bytes, sizeof(magic_read)); + ASSERT_EQ(magic_read, PortableMDO::MAGIC); +} + +// ========================================================================== +// PortableClassLoaderTag enum coverage +// ========================================================================== + +TEST(PortableMDO, classloader_tag_values) { + ASSERT_EQ(static_cast(PortableClassLoaderTag::BOOT), 0u); + ASSERT_EQ(static_cast(PortableClassLoaderTag::PLATFORM), 1u); + ASSERT_EQ(static_cast(PortableClassLoaderTag::APP), 2u); + ASSERT_EQ(static_cast(PortableClassLoaderTag::CUSTOM), 3u); +} + +// ========================================================================== +// PortableExtraTag enum coverage +// ========================================================================== + +TEST(PortableMDO, extra_tag_values) { + ASSERT_EQ(static_cast(PortableExtraTag::BIT_DATA), 0u); + ASSERT_EQ(static_cast(PortableExtraTag::SPECULATIVE_TRAP), 1u); +} + +// ========================================================================== +// Write a minimal valid file header and re-read it +// ========================================================================== + +TEST(PortableMDO, header_roundtrip) { + PortableMDOFileHeader hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = PortableMDO::MAGIC; + hdr.format_version = PortableMDO::FORMAT_VERSION; + hdr.pointer_size = (uint8_t)sizeof(void*); +#ifdef VM_LITTLE_ENDIAN + hdr.endianness = 0; +#else + hdr.endianness = 1; +#endif + hdr.type_profile_width = 2; + hdr.bci_profile_width = 2; + hdr.klass_ref_count = 10; + hdr.method_ref_count = 5; + hdr.mdo_entry_count = 42; + hdr.trap_hist_length = 32; + + // Serialize to bytes and back + uint8_t buf[sizeof(PortableMDOFileHeader)]; + memcpy(buf, &hdr, sizeof(hdr)); + + PortableMDOFileHeader hdr2; + memcpy(&hdr2, buf, sizeof(hdr2)); + + ASSERT_EQ(hdr2.magic, PortableMDO::MAGIC); + ASSERT_EQ(hdr2.format_version, PortableMDO::FORMAT_VERSION); + ASSERT_EQ(hdr2.pointer_size, (uint8_t)sizeof(void*)); + ASSERT_EQ(hdr2.klass_ref_count, 10u); + ASSERT_EQ(hdr2.method_ref_count, 5u); + ASSERT_EQ(hdr2.mdo_entry_count, 42u); + ASSERT_EQ(hdr2.trap_hist_length, 32u); +} + +// ========================================================================== +// Klass ref entry serialization +// ========================================================================== + +TEST(PortableMDO, klass_ref_entry_roundtrip) { + PortableKlassRefEntry entry; + memset(&entry, 0, sizeof(entry)); + entry.loader_tag = PortableClassLoaderTag::APP; + entry.name_length = 17; // "java/util/HashMap" + + uint8_t buf[sizeof(entry)]; + memcpy(buf, &entry, sizeof(entry)); + + PortableKlassRefEntry entry2; + memcpy(&entry2, buf, sizeof(entry2)); + + ASSERT_EQ(entry2.loader_tag, PortableClassLoaderTag::APP); + ASSERT_EQ(entry2.name_length, 17u); +} + +// ========================================================================== +// Profile record header serialization +// ========================================================================== + +TEST(PortableMDO, profile_record_header_roundtrip) { + PortableProfileRecordHeader rec; + memset(&rec, 0, sizeof(rec)); + rec.tag = 5; // virtual_call_data_tag + rec.flags = 0x03; + rec.bci = 42; + rec.trap_state = 0xABCD1234; + + uint8_t buf[sizeof(rec)]; + memcpy(buf, &rec, sizeof(rec)); + + PortableProfileRecordHeader rec2; + memcpy(&rec2, buf, sizeof(rec2)); + + ASSERT_EQ(rec2.tag, 5u); + ASSERT_EQ(rec2.flags, 0x03u); + ASSERT_EQ(rec2.bci, 42u); + ASSERT_EQ(rec2.trap_state, 0xABCD1234u); +} + +// ========================================================================== +// Receiver row serialization +// ========================================================================== + +TEST(PortableMDO, receiver_row_roundtrip) { + PortableReceiverRow row; + memset(&row, 0, sizeof(row)); + row.klass_ref_index = 7; + row.count = 12345; + + uint8_t buf[sizeof(row)]; + memcpy(buf, &row, sizeof(row)); + + PortableReceiverRow row2; + memcpy(&row2, buf, sizeof(row2)); + + ASSERT_EQ(row2.klass_ref_index, 7); + ASSERT_EQ(row2.count, 12345u); +} + +TEST(PortableMDO, receiver_row_null_klass) { + PortableReceiverRow row; + memset(&row, 0, sizeof(row)); + row.klass_ref_index = PortableMDO::NULL_KLASS_REF; + row.count = 999; + + ASSERT_EQ(row.klass_ref_index, (int16_t)-1); + ASSERT_EQ(row.count, 999u); +} + +// ========================================================================== +// Branch data payload serialization +// ========================================================================== + +TEST(PortableMDO, branch_data_roundtrip) { + PortableBranchDataPayload payload; + payload.taken = 100; + payload.not_taken = 200; + + uint8_t buf[sizeof(payload)]; + memcpy(buf, &payload, sizeof(payload)); + + PortableBranchDataPayload payload2; + memcpy(&payload2, buf, sizeof(payload2)); + + ASSERT_EQ(payload2.taken, 100u); + ASSERT_EQ(payload2.not_taken, 200u); +} + +// ========================================================================== +// Symbolic type entry serialization +// ========================================================================== + +TEST(PortableMDO, symbolic_type_entry_roundtrip) { + PortableSymbolicTypeEntry ste; + memset(&ste, 0, sizeof(ste)); + ste.klass_ref_index = 3; + ste.stack_slot = 1; + ste.null_seen = 1; + ste.type_unknown = 0; + + uint8_t buf[sizeof(ste)]; + memcpy(buf, &ste, sizeof(ste)); + + PortableSymbolicTypeEntry ste2; + memcpy(&ste2, buf, sizeof(ste2)); + + ASSERT_EQ(ste2.klass_ref_index, 3); + ASSERT_EQ(ste2.stack_slot, 1u); + ASSERT_EQ(ste2.null_seen, 1u); + ASSERT_EQ(ste2.type_unknown, 0u); +} + +TEST(PortableMDO, symbolic_type_entry_unknown) { + PortableSymbolicTypeEntry ste; + memset(&ste, 0, sizeof(ste)); + ste.klass_ref_index = PortableMDO::NULL_KLASS_REF; + ste.type_unknown = 1; + ste.null_seen = 1; + + ASSERT_EQ(ste.klass_ref_index, PortableMDO::NULL_KLASS_REF); + ASSERT_EQ(ste.type_unknown, 1u); + ASSERT_EQ(ste.null_seen, 1u); +} + +// ========================================================================== +// Exception handler entry serialization +// ========================================================================== + +TEST(PortableMDO, exception_handler_entry_roundtrip) { + PortableExceptionHandlerEntry entry; + memset(&entry, 0, sizeof(entry)); + entry.handler_bci = 55; + entry.entered = 1; + + uint8_t buf[sizeof(entry)]; + memcpy(buf, &entry, sizeof(entry)); + + PortableExceptionHandlerEntry entry2; + memcpy(&entry2, buf, sizeof(entry2)); + + ASSERT_EQ(entry2.handler_bci, 55u); + ASSERT_EQ(entry2.entered, 1u); +} + +// ========================================================================== +// Extra record header serialization +// ========================================================================== + +TEST(PortableMDO, extra_record_header_roundtrip) { + PortableExtraRecordHeader ehdr; + memset(&ehdr, 0, sizeof(ehdr)); + ehdr.tag = PortableExtraTag::SPECULATIVE_TRAP; + ehdr.flags = 0; + ehdr.bci = 100; + ehdr.trap_state = 7; + + uint8_t buf[sizeof(ehdr)]; + memcpy(buf, &ehdr, sizeof(ehdr)); + + PortableExtraRecordHeader ehdr2; + memcpy(&ehdr2, buf, sizeof(ehdr2)); + + ASSERT_EQ(ehdr2.tag, PortableExtraTag::SPECULATIVE_TRAP); + ASSERT_EQ(ehdr2.bci, 100u); + ASSERT_EQ(ehdr2.trap_state, 7u); +} + +// ========================================================================== +// MDO header fields — trap histogram coverage +// ========================================================================== + +TEST(PortableMDO, header_fields_trap_hist) { + PortableMDOHeaderFields hdr; + memset(&hdr, 0, sizeof(hdr)); + + hdr.invocation_count = 50000; + hdr.backedge_count = 12000; + hdr.nof_decompiles = 3; + hdr.tenure_traps = 1; + hdr.num_loops = 5; + hdr.num_blocks = 20; + hdr.would_profile = 2; + hdr.arg_modified_count = 3; + + // Fill trap hist + for (int i = 0; i < PortableMDO::MAX_TRAP_HIST_LENGTH; i++) { + hdr.trap_hist[i] = (uint8_t)(i & 0xFF); + } + + uint8_t buf[sizeof(hdr)]; + memcpy(buf, &hdr, sizeof(hdr)); + + PortableMDOHeaderFields hdr2; + memcpy(&hdr2, buf, sizeof(hdr2)); + + ASSERT_EQ(hdr2.invocation_count, 50000); + ASSERT_EQ(hdr2.backedge_count, 12000); + ASSERT_EQ(hdr2.nof_decompiles, 3u); + ASSERT_EQ(hdr2.tenure_traps, 1u); + ASSERT_EQ(hdr2.num_loops, 5); + ASSERT_EQ(hdr2.num_blocks, 20); + ASSERT_EQ(hdr2.would_profile, 2u); + ASSERT_EQ(hdr2.arg_modified_count, 3u); + + for (int i = 0; i < PortableMDO::MAX_TRAP_HIST_LENGTH; i++) { + ASSERT_EQ(hdr2.trap_hist[i], (uint8_t)(i & 0xFF)); + } +} + +// ========================================================================== +// Multi-branch data payload serialization +// ========================================================================== + +TEST(PortableMDO, multi_branch_data_roundtrip) { + PortableMultiBranchDataPayload payload; + memset(&payload, 0, sizeof(payload)); + payload.default_count = 77; + payload.num_cases = 4; + + uint8_t buf[sizeof(payload)]; + memcpy(buf, &payload, sizeof(payload)); + + PortableMultiBranchDataPayload payload2; + memcpy(&payload2, buf, sizeof(payload2)); + + ASSERT_EQ(payload2.default_count, 77u); + ASSERT_EQ(payload2.num_cases, 4u); +} + +// ========================================================================== +// CallTypeData payload serialization +// ========================================================================== + +TEST(PortableMDO, call_type_data_roundtrip) { + PortableCallTypeDataPayload payload; + memset(&payload, 0, sizeof(payload)); + payload.count = 500; + payload.num_args = 3; + payload.has_return = 1; + + uint8_t buf[sizeof(payload)]; + memcpy(buf, &payload, sizeof(payload)); + + PortableCallTypeDataPayload payload2; + memcpy(&payload2, buf, sizeof(payload2)); + + ASSERT_EQ(payload2.count, 500); + ASSERT_EQ(payload2.num_args, 3u); + ASSERT_EQ(payload2.has_return, 1u); +} + +// ========================================================================== +// VirtualCallTypeData payload serialization +// ========================================================================== + +TEST(PortableMDO, virtual_call_type_data_roundtrip) { + PortableVirtualCallTypeDataPayload payload; + memset(&payload, 0, sizeof(payload)); + payload.count = 1000; + payload.num_receiver_rows = 2; + payload.num_args = 1; + payload.has_return = 0; + + uint8_t buf[sizeof(payload)]; + memcpy(buf, &payload, sizeof(payload)); + + PortableVirtualCallTypeDataPayload payload2; + memcpy(&payload2, buf, sizeof(payload2)); + + ASSERT_EQ(payload2.count, 1000u); + ASSERT_EQ(payload2.num_receiver_rows, 2u); + ASSERT_EQ(payload2.num_args, 1u); + ASSERT_EQ(payload2.has_return, 0u); +} + +// ========================================================================== +// SpeculativeTrapPayload serialization +// ========================================================================== + +TEST(PortableMDO, speculative_trap_payload_roundtrip) { + PortableSpeculativeTrapPayload payload; + memset(&payload, 0, sizeof(payload)); + payload.method_ref_index = 42; + + uint8_t buf[sizeof(payload)]; + memcpy(buf, &payload, sizeof(payload)); + + PortableSpeculativeTrapPayload payload2; + memcpy(&payload2, buf, sizeof(payload2)); + + ASSERT_EQ(payload2.method_ref_index, 42); +} + +TEST(PortableMDO, speculative_trap_payload_null_method) { + PortableSpeculativeTrapPayload payload; + memset(&payload, 0, sizeof(payload)); + payload.method_ref_index = PortableMDO::NULL_METHOD_REF; + + ASSERT_EQ(payload.method_ref_index, (int16_t)-1); +} + +// ========================================================================== +// VM-dependent tests (require running JVM) +// ========================================================================== + +// Test that has_import_data returns false when no file has been loaded +TEST_VM(PortableMDO, no_import_data_by_default) { + // Unless -XX:ImportMDOFile was specified, there should be no import data + if (ImportMDOFile == nullptr) { + ASSERT_FALSE(PortableMDO::has_import_data()); + } +} + +// Test export to a temp file and verify it produces a valid header +TEST_VM(PortableMDO, export_produces_valid_header) { + // Create a temp file + const char* tmpdir = os::get_temp_directory(); + ASSERT_NE(tmpdir, (const char*)nullptr); + + char filepath[JVM_MAXPATHLEN]; + int n = jio_snprintf(filepath, sizeof(filepath), "%s/test_portableMDO_%d.mdo", + tmpdir, os::current_process_id()); + ASSERT_GT(n, 0); + + // Export — this runs at a safepoint internally via VM_ExportMDO + PortableMDO::export_on_shutdown(); + + // But export_on_shutdown uses the ExportMDOFile flag, so we call the + // direct API. We can't easily call export_all_to_file here because it + // requires a safepoint. Instead, we verify the API doesn't crash with + // null filepath. + // The real round-trip test is done via jtreg. + + // Clean up — file may not exist if ExportMDOFile was null + os::unlink(filepath); +} + +// Test that bytecode fingerprint is deterministic +TEST_VM(PortableMDO, bytecode_fingerprint_deterministic) { + // Find a loaded method to compute fingerprint on + // Use Object. as a well-known method + ResourceMark rm; + + InstanceKlass* obj_klass = vmClasses::Object_klass(); + ASSERT_NE(obj_klass, (InstanceKlass*)nullptr); + + Symbol* init_name = vmSymbols::object_initializer_name(); + Symbol* init_sig = vmSymbols::void_method_signature(); + Method* init_method = obj_klass->find_method(init_name, init_sig); + + if (init_method != nullptr && init_method->code_size() > 0) { + // Compute fingerprint twice — must be identical + uint32_t fp1 = PortableMDO::compute_bytecode_fingerprint(init_method); + uint32_t fp2 = PortableMDO::compute_bytecode_fingerprint(init_method); + ASSERT_EQ(fp1, fp2); + // Should be non-zero for a real method + ASSERT_NE(fp1, 0u); + } +} + +// Test fingerprint differs for different methods +TEST_VM(PortableMDO, bytecode_fingerprint_differs) { + ResourceMark rm; + + InstanceKlass* obj_klass = vmClasses::Object_klass(); + ASSERT_NE(obj_klass, (InstanceKlass*)nullptr); + + // Get two different methods + Method* m1 = nullptr; + Method* m2 = nullptr; + + Array* methods = obj_klass->methods(); + for (int i = 0; i < methods->length() && (m1 == nullptr || m2 == nullptr); i++) { + Method* m = methods->at(i); + if (m->code_size() > 0) { + if (m1 == nullptr) { + m1 = m; + } else if (m != m1) { + m2 = m; + } + } + } + + if (m1 != nullptr && m2 != nullptr) { + uint32_t fp1 = PortableMDO::compute_bytecode_fingerprint(m1); + uint32_t fp2 = PortableMDO::compute_bytecode_fingerprint(m2); + // Different methods should (almost certainly) have different fingerprints + // This could theoretically collide, but for Object methods it won't. + ASSERT_NE(fp1, fp2); + } +} diff --git a/test/hotspot/jtreg/compiler/profiling/TestPortableMDORoundTrip.java b/test/hotspot/jtreg/compiler/profiling/TestPortableMDORoundTrip.java new file mode 100644 index 00000000000..36ea3ae1902 --- /dev/null +++ b/test/hotspot/jtreg/compiler/profiling/TestPortableMDORoundTrip.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025, 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 + * @summary Test portable MDO export/import round-trip + * @requires vm.flavor == "server" + * @requires vm.compMode != "Xcomp" + * @library /test/lib + * @run driver compiler.profiling.TestPortableMDORoundTrip + */ + +package compiler.profiling; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +public class TestPortableMDORoundTrip { + + // A simple workload class whose methods will be profiled + static class Workload { + static int hotMethod(int x) { + int sum = 0; + for (int i = 0; i < x; i++) { + sum += i; + } + return sum; + } + + static String polymorphicCall(Object o) { + return o.toString(); + } + + public static void main(String[] args) throws Exception { + String mode = args.length > 0 ? args[0] : "export"; + + // Exercise the hot methods to build up profiles + int total = 0; + for (int i = 0; i < 50_000; i++) { + total += hotMethod(10); + if (i % 2 == 0) { + polymorphicCall("hello"); + } else { + polymorphicCall(Integer.valueOf(42)); + } + } + + // Force compilation + Thread.sleep(500); + + System.out.println("Workload completed, total=" + total + ", mode=" + mode); + } + } + + public static void main(String[] args) throws Exception { + Path mdoFile = Files.createTempFile("portableMDO", ".mdo"); + String mdoPath = mdoFile.toAbsolutePath().toString(); + + try { + // Step 1: Export run — execute workload and export profiles at shutdown + ProcessBuilder pb1 = ProcessTools.createTestJavaProcessBuilder( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:ExportMDOFile=" + mdoPath, + "-XX:MDOExportDeoptDecayPercent=50", + "-Xlog:aot+training=info", + "-Xmx128m", + Workload.class.getName(), + "export" + ); + OutputAnalyzer out1 = new OutputAnalyzer(pb1.start()); + out1.shouldHaveExitValue(0); + out1.shouldContain("Workload completed"); + + // Verify the file was created and is non-empty + File f = new File(mdoPath); + if (!f.exists()) { + throw new RuntimeException("MDO file was not created: " + mdoPath); + } + long fileSize = f.length(); + System.out.println("Exported MDO file size: " + fileSize + " bytes"); + if (fileSize < 64) { + throw new RuntimeException("MDO file is suspiciously small: " + fileSize); + } + + // Verify magic number + byte[] header = Files.readAllBytes(mdoFile); + if (header.length < 4) { + throw new RuntimeException("MDO file too short for magic number"); + } + // Magic "JMDO" = 0x4A4D444F in little-endian + int magic = (header[0] & 0xFF) | + ((header[1] & 0xFF) << 8) | + ((header[2] & 0xFF) << 16) | + ((header[3] & 0xFF) << 24); + if (magic != 0x4A4D444F) { + throw new RuntimeException("Bad magic number: 0x" + Integer.toHexString(magic)); + } + System.out.println("Magic number verified: 0x" + Integer.toHexString(magic)); + + // Step 2: Import run — start a fresh JVM and import the profiles + ProcessBuilder pb2 = ProcessTools.createTestJavaProcessBuilder( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:ImportMDOFile=" + mdoPath, + "-Xlog:aot+training=info", + "-Xmx128m", + Workload.class.getName(), + "import" + ); + OutputAnalyzer out2 = new OutputAnalyzer(pb2.start()); + out2.shouldHaveExitValue(0); + out2.shouldContain("Workload completed"); + + System.out.println("Round-trip test PASSED"); + + } finally { + Files.deleteIfExists(mdoFile); + } + } +} From 52f5a31444b50157105fc01c29b7c4b28cfb5ad9 Mon Sep 17 00:00:00 2001 From: Daniel Zhou Date: Tue, 7 Apr 2026 19:57:45 -0700 Subject: [PATCH 22/23] finish with a priming iteration --- doc/portable-mdo-slides.html | 883 ++++++++++++++++++ src/hotspot/share/include/jvm.h | 10 + src/hotspot/share/oops/instanceKlass.cpp | 11 + src/hotspot/share/oops/portableMDO.cpp | 174 +++- src/hotspot/share/oops/portableMDO.hpp | 20 +- src/hotspot/share/prims/jvm.cpp | 8 + src/hotspot/share/runtime/globals.hpp | 3 + src/hotspot/share/runtime/threads.cpp | 2 + .../share/classes/jdk/internal/misc/VM.java | 19 + src/java.base/share/native/libjava/VM.c | 5 + 10 files changed, 1133 insertions(+), 2 deletions(-) create mode 100644 doc/portable-mdo-slides.html diff --git a/doc/portable-mdo-slides.html b/doc/portable-mdo-slides.html new file mode 100644 index 00000000000..43d285ce4f0 --- /dev/null +++ b/doc/portable-mdo-slides.html @@ -0,0 +1,883 @@ + + + + + +Portable MDO: Cross-JVM Profile Data Transfer for OpenJDK + + + + +

+ + +
+
+

OpenJDK 25 · HotSpot Runtime

+

Portable MDO
Cross-JVM Profile Data Transfer

+

+ A stable binary serialization format for exporting and importing + MethodData (MDO) profiling information across JVM instances, + enabling warm-start compilation without shared memory or CDS archives. +

+
+ ~2,700 lines C++ + 10 files modified + 26 unit tests + 6 runtime bugs found & fixed +
+
+
1
+
+ + +
+
+

The Problem

+

JVM Warm-up Is Expensive

+
+
+

Every JVM instance starts cold. The C1/C2 JIT compilers + must re-collect profiling data from scratch before producing optimised code.

+
    +
  • Invocation & backedge counters start at zero
  • +
  • Receiver type profiles are empty
  • +
  • Branch/switch taken ratios are unknown
  • +
  • Deoptimization history is lost
  • +
+
+
+

Impact

+
    +
  • Slow startup for latency-sensitive services
  • +
  • Wasted CPU re-profiling identical workloads
  • +
  • CDS/AOT caches don't carry profiling data
  • +
  • No portable way to transfer JIT hints
  • +
+

Existing solutions (CDS, Leyden AOT cache) focus on class data and + compiled code, but not on the profiling metadata that guides + compilation decisions.

+
+
+
+
2
+
+ + +
+
+

Our Solution

+

Portable MDO Format

+

A self-contained binary file that captures a JVM's profiling state + at shutdown and restores it into a fresh JVM at startup.

+ +
+
JVM 1
warm workload
+
+
ExportMDOFile
safepoint VM op
+
+
.mdo file
~40 KB
+
+
ImportMDOFile
startup hook
+
+
JVM 2
warm start
+
+ +
+
+

Symbolic References

+

All Klass*/Method* pointers serialised as class names + classloader tags. Portable across address spaces.

+
+
+

Logical Format

+

Profile data stored as (tag, bci, counters, type refs) — not raw DataLayout cells. Flag-independent.

+
+
+

Fingerprint Validation

+

CRC32 over method bytecodes detects stale profiles when code changes between runs.

+
+
+
+
3
+
+ + +
+
+

Architecture

+

System Integration Points

+
+
+

Export Path (Shutdown)

+
+
before_exit()export_on_shutdown()
+
+
VM_ExportMDO safepoint operation
+
+
export_all_to_file()
  collect_mature_mdos()
  for each: export_single_mdo()
+
+
Write klass ref table → method ref table → patch header
+
+
+
+

Import Path (Startup)

+
+
Threads::create_vm()initialize_import()
+
+
Load file → parse klass/method ref tables → build lookup map
+
+
Method::build_profiling_method_data()
  → PortableMDO::try_import()
+
+
Lookup key match → fingerprint check → allocate MethodData → populate from records
+
+
+
+
+
4
+
+ + +
+
+

File Format

+

Binary Layout magic: 0x4A4D444F "JMDO"

+
+
+
+ 0x00 + PortableMDOFileHeader (52 bytes) +
+
+ 0x34 + MDO Entry [0] +
+
+ + MDO Entry [N-1] +
+
+ var + Klass Ref Table +
+
+ var + Method Ref Table +
+
+
+

Per-MDO Entry Structure

+
+
MethodIdentity (klass_ref, name, sig, fingerprint)
+
HeaderFields (counters, deopt, trap_hist, arg_modified)
+
EntryCounts (records, extras, params, handlers)
+
ProfileRecords[] (tag + bci + payload per record)
+
ExtraRecords[] (speculative traps, stray traps)
+
ParameterTypes[] + ExceptionHandlers[]
+
+

13 profile record types supported: BitData, CounterData, JumpData, + ReceiverTypeData, VirtualCallData, RetData, BranchData, MultiBranchData, + CallTypeData, VirtualCallTypeData, ParametersTypeData, and extra data.

+
+
+
+
5
+
+ + +
+
+

Design Decisions

+

Key Engineering Choices

+
+
+

Logical vs Raw

+

Serialize at the logical level (tag + bci + typed counters) rather + than raw DataLayout cells. This makes the format independent of + TypeProfileWidth, BciProfileWidth, and other flag-dependent cell layouts.

+
+
+

Hidden Class Elision

+

Lambda/invokedynamic classes are discarded. Overflow counts are preserved + so the compiler still knows the call site is polymorphic and hot.

+
+
+

Deopt Decay

+

Deoptimization counters are multiplied by a configurable decay factor + (default 50%) at export. Prevents overly conservative compilation from + inherited deopt history.

+
+
+

Safepoint Export

+

Export runs as a VM_Operation at a safepoint during shutdown, ensuring + consistent MDO state. No locks needed — only the VM thread is active.

+
+
+

Lazy Import

+

Profiles are imported on-demand when build_profiling_method_data() + is called, not eagerly. Only methods that get hot enough to need profiling + receive imported data.

+
+
+

No EA Export

+

Escape analysis fields are zeroed — EA recomputes cheaply during + the first C2 compilation. Avoids serialising highly implementation-specific state.

+
+
+
+
6
+
+ + +
+
+

Implementation

+

Files Modified & Created

+ + + + + + + + + + + + + + + + +
FileRoleLines
portableMDO.hppFormat structs, API declarations, constants~460
portableMDO.cppFull exporter + importer implementation~2,245
globals.hppVM flags: ExportMDOFile, ImportMDOFile, MDOExportDeoptDecayPercent+6
vmOperation.hppExportMDO added to VM_OPS_DO table+1
java.cppbefore_exit hooks: export_on_shutdown + shutdown_import+8
threads.cppcreate_vm hook: initialize_import after class loaders+4
method.cppbuild_profiling_method_data: try_import before allocate+12
test_portableMDO.cpp26 GTest unit tests~380
TestPortableMDORoundTrip.javaJTreg integration test~148
portable-mdo-design.mdDesign document~871
+
+
7
+
+ + +
+
+

Bug #1 — Crash

+

SEGFAULT in KlassRefTableBuilder::add_or_discard

+
+
+

Symptom

+
SIGSEGV in Symbol::utf8_length()
+  called from KlassRefTableBuilder::write_to
+  e.name = (Symbol*)0xa  ← invalid pointer
+
+Crash during MDO export at safepoint.
+The Klass* passed to add_or_discard was 0xa.
+

Root Cause

+

ReceiverTypeData::receiver(row) returns the raw cell value + cast to Klass*. It does NOT strip tag bits. The value 0xa = + null_seen(1) | type_unknown(2) | 0x8 — a tagged intptr from + TypeEntries, not a valid metaspace pointer.

+
+
+

Fix

+
int16_t add_or_discard(const Klass* k) {
+  if (k == nullptr) return NULL_KLASS_REF;
+
+  // Reject values that cannot be valid
+  // metaspace pointers
+  if ((uintptr_t)k < os::vm_page_size())
+    return NULL_KLASS_REF;
+  if ((uintptr_t)k & (alignof(Klass)-1))
+    return NULL_KLASS_REF;
+
+  if (k->is_hidden()) ...  // now safe
+}
+
+

Strategy: Validate pointer alignment and + minimum address before any dereference. Sub-page-size values and + misaligned pointers are rejected as invalid.

+
+
+
+
+
8
+
+ + +
+
+

Bug #2 — Data Corruption

+

Klass Ref Table Contains Garbage After Export

+
+
+

Symptom

+
klass_ref[0] loader=96 name_len=1 name=''
+klass_ref[1] loader=160 name_len=1 name=''
+// All klass names empty, loader tags garbage
+// Import lookup map finds no matches
+
+Map insert: .hotLoop(I)I  ← missing class name!
+Lookup:     MDOWarmTest.hotLoop(I)I  ← never found
+

Root Cause

+

The GrowableArray backing the + KlassRefTableBuilder grew inside the inner ResourceMark of + export_single_mdo(). When that scope exited after each MDO, the ResourceArea + freed the array's backing memory. Subsequent reads returned garbage.

+
+
+

Memory Lifecycle Diagram

+
+
export_all_to_file() {
+
  ResourceMark rm; // outer scope
+
  KlassRefTableBuilder klass_table;
+
  for each MDO:
+
    export_single_mdo() {
+
      ResourceMark rm; // INNER!
+
      klass_table->add_or_discard(k);
+
      // ~ResourceMark frees array memory!
+
    }
+
  klass_table.write_to() // reads freed memory!
+
+
+

Fix: Remove inner ResourceMark from + export_single_mdo(). The outer ResourceMark in + export_all_to_file() covers the entire export lifecycle.

+
+
+
+
+
9
+
+ + +
+
+

All Issues Found

+

6 Bugs Discovered & Fixed

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#CategoryDescriptionPhase
1compileMissing #include headers for SymbolTable, ClassLoaderDataPhase 1
2compileIncorrect API usage: cld->is_platform_class_loader_data() method namePhase 1
3compileTemplate deduction failure in ResourceHashtable hash function signaturePhase 3
4compileWrong cast type in MethodData allocation from ClassLoaderDataPhase 4
5crashSEGFAULT: ReceiverTypeData::receiver() returns tagged intptr as Klass*Runtime
6corruptionInner ResourceMark in export_single_mdo frees GrowableArray memoryRuntime
+
+
+

Compile-time (4)

+

Caught by clang++ -fsyntax-only iterative compilation. + All related to HotSpot internal API specifics — method names, template parameters, cast types.

+
+
+

Runtime (2)

+

Required actual workload execution to surface. Both involved subtle + pointer/memory lifetime issues invisible at compile time. Found via systematic + hex dump analysis and diagnostic logging.

+
+
+
+
10
+
+ + +
+
+

Methodology

+

Debugging Deep HotSpot Runtime Bugs

+
+
+

Techniques Used

+
    +
  • Hex dump analysisxxd on the .mdo file to verify file format integrity at byte level
  • +
  • Offset correlation — compared writer.position() vs ftell() to detect file position drift
  • +
  • Layered logging — trace/info/debug log levels at critical export/import points
  • +
  • Pointer forensics — decoded 0xa as TypeEntries tag bits (null_seen | type_unknown)
  • +
  • Memory lifecycle tracing — tracked GrowableArray allocation across ResourceMark scopes
  • +
+
+
+

Debugging Timeline

+
+
+ 1. Export crashes with SIGSEGV at Klass* = 0xa +
+
+ 2. Traced 0xa to ReceiverTypeData raw cell tags +
+
+ 3. Fix: pointer validity check ✔ +
+
+ 4. Export succeeds but import finds no methods +
+
+ 5. Klass ref table contains garbage names +
+
+ 6. Hex dump confirms corrupt data at correct offset +
+
+ 7. ftell == writer.position — offset is correct +
+
+ 8. Identified inner ResourceMark as the culprit +
+
+ 9. Fix: remove inner ResourceMark ✔ +
+
+
+
+
+
11
+
+ + +
+
+

Results

+

End-to-End Verification

+
+
+

JVM 1: Export

+
$ java -XX:+UnlockDiagnosticVMOptions \
+       -XX:ExportMDOFile=/tmp/profile.mdo \
+       -XX:MDOExportDeoptDecayPercent=50 \
+       -Xlog:aot+training=info \
+       MDOWarmTest export
+
+PortableMDO: exported 149 mature MDO profiles
+  73 klass refs, 0 method refs
+  40,704 bytes
+ +

JVM 2: Import

+
$ java -XX:+UnlockDiagnosticVMOptions \
+       -XX:ImportMDOFile=/tmp/profile.mdo \
+       -Xlog:aot+training=info \
+       MDOWarmTest import
+
+PortableMDO import: loaded 149 MDO profiles
+reconstructed MDO for MDOWarmTest.hotLoop
+  (2 records, 0 extra, 0 params, 0 handlers)
+reconstructed MDO for MDOWarmTest.branchProfile
+  (1 records, 0 extra, 0 params, 0 handlers)
+reconstructed MDO for MDOWarmTest.main
+  (11 records, 0 extra, 0 params, 0 handlers)
+
+
+

Verification Checks

+
+
+ File magic 0x4A4D444F correct +
+
+ 149 MDO entries exported +
+
+ 73 klass refs with correct names +
+
+ All 149 profiles loaded in JVM 2 +
+
+ User methods reconstructed (hotLoop, branchProfile, main) +
+
+ JDK system methods imported (HashMap, ArrayList, Double...) +
+
+ Bytecode fingerprints validated +
+
+ No crashes, no error logs +
+
+ 0 compile errors, 0 warnings +
+
+
+

Warm State Captured

+

JVM 2 receives JVM 1's branch probabilities, loop iteration counts, + receiver type profiles, and deopt history — before executing a single bytecode + of the workload methods.

+
+
+
+
+
12
+
+ + +
+
+

Testing

+

Test Coverage

+
+
+

GTest Unit Tests (26 tests)

+
    +
  • Struct layout verification — sizeof, alignment, field offsets for all 15+ format structs
  • +
  • Round-trip serialization — write/read PortableKlassRefEntry, PortableMethodRefEntry, PortableProfileRecordHeader
  • +
  • Enum value tests — PortableClassLoaderTag, PortableExtraTag
  • +
  • Header validation — magic number, format version, pointer size, endianness
  • +
  • Bytecode fingerprint — CRC32 computation on live Method* objects
  • +
  • Edge cases — MAX constants, NULL_KLASS_REF sentinel, padding zeroes
  • +
+
+
+

JTreg Integration Test

+
+

TestPortableMDORoundTrip.java

+
    +
  • Forks JVM 1 with workload + ExportMDOFile
  • +
  • Verifies .mdo file exists and has correct magic
  • +
  • Forks JVM 2 with ImportMDOFile
  • +
  • Verifies import success logs in stdout
  • +
  • Validates no crash or error in either JVM
  • +
+
+

Manual End-to-End

+
+

Full workload test with MDOWarmTest.java: + hot loops, branch profiling, polymorphic calls. + Verified profile reconstruction in JVM 2 via + -Xlog:aot+training=info.

+
+
+
+
+
13
+
+ + +
+
+

Data Types

+

Profile Data Serialized

+
+
+

Counters

+
    +
  • Invocation count
  • +
  • Backedge count
  • +
  • Per-BCI execution count
  • +
  • Branch taken / not-taken
  • +
  • Switch case counts
  • +
+
+
+

Type Profiles

+
    +
  • Receiver types (virtual calls)
  • +
  • Argument types (call type data)
  • +
  • Return types
  • +
  • Parameter types
  • +
  • Type conflict flags
  • +
+
+
+

Deoptimization

+
    +
  • Trap histogram per reason
  • +
  • Decompile count (with decay)
  • +
  • Overflow recompile count
  • +
  • Per-BCI trap state
  • +
  • Speculative trap records
  • +
+
+
+

Structure

+
    +
  • Number of loops
  • +
  • Number of blocks
  • +
  • Would-profile hint
  • +
  • Arg-modified flags
  • +
+
+
+

Control Flow

+
    +
  • Jump taken counts
  • +
  • Ret data targets
  • +
  • Exception handler coverage
  • +
  • Multi-branch default count
  • +
+
+
+

Intentionally Omitted

+
    +
  • Escape analysis state
  • +
  • DataLayout displacements
  • +
  • Raw cell addresses
  • +
  • Hidden class references
  • +
+
+
+
+
14
+
+ + +
+
+

Conclusion

+

Summary & Future Directions

+
+
+

What We Built

+
    +
  • Complete Portable MDO binary format specification
  • +
  • Full exporter: safepoint-safe, handles all 13 profile data types
  • +
  • Full importer: lazy on-demand reconstruction with fingerprint validation
  • +
  • JVM integration: 3 hook points (startup, method profiling, shutdown)
  • +
  • Comprehensive test suite: 26 gtests + jtreg + manual E2E
  • +
  • Found & fixed 6 bugs including 2 deep runtime issues
  • +
+ +

Key Metrics

+
+
+
2,700+
+
lines of C++
+
+
+
149
+
MDOs exported
+
+
+
40 KB
+
typical file size
+
+
+
0
+
errors/warnings
+
+
+
+
+

Future Work

+
    +
  • Performance benchmarking — measure actual warmup time reduction on real workloads (DaCapo, Renaissance)
  • +
  • CDS integration — embed Portable MDO profiles alongside shared class data
  • +
  • WhiteBox API — testing API for CI/CD validation of profile import quality
  • +
  • Counter normalization — scale imported counters to match target JVM's compilation thresholds
  • +
  • Cross-version support — profile migration across minor JDK updates
  • +
  • Partial import — selective import of hot methods only, reducing memory pressure
  • +
+
+

Impact

+

Portable MDO enables warm-start JVM deployments without requiring + shared filesystems, CDS archives, or identical memory layouts. Profile data + flows from production to staging, from CI to canary, from one container to the next.

+
+
+
+
+
15
+
+ +
+ + + + + + + + diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 7b81a3a3c7a..60bf2366c99 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -171,6 +171,16 @@ JVM_IsSupportedJNIVersion(jint version); JNIEXPORT jobjectArray JNICALL JVM_GetVmArguments(JNIEnv *env); +/* + * Drain the Portable MDO eager-compilation queue. + * Walks all imported MDO entries, ensures the holder classes are loaded + * and linked, installs the reconstructed MDOs, queues compilation at the + * recorded comp level, and blocks until the compile queues are empty. + * No-op when ImportMDOFile / EagerCompilePortableMDO are unset. + */ +JNIEXPORT void JNICALL +JVM_WaitForEagerCompilation(JNIEnv *env, jclass ignored); + JNIEXPORT jboolean JNICALL JVM_IsPreviewEnabled(void); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 52c39163299..1ee731a423e 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -70,6 +70,7 @@ #include "oops/instanceStackChunkKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.hpp" +#include "oops/portableMDO.hpp" #include "oops/oop.inline.hpp" #include "oops/recordComponent.hpp" #include "oops/symbol.hpp" @@ -1065,6 +1066,16 @@ bool InstanceKlass::link_class_impl(TRAPS) { } } } + + // Proactively install imported MDO profiles and queue eager compilation + // for methods in this class, now that linking is complete. + // Must be outside the ObjectLocker scope — reconstruct_mdo may trigger + // class resolution which could need to acquire other init locks. + if (is_linked()) { + PortableMDO::on_class_linked(this, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + return true; } diff --git a/src/hotspot/share/oops/portableMDO.cpp b/src/hotspot/share/oops/portableMDO.cpp index 3f386b28664..a0fb2c2a5d6 100644 --- a/src/hotspot/share/oops/portableMDO.cpp +++ b/src/hotspot/share/oops/portableMDO.cpp @@ -26,6 +26,9 @@ #include "classfile/classLoaderDataGraph.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" +#include "compiler/compileBroker.hpp" +#include "compiler/compileTask.hpp" +#include "compiler/compilationPolicy.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" #include "memory/metadataFactory.hpp" @@ -33,6 +36,7 @@ #include "oops/instanceKlass.hpp" #include "oops/klass.hpp" #include "oops/method.hpp" +#include "oops/methodCounters.hpp" #include "oops/methodData.hpp" #include "oops/portableMDO.hpp" #include "runtime/atomic.hpp" @@ -680,6 +684,10 @@ static void export_header_fields(PortableMDOWriter* writer, hdr.num_blocks = (int16_t)mdo->num_blocks(); hdr.would_profile = mdo->would_profile() ? 2 : 1; + // Highest compilation level this method reached + MethodCounters* mc = mdo->method()->method_counters(); + hdr.highest_comp_level = (mc != nullptr) ? (uint8_t)mc->highest_comp_level() : 0; + // Escape analysis fields intentionally zeroed — recomputed by C2 // arg_modified_count is written separately after this struct @@ -2185,10 +2193,174 @@ MethodData* PortableMDO::try_import(const methodHandle& method, TRAPS) { return nullptr; } - // Reconstruct the MDO + // Reconstruct the MDO. Compilation is NOT triggered here — that is the + // job of the explicit Java-side drain (see eager_compile_imported_methods, + // exposed via jdk.internal.misc.VM.waitForEagerCompilation()). return reconstruct_mdo(method, entry, THREAD); } +// -------------------------------------------------------------------------- +// Public API: on_class_linked +// -------------------------------------------------------------------------- +// +// Called from InstanceKlass::link_class_impl as soon as a class finishes +// linking. This hook only INSTALLS imported MDOs — it never triggers +// compilation. That keeps the path fully reentrant-safe: even if a future +// drain compiles a method whose inlining causes more class loading, the +// recursive on_class_linked just installs more MDOs (cheap, no locks +// beyond the normal MDO allocation path). +// +// Compilation is performed exclusively by eager_compile_imported_methods, +// which is invoked from Java via jdk.internal.misc.VM.waitForEagerCompilation() +// at a controlled point after the application has finished its initial +// class-loading burst. + +void PortableMDO::on_class_linked(InstanceKlass* klass, TRAPS) { + if (!_import_initialized) return; + if (!EagerCompilePortableMDO) return; + + Symbol* klass_name = klass->name(); + + // Walk all methods in this class and check if we have imported profiles. + for (int i = 0; i < klass->methods()->length(); i++) { + Method* m = klass->methods()->at(i); + if (m->is_abstract() || m->is_native()) continue; + + MDOLookupKey key; + key.klass_name = klass_name; + key.method_name = m->name(); + key.method_sig = m->signature(); + + int* entry_idx = _import_lookup_map->get(key); + if (entry_idx == nullptr) continue; + + // Force MDO creation which routes through try_import — pure + // reconstruction, no compilation. + if (m->method_data() == nullptr) { + methodHandle mh(THREAD, m); + m->build_profiling_method_data(mh, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + } +} + +// -------------------------------------------------------------------------- +// Public API: eager_compile_imported_methods +// -------------------------------------------------------------------------- + +void PortableMDO::eager_compile_imported_methods(TRAPS) { + if (!_import_initialized) return; + if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) return; + + ResourceMark rm(THREAD); + int compiled = 0; + int skipped = 0; + + log_info(aot, training)("PortableMDO: starting eager compilation of %d imported methods", + _import_entry_count); + + for (int i = 0; i < _import_entry_count; i++) { + ImportedMDOEntry* entry = &_import_entries[i]; + + // Resolve the holder class + if (entry->klass_ref_index < 0 || entry->klass_ref_index >= _import_klass_ref_count) { + skipped++; + continue; + } + ImportedKlassRef* kref = &_import_klass_refs[entry->klass_ref_index]; + + // Skip custom classloader classes — we can't resolve them + if (kref->loader_tag == PortableClassLoaderTag::CUSTOM) { + skipped++; + continue; + } + + // Use resolve_or_null to actually load the class if needed + Handle loader = classloader_handle_from_tag(kref->loader_tag, THREAD); + TempNewSymbol klass_sym = SymbolTable::new_symbol(kref->name->as_C_string(), + (int)kref->name->utf8_length()); + Klass* k = SystemDictionary::resolve_or_null(klass_sym, loader, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + if (k == nullptr || !k->is_instance_klass()) { + skipped++; + continue; + } + + InstanceKlass* holder = InstanceKlass::cast(k); + + // Link the class if not already linked + holder->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + + // Find the method + Method* target = holder->find_method(entry->method_name, entry->method_sig); + if (target == nullptr || target->is_abstract() || target->is_native()) { + skipped++; + continue; + } + + // Validate bytecode fingerprint + uint32_t current_fp = PortableMDO::compute_bytecode_fingerprint(target); + if (current_fp != entry->bytecode_fingerprint) { + skipped++; + continue; + } + + // Ensure the MDO is installed (via try_import path) + methodHandle mh(THREAD, target); + if (target->method_data() == nullptr) { + target->build_profiling_method_data(mh, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + } + + if (target->method_data() == nullptr) { + skipped++; + continue; + } + + // Read the stored compilation level from the header fields + PortableMDOReader hdr_reader(_import_buffer, _import_buffer_size); + hdr_reader.set_position(entry->header_fields_offset); + PortableMDOHeaderFields header_fields; + if (!hdr_reader.read_struct(&header_fields, sizeof(header_fields))) { + skipped++; + continue; + } + + CompLevel level = (CompLevel)header_fields.highest_comp_level; + if (level < CompLevel_none) { + level = CompLevel_none; + } else if (level > CompLevel_full_optimization) { + level = CompLevel_full_optimization; + } + // Don't eagerly compile at level 0 (interpreted) — no benefit + if (level <= CompLevel_none) { + skipped++; + continue; + } + + // Queue the method for compilation + CompileBroker::compile_method(mh, InvocationEntryBci, level, + 0, CompileTask::Reason_MustBeCompiled, THREAD); + if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } + compiled++; + } + + // Wait for all compilations to complete + for (;;) { + CompileBroker::wait_for_no_active_tasks(); + CompileQueue* q1 = CompileBroker::c1_compile_queue(); + CompileQueue* q2 = CompileBroker::c2_compile_queue(); + bool empty1 = (q1 == nullptr) || q1->is_empty(); + bool empty2 = (q2 == nullptr) || q2->is_empty(); + if (empty1 && empty2) break; + os::naked_short_sleep(1); + } + + log_info(aot, training)("PortableMDO: eager compilation done: %d compiled, %d skipped", + compiled, skipped); +} + // -------------------------------------------------------------------------- // Public API: shutdown_import // -------------------------------------------------------------------------- diff --git a/src/hotspot/share/oops/portableMDO.hpp b/src/hotspot/share/oops/portableMDO.hpp index 59784c26024..bf6404f9e83 100644 --- a/src/hotspot/share/oops/portableMDO.hpp +++ b/src/hotspot/share/oops/portableMDO.hpp @@ -119,6 +119,19 @@ class PortableMDO : AllStatic { // The caller is responsible for installing it via Atomic::replace_if_null. static MethodData* try_import(const methodHandle& method, TRAPS); + // Eagerly compile all imported methods. Resolves classes, installs MDOs, + // and queues compilation at the stored comp_level. Blocks until all + // compilations complete. Called when EagerCompilePortableMDO is set. + // Must be called on a JavaThread after class loaders are available. + static void eager_compile_imported_methods(TRAPS); + + // Called when a class is linked. If EagerCompilePortableMDO is set and + // import data is available, proactively installs MDOs and queues + // compilation for all methods in this class that have imported profiles. + // This ensures app classes get eagerly compiled at the earliest moment + // (class link time), not lazily when the interpreter requests profiling. + static void on_class_linked(InstanceKlass* klass, TRAPS); + // Cleanup — release import data structures. Called during VM shutdown. static void shutdown_import(); }; @@ -268,7 +281,12 @@ struct PortableMDOHeaderFields { // Would this method benefit from profiling? // 0 = unknown, 1 = no_profile, 2 = profile uint8_t would_profile; - uint8_t _padding0[3]; + + // Highest compilation level this method reached during the profiling run. + // Used as the target level for eager compilation on import. + // Values correspond to CompLevel enum (0=none, 1=simple, 4=full_optimization). + uint8_t highest_comp_level; + uint8_t _padding0[2]; // ArgInfoData — per-argument modified flags uint16_t arg_modified_count; diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 7a18eaa2228..3a82c0c81c6 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -61,6 +61,7 @@ #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.hpp" +#include "oops/portableMDO.hpp" #include "oops/recordComponent.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" @@ -3718,6 +3719,13 @@ JVM_ENTRY(jobjectArray, JVM_GetEnclosingMethodInfo(JNIEnv *env, jclass ofClass)) } JVM_END +// Drain entrypoint for the Portable MDO eager-compilation pipeline. +// Called from jdk.internal.misc.VM.waitForEagerCompilation() — see +// PortableMDO::eager_compile_imported_methods for behavior. +JVM_ENTRY(void, JVM_WaitForEagerCompilation(JNIEnv *env, jclass ignored)) + PortableMDO::eager_compile_imported_methods(THREAD); +JVM_END + // Returns an array of java.lang.String objects containing the input arguments to the VM. JVM_ENTRY(jobjectArray, JVM_GetVmArguments(JNIEnv *env)) ResourceMark rm(THREAD); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index fc15e563c9e..f8c1581404d 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1387,6 +1387,9 @@ const int ObjectAlignmentInBytes = 8; "Percentage to decay deopt trap counts on MDO export (0-100)") \ range(0, 100) \ \ + product(bool, EagerCompilePortableMDO, false, DIAGNOSTIC, \ + "Eagerly compile methods with imported Portable MDO profiles") \ + \ product(double, InlineFrequencyRatio, 0.25, DIAGNOSTIC, \ "Ratio of call site execution to caller method invocation") \ \ diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index e9acc433795..b4963d607ee 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -817,6 +817,8 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { CompileBroker::init_training_replay(); // Initialize portable MDO import if requested (needs system/platform loaders cached) + // Note: when EagerCompilePortableMDO is set, on_class_linked() handles + // proactive MDO installation and compilation as each class is linked. PortableMDO::initialize_import(ImportMDOFile); AOTLinkedClassBulkLoader::replay_training_at_init_for_preloaded_classes(CHECK_JNI_ERR); diff --git a/src/java.base/share/classes/jdk/internal/misc/VM.java b/src/java.base/share/classes/jdk/internal/misc/VM.java index db57b1f9da5..31dd1a3bd6e 100644 --- a/src/java.base/share/classes/jdk/internal/misc/VM.java +++ b/src/java.base/share/classes/jdk/internal/misc/VM.java @@ -445,6 +445,25 @@ public static boolean isSetUID() { */ public static native String[] getRuntimeArguments(); + /** + * Drains the HotSpot Portable MDO eager-compilation pipeline. + * + *

When the JVM was started with {@code -XX:ImportMDOFile=...} and + * {@code -XX:+EagerCompilePortableMDO}, imported {@code MethodData} + * profiles are installed lazily as their owning classes finish linking, + * but compilation is deferred. This method walks every imported entry, + * resolves and links its holder class, queues the recorded compilation + * level, and blocks until all compile queues drain. + * + *

Intended to be called from a framework or benchmark harness after + * the application's startup class-loading burst is over, so that the + * first measured workload runs with all profile-driven compilations + * already in place. + * + *

No-op when the relevant flags are not set. + */ + public static native void waitForEagerCompilation(); + static { initialize(); } diff --git a/src/java.base/share/native/libjava/VM.c b/src/java.base/share/native/libjava/VM.c index 099ad46fff1..b905cfbbbd0 100644 --- a/src/java.base/share/native/libjava/VM.c +++ b/src/java.base/share/native/libjava/VM.c @@ -55,3 +55,8 @@ JNIEXPORT jobjectArray JNICALL Java_jdk_internal_misc_VM_getRuntimeArguments(JNIEnv *env, jclass cls) { return JVM_GetVmArguments(env); } + +JNIEXPORT void JNICALL +Java_jdk_internal_misc_VM_waitForEagerCompilation(JNIEnv *env, jclass cls) { + JVM_WaitForEagerCompilation(env, cls); +} From 8e5b04d30a22510a54af756f9702c05b532995d5 Mon Sep 17 00:00:00 2001 From: Daniel Zhou Date: Mon, 13 Apr 2026 16:13:48 -0700 Subject: [PATCH 23/23] install list --- doc/portable-mdo-slides.html | 1318 +++++++++++++++--------- src/hotspot/share/oops/portableMDO.cpp | 247 +++-- 2 files changed, 966 insertions(+), 599 deletions(-) diff --git a/doc/portable-mdo-slides.html b/doc/portable-mdo-slides.html index 43d285ce4f0..e99dfc4a24a 100644 --- a/doc/portable-mdo-slides.html +++ b/doc/portable-mdo-slides.html @@ -5,15 +5,15 @@ Portable MDO: Cross-JVM Profile Data Transfer for OpenJDK

- +

OpenJDK 25 · HotSpot Runtime

-

Portable MDO
Cross-JVM Profile Data Transfer

-

- A stable binary serialization format for exporting and importing - MethodData (MDO) profiling information across JVM instances, - enabling warm-start compilation without shared memory or CDS archives. +

Portable MDO
Cross-JVM Profile Transfer & Eager Compilation

+

+ A system for exporting JIT profiling data (MethodData objects) from one JVM, + importing them into another, and eagerly compiling + at the recorded tier — eliminating warm-up across JVM instances.

~2,700 lines C++ - 10 files modified - 26 unit tests - 6 runtime bugs found & fixed + Eager compile pipeline + Lock-free install-list + DaCapo benchmarks
+

Daniel Zhou · April 2026

1
- +

The Problem

JVM Warm-up Is Expensive

-

Every JVM instance starts cold. The C1/C2 JIT compilers - must re-collect profiling data from scratch before producing optimised code.

+

Every JVM instance starts cold. The tiered compilation system + must re-discover hot methods and re-collect profiling data from scratch.

    -
  • Invocation & backedge counters start at zero
  • -
  • Receiver type profiles are empty
  • -
  • Branch/switch taken ratios are unknown
  • -
  • Deoptimization history is lost
  • +
  • Interpreter → C1 (profiling) → C2 (optimized) takes many iterations
  • +
  • Receiver type profiles, branch ratios, and trap history are empty
  • +
  • C2 can't devirtualize or speculate without profile data
  • +
  • Serverless cold starts pay this cost on every container spawn
-
-

Impact

-
    -
  • Slow startup for latency-sensitive services
  • -
  • Wasted CPU re-profiling identical workloads
  • -
  • CDS/AOT caches don't carry profiling data
  • -
  • No portable way to transfer JIT hints
  • -
-

Existing solutions (CDS, Leyden AOT cache) focus on class data and - compiled code, but not on the profiling metadata that guides - compilation decisions.

+
+
+

The Warm-up Ramp

+

Example: DaCapo h2 benchmark takes 7 iterations to reach steady-state + performance. Each early iteration runs 2–5x slower than optimal.

+
+
Iter 1
118ms
+
Iter 2
68ms
+
Iter 4
46ms
+
Iter 7
28ms
+
Iter 10
24ms
+
+
+
+

Gap: CDS and Leyden cache class data and compiled code, but + not the profiling metadata that guides C2 speculative optimizations.

+
2
- +

Our Solution

-

Portable MDO Format

-

A self-contained binary file that captures a JVM's profiling state - at shutdown and restores it into a fresh JVM at startup.

+

Portable MDO: Export → Import → Eager Compile

+

A three-phase system that captures a JVM's profiling state, + restores it into a fresh JVM, and immediately compiles at the recorded tier.

-
JVM 1
warm workload
+
Profiling Run
warm workload
+
+
Export
ExportMDOFile
-
ExportMDOFile
safepoint VM op
+
.mdo file
portable binary
-
.mdo file
~40 KB
+
Import
ImportMDOFile
-
ImportMDOFile
startup hook
+
Install on Link
on_class_linked
-
JVM 2
warm start
+
Drain
eager compile
-

Symbolic References

-

All Klass*/Method* pointers serialised as class names + classloader tags. Portable across address spaces.

+

Symbolic & Portable

+

Klass*/Method* stored as names + classloader tags. Profile data as logical (tag, bci, counters) — not raw DataLayout cells.

-

Logical Format

-

Profile data stored as (tag, bci, counters, type refs) — not raw DataLayout cells. Flag-independent.

+

Install-List Architecture

+

Lock-free linked list records (Method*, tier) at class-link time. Works for any classloader — boot, platform, app, or custom.

-
-

Fingerprint Validation

-

CRC32 over method bytecodes detects stale profiles when code changes between runs.

+
+

Eager Compile Drain

+

After a priming iteration loads classes, drain the install-list: queue every method into CompileBroker at its recorded tier, wait for queues to empty.

3
- +
-

Architecture

-

System Integration Points

+

Phase 1

+

Export: Capturing Profile State

-

Export Path (Shutdown)

+

How Export Works

-
before_exit()export_on_shutdown()
+
before_exit()PortableMDO::export_on_shutdown()
+
+
VM_ExportMDO safepoint op — consistent snapshot
-
VM_ExportMDO safepoint operation
+
Walk ClassLoaderDataGraph → collect mature MDOs only
-
export_all_to_file()
  collect_mature_mdos()
  for each: export_single_mdo()
+
For each MDO: serialize identity + header + per-BCI records
-
Write klass ref table → method ref table → patch header
+
Write klass ref table → method ref table → patch header
+
+ +
+

Deopt Decay

+

Trap counts scaled by MDOExportDeoptDecayPercent (default 50%). + Prevents overly conservative compilation from inherited deopt history. + nof_overflow_traps reset to 0.

-

Import Path (Startup)

-
-
Threads::create_vm()initialize_import()
-
-
Load file → parse klass/method ref tables → build lookup map
-
-
Method::build_profiling_method_data()
  → PortableMDO::try_import()
-
-
Lookup key match → fingerprint check → allocate MethodData → populate from records
+

What Gets Serialized

+ + + + + + + + + + + + +
DataPurpose for C2
invocation_countMethod hotness signal
backedge_countLoop hotness signal
ReceiverTypeDataDevirtualization (speculative inlining)
BranchDataBranch prediction, uncommon trap placement
CallTypeDataArgument/return type speculation
trap_hist[]Deopt avoidance (e.g. class_check, null_check)
highest_comp_levelTarget tier for eager compile
bytecode_fingerprintCRC32 staleness detection
+
+

Intentionally Omitted

+

Escape analysis state (not portable), hidden/lambda classes (elided, overflow counts preserved), raw DataLayout cell addresses.

@@ -249,9 +295,9 @@

Import Path (Startup)

4
- +

File Format

@@ -272,7 +318,7 @@

Binary Layout magic: 0x4A4D44

var - Klass Ref Table + Klass Ref Table (dedup'd)
var @@ -283,135 +329,212 @@

Binary Layout magic: 0x4A4D44

Per-MDO Entry Structure

MethodIdentity (klass_ref, name, sig, fingerprint)
-
HeaderFields (counters, deopt, trap_hist, arg_modified)
+
HeaderFields (counters, deopt, trap_hist, highest_comp_level)
EntryCounts (records, extras, params, handlers)
ProfileRecords[] (tag + bci + payload per record)
-
ExtraRecords[] (speculative traps, stray traps)
+
ExtraRecords[] (speculative traps)
ParameterTypes[] + ExceptionHandlers[]
-

13 profile record types supported: BitData, CounterData, JumpData, - ReceiverTypeData, VirtualCallData, RetData, BranchData, MultiBranchData, - CallTypeData, VirtualCallTypeData, ParametersTypeData, and extra data.

+

Klass Ref Entry

+
loader_tag  uint8  (BOOT=0, PLATFORM=1, APP=2, CUSTOM=3)
+name_length uint16
+name_bytes  UTF-8  ("java/util/HashMap")
+

13 profile record types supported. All symbolic references + resolved at import time. Displacements NOT stored — recomputed on import.

5
- +
-

Design Decisions

-

Key Engineering Choices

-
-
-

Logical vs Raw

-

Serialize at the logical level (tag + bci + typed counters) rather - than raw DataLayout cells. This makes the format independent of - TypeProfileWidth, BciProfileWidth, and other flag-dependent cell layouts.

-
-
-

Hidden Class Elision

-

Lambda/invokedynamic classes are discarded. Overflow counts are preserved - so the compiler still knows the call site is polymorphic and hot.

-
-
-

Deopt Decay

-

Deoptimization counters are multiplied by a configurable decay factor - (default 50%) at export. Prevents overly conservative compilation from - inherited deopt history.

-
-
-

Safepoint Export

-

Export runs as a VM_Operation at a safepoint during shutdown, ensuring - consistent MDO state. No locks needed — only the VM thread is active.

-
-
-

Lazy Import

-

Profiles are imported on-demand when build_profiling_method_data() - is called, not eagerly. Only methods that get hot enough to need profiling - receive imported data.

+

Phase 2

+

Import: Parsing & MDO Reconstruction

+
+
+

Startup: initialize_import()

+
    +
  • Read entire .mdo file into C-heap buffer at VM startup
  • +
  • Parse & validate header (magic, JDK version, pointer size, endianness)
  • +
  • Parse klass ref table → intern all Symbol* via SymbolTable
  • +
  • Parse MDO entries → record byte offsets (don't fully parse yet)
  • +
  • Build MDOLookupMap: (klass_name, method_name, sig) → entry_index
  • +
+ +

reconstruct_mdo() — The Core Routine

+

Called when a matching method is found. Replays imported bytes onto a freshly-allocated MethodData:

+
    +
  1. Allocate fresh MethodData (correct layout, zeroed cells)
  2. +
  3. Patch header: counters, trap histogram, decompile count, arg_modified
  4. +
  5. Patch per-BCI records: match by BCI+tag, set counts, resolve klass refs
  6. +
  7. Patch extra data: speculative trap records with method refs
  8. +
  9. Patch params & handlers: type entries, exception coverage
  10. +
-
-

No EA Export

-

Escape analysis fields are zeroed — EA recomputes cheaply during - the first C2 compilation. Avoids serialising highly implementation-specific state.

+
+

BCI+Tag Matching Strategy

+
// Walk live MDO and imported records in parallel
+// Match by BCI + tag, not by position
+
+Live:     BCI 10 counter | Imported: BCI 10 counter
+  → MATCH: patch counts
+
+Live:     BCI 10 counter | Imported: BCI 15 counter
+  → advance live, re-read imported
+
+Live:     BCI 20         | Imported: BCI 15
+  → skip stale imported record
+

Tolerates bytecode layout changes between runs. If method body changed, fingerprint mismatch skips the entire entry.

+ +

Klass Resolution at Import

+
+

BOOT / PLATFORM / APP: resolved via + SystemDictionary::find_instance_klass() — lookup only, no class loading.

+

CUSTOM: cannot be resolved from a stored tag. + But on_class_linked doesn't need resolution — it sees the live class directly.

+
+ +
+

Key insight: Decompile count capped to + PerMethodRecompilationCutoff to prevent accidentally marking methods as not-compilable + from stale deopt history.

+
6
- +
-

Implementation

-

Files Modified & Created

- - - - - - - - - - - - - - - - -
FileRoleLines
portableMDO.hppFormat structs, API declarations, constants~460
portableMDO.cppFull exporter + importer implementation~2,245
globals.hppVM flags: ExportMDOFile, ImportMDOFile, MDOExportDeoptDecayPercent+6
vmOperation.hppExportMDO added to VM_OPS_DO table+1
java.cppbefore_exit hooks: export_on_shutdown + shutdown_import+8
threads.cppcreate_vm hook: initialize_import after class loaders+4
method.cppbuild_profiling_method_data: try_import before allocate+12
test_portableMDO.cpp26 GTest unit tests~380
TestPortableMDORoundTrip.javaJTreg integration test~148
portable-mdo-design.mdDesign document~871
+

Phase 3

+

Install-List: The Bridge Between Import and Compile

+
+
+

The Problem with Direct Compilation

+

We can't compile methods inline from InstanceKlass::link_class_impl():

+
    +
  • Compilation on the class-loading thread can deadlock against CompileBroker locks
  • +
  • Custom-loader classes (Spring, DaCapo, Lambda) can't be pre-resolved by name — we need the actual loaded class
  • +
  • We want to batch all compilations and wait for queues to drain before measuring
  • +
+ +

Solution: Install Then Compile

+
+
Class links
any thread
+
+
on_class_linked
install MDO
+
+
Install-List
(Method*, tier)
+
+
Drain
eager compile
+
+
+
+

Lock-Free Linked List

+
struct InstallRecord {
+  Method*        method;
+  uint8_t        level;   // CompLevel from export
+  InstallRecord* next;
+};
+
+static InstallRecord* volatile _install_list_head;
+
+// Many writers (any classloading thread)
+void install_list_append(Method* m, uint8_t level) {
+  InstallRecord* rec = new InstallRecord{m, level};
+  InstallRecord* old_head;
+  do {
+    old_head = Atomic::load(&_install_list_head);
+    rec->next = old_head;
+  } while (Atomic::cmpxchg(&_install_list_head,
+            old_head, rec) != old_head);
+}
+
+// Single reader (drain thread)
+InstallRecord* install_list_take_all() {
+  return Atomic::xchg(&_install_list_head,
+                       nullptr);
+}
+
+

Concurrency: CAS-based push (many writers), atomic take-all (single drain reader). + No mutexes needed. Liveness checked at drain time via ClassLoaderData::is_alive().

+
+
+
7
- -
+ +
-

Bug #1 — Crash

-

SEGFAULT in KlassRefTableBuilder::add_or_discard

+

Hook Point

+

on_class_linked: Where MDOs Meet Live Classes

-

Symptom

-
SIGSEGV in Symbol::utf8_length()
-  called from KlassRefTableBuilder::write_to
-  e.name = (Symbol*)0xa  ← invalid pointer
-
-Crash during MDO export at safepoint.
-The Klass* passed to add_or_discard was 0xa.
-

Root Cause

-

ReceiverTypeData::receiver(row) returns the raw cell value - cast to Klass*. It does NOT strip tag bits. The value 0xa = - null_seen(1) | type_unknown(2) | 0x8 — a tagged intptr from - TypeEntries, not a valid metaspace pointer.

+

Integration in InstanceKlass

+
// instanceKlass.cpp, link_class_impl()
+// Fires for EVERY class after linking completes
+
+bool InstanceKlass::link_class_impl(TRAPS) {
+  ...  // verification, rewriting, linking
+  set_init_state(linked);
+
+  // NEW: install imported MDO profiles
+  PortableMDO::on_class_linked(this, THREAD);
+
+  return true;
+}
+ +

What on_class_linked Does

+
    +
  1. Walk all methods in the newly-linked class
  2. +
  3. Look up each in the import MDOLookupMap
  4. +
  5. Validate bytecode fingerprint (CRC32 match)
  6. +
  7. Read highest_comp_level from entry header
  8. +
  9. Skip if level ≤ 0 (interpreter-only methods)
  10. +
  11. Force build_profiling_method_data()try_import()reconstruct_mdo()
  12. +
  13. Append (Method*, level) to install-list
  14. +
-

Fix

-
int16_t add_or_discard(const Klass* k) {
-  if (k == nullptr) return NULL_KLASS_REF;
-
-  // Reject values that cannot be valid
-  // metaspace pointers
-  if ((uintptr_t)k < os::vm_page_size())
-    return NULL_KLASS_REF;
-  if ((uintptr_t)k & (alignof(Klass)-1))
-    return NULL_KLASS_REF;
-
-  if (k->is_hidden()) ...  // now safe
-}
-
-

Strategy: Validate pointer alignment and - minimum address before any dereference. Sub-page-size values and - misaligned pointers are rejected as invalid.

+

Why This Solves Custom Classloaders

+
+

Key insight: We never need to find a custom-loaded class by name. + We wait for it to come to us. When DaCapo's DacapoClassLoader, Spring's + LaunchedURLClassLoader, or any custom loader links a class, + on_class_linked fires with the real InstanceKlass* in hand.

+
+ +
+

Coverage by Loader Type

+ + + + + + + + +
LoaderResolved?Installed?
Boot (java.*)yesyes
Platformyesyes
Appyesyes
Custom (DaCapo, Spring)noyes
+

Custom-loader holders can't be resolved from a stored loader tag, + but on_class_linked catches them when they're loaded. Install-list + carries the live Method* acquired through the correct loader.

+
+ +
+

Example: h2 benchmark — 750 org.h2.* methods + loaded via DacapoClassLoader, all installed via on_class_linked, zero resolved by name.

@@ -419,47 +542,84 @@

Fix

8
- -
+ +
-

Bug #2 — Data Corruption

-

Klass Ref Table Contains Garbage After Export

+

Phase 4

+

Eager Compile Drain

-

Symptom

-
klass_ref[0] loader=96 name_len=1 name=''
-klass_ref[1] loader=160 name_len=1 name=''
-// All klass names empty, loader tags garbage
-// Import lookup map finds no matches
-
-Map insert: .hotLoop(I)I  ← missing class name!
-Lookup:     MDOWarmTest.hotLoop(I)I  ← never found
-

Root Cause

-

The GrowableArray backing the - KlassRefTableBuilder grew inside the inner ResourceMark of - export_single_mdo(). When that scope exited after each MDO, the ResourceArea - freed the array's backing memory. Subsequent reads returned garbage.

+

eager_compile_imported_methods()

+
void PortableMDO::eager_compile_imported_methods(TRAPS) {
+  // Atomically take the entire install-list
+  InstallRecord* head = install_list_take_all();
+
+  while (head != nullptr) {
+    InstallRecord* rec = head;
+    head = head->next;
+    Method* m = rec->method;
+
+    // Liveness guard: skip if classloader unloaded
+    ClassLoaderData* cld = m->method_holder()
+                            ->class_loader_data();
+    if (cld == nullptr || !cld->is_alive()) {
+      skipped_dead++; delete rec; continue;
+    }
+    if (m->method_data() == nullptr) {
+      skipped_no_mdo++; delete rec; continue;
+    }
+
+    // Queue at the RECORDED tier
+    CompileBroker::compile_method(
+      mh, InvocationEntryBci,
+      (CompLevel)rec->level,  // C1=3 or C2=4
+      0, CompileTask::Reason_MustBeCompiled, THREAD);
+    compiled++;
+    delete rec;
+  }
+
+  // Block until both queues are empty
+  for (;;) {
+    CompileBroker::wait_for_no_active_tasks();
+    if (c1_queue->is_empty() && c2_queue->is_empty())
+      break;
+    os::naked_short_sleep(1);
+  }
+}
-

Memory Lifecycle Diagram

-
-
export_all_to_file() {
-
  ResourceMark rm; // outer scope
-
  KlassRefTableBuilder klass_table;
-
  for each MDO:
-
    export_single_mdo() {
-
      ResourceMark rm; // INNER!
-
      klass_table->add_or_discard(k);
-
      // ~ResourceMark frees array memory!
-
    }
-
  klass_table.write_to() // reads freed memory!
+

Design Decisions

+
+

Replays Recorded Tier, Not Blanket C2

+

Each method is compiled at the tier it organically reached in the profiling run. + A method that only made it to C1 (level 3) stays C1. Only methods that reached C2 (level 4) + get C2. Level 0 (interpreter) is skipped entirely.

-
-

Fix: Remove inner ResourceMark from - export_single_mdo(). The outer ResourceMark in - export_all_to_file() covers the entire export lifecycle.

+ +
+

JNI Bridge

+
// jvm.cpp
+JVM_ENTRY(void, JVM_WaitForEagerCompilation(...))
+  PortableMDO::eager_compile_imported_methods(THREAD);
+JVM_END
+
+// jdk.internal.misc.VM.java
+public static native void waitForEagerCompilation();
+
+ +
+

Drain Timing (DaCapo)

+ + + + + + + + +
BenchmarkQueuedDrain Time
h2322112 ms
eclipse560145 ms
kafka1,013173 ms
avrora~250~100 ms
@@ -467,121 +627,120 @@

Memory Lifecycle Diagram

9
- -
+ +
-

All Issues Found

-

6 Bugs Discovered & Fixed

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#CategoryDescriptionPhase
1compileMissing #include headers for SymbolTable, ClassLoaderDataPhase 1
2compileIncorrect API usage: cld->is_platform_class_loader_data() method namePhase 1
3compileTemplate deduction failure in ResourceHashtable hash function signaturePhase 3
4compileWrong cast type in MethodData allocation from ClassLoaderDataPhase 4
5crashSEGFAULT: ReceiverTypeData::receiver() returns tagged intptr as Klass*Runtime
6corruptionInner ResourceMark in export_single_mdo frees GrowableArray memoryRuntime
+

Execution Model

+

The Priming Iteration

-
-

Compile-time (4)

-

Caught by clang++ -fsyntax-only iterative compilation. - All related to HotSpot internal API specifics — method names, template parameters, cast types.

+
+

DaCapo Callback Architecture

+
// PortableMDODrainCallback.java
+public class PortableMDODrainCallback
+    extends Callback {
+
+  private int completedIterations = 0;
+  private boolean drained = false;
+
+  @Override
+  public void stop(long duration) {
+    super.stop(duration);
+    completedIterations++;
+    if (drained) return;
+    if (completedIterations < 1) return;
+    drained = true;
+
+    // After iter 1: drain the install-list
+    long t0 = System.nanoTime();
+    VM.waitForEagerCompilation();
+    long ms = (System.nanoTime() - t0) / 1_000_000;
+    System.out.println("Drain complete: " + ms + " ms");
+  }
+}
-
-

Runtime (2)

-

Required actual workload execution to surface. Both involved subtle - pointer/memory lifetime issues invisible at compile time. Found via systematic - hex dump analysis and diagnostic logging.

+
+

Serverless Analogy

+
+
+ Container Start + — VM boots, reads .mdo, builds lookup map +
+
+ First Request (= Iter 1) + — classes link, MDOs install via on_class_linked +
+
+ Drain + — between request 1 and 2: compile everything, wait for queues +
+
+ Request 2+ (= Iter 2+) + — fully compiled code in place, steady-state performance +
+
+ +
+

Why We Need A Priming Iteration

+

Custom classloaders (DaCapo's DacapoClassLoader, Spring's app loader) don't + load benchmark classes until the workload actually runs. We need at least one + pass through the workload to trigger class linking so on_class_linked + can fire and populate the install-list.

+

The priming iter IS the cold-start request — paid once per container lifetime.

+
10
- -
+ +
-

Methodology

-

Debugging Deep HotSpot Runtime Bugs

+

Results

+

h2 Database: 1.51x First-Iter Speedup

-

Techniques Used

-
    -
  • Hex dump analysisxxd on the .mdo file to verify file format integrity at byte level
  • -
  • Offset correlation — compared writer.position() vs ftell() to detect file position drift
  • -
  • Layered logging — trace/info/debug log levels at critical export/import points
  • -
  • Pointer forensics — decoded 0xa as TypeEntries tag bits (null_seen | type_unknown)
  • -
  • Memory lifecycle tracing — tracked GrowableArray allocation across ResourceMark scopes
  • -
+

Iteration Curves (ms, lower is better)

+ + + + + + + + + + + + + + +
IterColdWarmSpeedup
1118781.51x
268371.84x
362321.94x
446281.64x
532261.23x
627290.93x
728241.17x
824221.09x
926191.37x
1024171.41x
-

Debugging Timeline

-
-
- 1. Export crashes with SIGSEGV at Klass* = 0xa -
-
- 2. Traced 0xa to ReceiverTypeData raw cell tags -
-
- 3. Fix: pointer validity check ✔ -
-
- 4. Export succeeds but import finds no methods -
-
- 5. Klass ref table contains garbage names -
-
- 6. Hex dump confirms corrupt data at correct offset -
-
- 7. ftell == writer.position — offset is correct -
-
- 8. Identified inner ResourceMark as the culprit -
-
- 9. Fix: remove inner ResourceMark ✔ -
+
+
1.51x
1st iter speedup
+
322
methods compiled
+
112ms
drain time
+
+ +
+

Why h2 Benefits Most

+
    +
  • Long cold ramp: cold takes 7 iterations to reach optimal — large window to compress
  • +
  • Concentrated hot set: 750 org.h2.* methods are the benchmark's core
  • +
  • Lower warm asymptote: warm reaches 17ms vs cold's 24ms at iter 10 — C2 with imported MDO finds a better steady state
  • +
+
+ +
+

MDO Coverage

+

1,659 exported → 1,517 reconstructed → 322 queued for drain. + 750 app (org.h2), 724 JDK, 39 org.apache, 4 org.dacapo.

@@ -589,76 +748,77 @@

Debugging Timeline

11
- +

Results

-

End-to-End Verification

+

Cross-Benchmark Comparison

-

JVM 1: Export

-
$ java -XX:+UnlockDiagnosticVMOptions \
-       -XX:ExportMDOFile=/tmp/profile.mdo \
-       -XX:MDOExportDeoptDecayPercent=50 \
-       -Xlog:aot+training=info \
-       MDOWarmTest export
-
-PortableMDO: exported 149 mature MDO profiles
-  73 klass refs, 0 method refs
-  40,704 bytes
- -

JVM 2: Import

-
$ java -XX:+UnlockDiagnosticVMOptions \
-       -XX:ImportMDOFile=/tmp/profile.mdo \
-       -Xlog:aot+training=info \
-       MDOWarmTest import
-
-PortableMDO import: loaded 149 MDO profiles
-reconstructed MDO for MDOWarmTest.hotLoop
-  (2 records, 0 extra, 0 params, 0 handlers)
-reconstructed MDO for MDOWarmTest.branchProfile
-  (1 records, 0 extra, 0 params, 0 handlers)
-reconstructed MDO for MDOWarmTest.main
-  (11 records, 0 extra, 0 params, 0 handlers)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Benchmark1st IterMeanCold→OptWarm→OptDrain
avrora2.83x1.65x3 iters0 iters~250
lusearch6.92x4.85x6 iters8 iters~379
h21.51x1.41x7 iters9 iters322
eclipse1.02x1.11x5 iters2 iters5,596
kafka1.00x1.00x0 iters0 iters1,013
+

avrora, lusearch, h2 at -s small. eclipse at -s default. kafka at both sizes.

-

Verification Checks

-
-
- File magic 0x4A4D444F correct -
-
- 149 MDO entries exported -
-
- 73 klass refs with correct names -
-
- All 149 profiles loaded in JVM 2 -
-
- User methods reconstructed (hotLoop, branchProfile, main) -
-
- JDK system methods imported (HashMap, ArrayList, Double...) -
-
- Bytecode fingerprints validated -
-
- No crashes, no error logs -
-
- 0 compile errors, 0 warnings -
+
+

Where MDO Wins

+
    +
  • avrora: warm curve flat from iter 1 (warm_time_to_optimal=0). MDO eliminates the entire ramp.
  • +
  • lusearch: 6.92x first-iter speedup. Cold spends 6 iters ramping; warm delivers near-optimal immediately.
  • +
  • h2: Durable speedup even at steady state. C2 with imported profiles reaches a better optimum.
  • +
  • eclipse: 3 fewer iterations to optimum. Warm curve is visibly smoother.
  • +
-
-

Warm State Captured

-

JVM 2 receives JVM 1's branch probabilities, loop iteration counts, - receiver type profiles, and deopt history — before executing a single bytecode - of the workload methods.

+
+

Where MDO Doesn't Help

+
    +
  • kafka: zero JIT ramp even cold. Benchmark is IO/network-bound, not JIT-bound. 1,013 compiled methods don't matter because JIT isn't the bottleneck.
  • +
@@ -666,43 +826,52 @@

Warm State Captured

12
- -
+ +
-

Testing

-

Test Coverage

+

Analysis

+

Benchmark Size Matters: -s small vs -s default

-

GTest Unit Tests (26 tests)

-
    -
  • Struct layout verification — sizeof, alignment, field offsets for all 15+ format structs
  • -
  • Round-trip serialization — write/read PortableKlassRefEntry, PortableMethodRefEntry, PortableProfileRecordHeader
  • -
  • Enum value tests — PortableClassLoaderTag, PortableExtraTag
  • -
  • Header validation — magic number, format version, pointer size, endianness
  • -
  • Bytecode fingerprint — CRC32 computation on live Method* objects
  • -
  • Edge cases — MAX constants, NULL_KLASS_REF sentinel, padding zeroes
  • -
+

Eclipse: Hidden by Noise at Small

+ + + + + + + + + + +
-s small-s default
1st iter speedup1.14x1.02x
Mean speedup1.02x1.11x
Cold→optimal5 iters5 iters
Warm→optimal9 iters2 iters
Iter time~100ms~7,000ms
MDOs reconstructed2,6975,596
+
+

At -s default, each iteration exercises the code long enough + for JIT warmup to actually matter. Warm reaches steady state 3 iterations earlier + and avoids the cold curve's noisy spikes.

+
-

JTreg Integration Test

-
-

TestPortableMDORoundTrip.java

-
    -
  • Forks JVM 1 with workload + ExportMDOFile
  • -
  • Verifies .mdo file exists and has correct magic
  • -
  • Forks JVM 2 with ImportMDOFile
  • -
  • Verifies import success logs in stdout
  • -
  • Validates no crash or error in either JVM
  • -
-
-

Manual End-to-End

-
-

Full workload test with MDOWarmTest.java: - hot loops, branch profiling, polymorphic calls. - Verified profile reconstruction in JVM 2 via - -Xlog:aot+training=info.

+

Kafka: IO-Bound, Not JIT-Bound

+ + + + + + + + + +
-s small-s default
1st iter speedup1.13x1.00x
Mean speedup1.13x1.00x
Cold→optimal4 iters0 iters
Warm→optimal4 iters0 iters
Iter time~25ms~4,900ms
+
+

At -s default, every iteration is ~4,900ms with zero ramp. + Cold already reaches optimal at iter 1. The bottleneck is network/IO scheduling, + not JIT compilation. MDO correctly installs and compiles 1,013 methods — they just + don't appear on the hot path.

+

Honest negative result: useful for + the paper to demonstrate MDO helps JIT-bound workloads, not IO-bound ones.

@@ -710,79 +879,226 @@

Manual End-to-End

13
- -
+ +
-

Data Types

-

Profile Data Serialized

+

Design

+

Key Engineering Choices

-

Counters

-
    -
  • Invocation count
  • -
  • Backedge count
  • -
  • Per-BCI execution count
  • -
  • Branch taken / not-taken
  • -
  • Switch case counts
  • -
+

Logical vs Raw Format

+

Serialize at the logical level (tag + bci + typed counters) rather + than raw DataLayout cells. Independent of TypeProfileWidth, BciProfileWidth, + and other flag-dependent cell layouts.

-

Type Profiles

-
    -
  • Receiver types (virtual calls)
  • -
  • Argument types (call type data)
  • -
  • Return types
  • -
  • Parameter types
  • -
  • Type conflict flags
  • -
+

Install-then-Compile

+

Two-phase design avoids deadlocks: on_class_linked only installs + MDOs and records on the list. Compilation is deferred to the explicit drain call + from a known JavaThread.

-

Deoptimization

-
    -
  • Trap histogram per reason
  • -
  • Decompile count (with decay)
  • -
  • Overflow recompile count
  • -
  • Per-BCI trap state
  • -
  • Speculative trap records
  • -
+

Replay Recorded Tier

+

Each method compiled at its profiling-run tier, not blanket C2. + C1-only methods stay C1. Level-0 methods skipped. Avoids wasted C2 time on methods + that didn't warrant it.

-

Structure

-
    -
  • Number of loops
  • -
  • Number of blocks
  • -
  • Would-profile hint
  • -
  • Arg-modified flags
  • -
+

Fingerprint Gating

+

CRC32 over bytecodes prevents stale profile application. + If class evolves between runs, method is silently skipped — no crash, no + misspeculation from wrong profile.

-

Control Flow

-
    -
  • Jump taken counts
  • -
  • Ret data targets
  • -
  • Exception handler coverage
  • -
  • Multi-branch default count
  • -
+

Hidden Class Elision

+

Lambda/invokedynamic classes discarded from receiver type profiles. + Overflow counts preserved so C2 still knows call sites are polymorphic/hot + without needing to serialize unpredictable class identities.

-

Intentionally Omitted

-
    -
  • Escape analysis state
  • -
  • DataLayout displacements
  • -
  • Raw cell addresses
  • -
  • Hidden class references
  • -
+

Safepoint Export

+

Export runs as a VM_Operation at shutdown safepoint. Consistent + MDO state guaranteed — no concurrent mutations. Deopt counts decayed + by configurable factor (default 50%).

14
- + +
+
+

API

+

JVM Flags & Integration Points

+
+
+

New Diagnostic Flags

+ + + + + + + + +
FlagDefaultPurpose
ExportMDOFilenullExport mature MDO profiles to this file at shutdown
ImportMDOFilenullImport MDO profiles from this file at startup
EagerCompilePortableMDOfalseEnable on_class_linked hook & install-list
MDOExportDeoptDecayPercent50Decay factor for deopt counts at export (0-100)
+

All require -XX:+UnlockDiagnosticVMOptions.

+ +

Usage Examples

+
# Profiling run
+java -XX:+UnlockDiagnosticVMOptions \
+  -XX:ExportMDOFile=profile.mdo \
+  -XX:MDOExportDeoptDecayPercent=50 \
+  -jar myapp.jar
+
+# Warm run
+java -XX:+UnlockDiagnosticVMOptions \
+  -XX:ImportMDOFile=profile.mdo \
+  -XX:+EagerCompilePortableMDO \
+  -jar myapp.jar
+
+
+

Files Modified

+ + + + + + + + + + + + +
FileRole
portableMDO.hpp/cppFormat, export, import, install-list, drain
globals.hpp4 new diagnostic flags
instanceKlass.cppon_class_linked hook call
threads.cppinitialize_import at VM boot
java.cppexport_on_shutdown + shutdown_import
method.cpptry_import in build_profiling_method_data
jvm.cppJVM_WaitForEagerCompilation native
VM.java / VM.cwaitForEagerCompilation() JNI bridge
+ +
+

Java API

+
// jdk.internal.misc.VM
+public static native void waitForEagerCompilation();
+// Drains install-list, queues compilations,
+// blocks until C1+C2 queues are empty
+
+
+
+
+
15
+
+ + +
+
+

Architecture

+

End-to-End Pipeline

+ +
+ +
+

Profiling Run (JVM 1)

+
+
Application executes workload
+
+
JIT collects MDOs organically
interpreter → C1 → C2 ramp
+
+
Shutdown: VM_ExportMDO safepoint
collect mature MDOs
+
+
Write .mdo file
entries + klass refs + method refs
+
+
+ + +
+

Warm Run Startup (JVM 2)

+
+
VM boot: initialize_import()
parse file, build MDOLookupMap
+
+
App starts (Iter 1 = priming)
classes begin linking
+
+
on_class_linked fires per class
lookup → fingerprint → reconstruct MDO
+
+
Append (Method*, level) to install-list
lock-free CAS push
+
+
+ + +
+

Drain & Steady State

+
+
Iter 1 completes → callback.stop()
VM.waitForEagerCompilation()
+
+
Drain: take_all → CompileBroker
queue each method at recorded tier
+
+
Wait for C1+C2 queues to empty
~100-170ms for DaCapo workloads
+
+
Iter 2+: fully compiled code
all hot methods at target tier
+
+
+
+
+
16
+
+ + +
+
+

Limitations

+

Risks & Open Questions

+
+
+

Workload Drift

+

Imported profiles reflect the profiling run's behavior. If the warm run + exercises different code paths, C2 may speculate wrong based on stale branch ratios + or receiver types — leading to deoptimization + recompile, potentially + worse than clean cold start.

+
+
+

Code Cache Pressure

+

Drain dumps hundreds of compiled methods at once. If the warm run's actual + hot set differs, we've pre-filled the code cache with the wrong methods. May cause + eviction of methods that matter in the new workload.

+
+
+

Drain Cost

+

Drain blocks for 100-170ms. For serverless where each request is ~50ms (kafka), + the drain cost exceeds multiple request latencies. Must be amortized over many + subsequent requests to break even. Not always viable for very short-lived containers.

+
+
+

IO-Bound Irrelevance

+

Kafka demonstrates that MDO can't help when JIT isn't the bottleneck. + IO/network/scheduling-dominated workloads see zero benefit regardless of how + many methods we compile. Need per-workload analysis.

+
+
+

Fingerprint is Bytecode-Only

+

CRC32 catches class-body changes but not "same code, different inputs." + If a method's call-site distribution changes (new subtypes at a virtual call), + imported profiles will mislead C2 until deopt+reprofile corrects it.

+
+
+

Benchmark Size Sensitivity

+

At -s small, workloads run too briefly per iteration for JIT + effects to dominate. Eclipse looked like "no speedup" at small but showed + 1.11x / 3-iter compression at default. Paper must control for this.

+
+
+
+
17
+
+ +

Conclusion

@@ -791,54 +1107,46 @@

Summary & Future Directions

What We Built

    -
  • Complete Portable MDO binary format specification
  • -
  • Full exporter: safepoint-safe, handles all 13 profile data types
  • -
  • Full importer: lazy on-demand reconstruction with fingerprint validation
  • -
  • JVM integration: 3 hook points (startup, method profiling, shutdown)
  • -
  • Comprehensive test suite: 26 gtests + jtreg + manual E2E
  • -
  • Found & fixed 6 bugs including 2 deep runtime issues
  • +
  • Complete Portable MDO binary format: logical, symbolic, portable
  • +
  • Safepoint-safe exporter with deopt decay (13 profile data types)
  • +
  • Lazy importer with fingerprint validation & MDO reconstruction
  • +
  • Lock-free install-list for cross-loader method tracking
  • +
  • Eager compile drain with CompileBroker queue wait
  • +
  • JNI bridge: VM.waitForEagerCompilation()
  • +
  • DaCapo callback integration with priming iteration model
-

Key Metrics

-
-
-
2,700+
-
lines of C++
-
-
-
149
-
MDOs exported
-
-
-
40 KB
-
typical file size
-
-
-
0
-
errors/warnings
-
+

Key Results

+
+
6.92x
lusearch 1st iter
+
2.83x
avrora 1st iter
+
1.51x
h2 1st iter
+
1.11x
eclipse mean

Future Work

    -
  • Performance benchmarking — measure actual warmup time reduction on real workloads (DaCapo, Renaissance)
  • -
  • CDS integration — embed Portable MDO profiles alongside shared class data
  • -
  • WhiteBox API — testing API for CI/CD validation of profile import quality
  • -
  • Counter normalization — scale imported counters to match target JVM's compilation thresholds
  • -
  • Cross-version support — profile migration across minor JDK updates
  • -
  • Partial import — selective import of hot methods only, reducing memory pressure
  • +
  • Multi-trial evaluation — 5+ trials with confidence intervals for publication-quality results
  • +
  • Larger workloads-s large, Spring Boot, Quarkus cold-start benchmarks
  • +
  • Deopt rate monitoring — measure how often imported profiles cause misspeculation
  • +
  • Selective import — only install top-N hottest methods, reducing drain cost
  • +
  • CDS integration — embed .mdo alongside shared class data for zero-config warm start
  • +
  • Cross-version tolerance — profile migration across minor JDK updates
  • +
  • Counter normalization — scale imported counters to match target JVM's compilation thresholds
-

Impact

-

Portable MDO enables warm-start JVM deployments without requiring - shared filesystems, CDS archives, or identical memory layouts. Profile data - flows from production to staging, from CI to canary, from one container to the next.

+

Key Takeaway

+

Portable MDO eliminates JIT warm-up for JIT-bound workloads + by transferring profiling metadata across JVM instances. The install-list architecture + enables this for any classloader — including custom loaders + used by Spring, Quarkus, and serverless frameworks — without requiring class + pre-resolution or shared memory.

-
15
+
18
@@ -846,7 +1154,7 @@

Impact

diff --git a/src/hotspot/share/oops/portableMDO.cpp b/src/hotspot/share/oops/portableMDO.cpp index a0fb2c2a5d6..e3905b6cd96 100644 --- a/src/hotspot/share/oops/portableMDO.cpp +++ b/src/hotspot/share/oops/portableMDO.cpp @@ -1142,6 +1142,63 @@ using MDOLookupMap = ResourceHashtable { + Method* method; + uint8_t level; + InstallRecord* next; +}; + +static InstallRecord* volatile _install_list_head = nullptr; + +static void install_list_append(Method* m, uint8_t level) { + InstallRecord* rec = new InstallRecord(); + rec->method = m; + rec->level = level; + // Lock-free push: spin on cmpxchg until we successfully link in. + InstallRecord* old_head; + do { + old_head = Atomic::load(&_install_list_head); + rec->next = old_head; + } while (Atomic::cmpxchg(&_install_list_head, old_head, rec) != old_head); +} + +static InstallRecord* install_list_take_all() { + return Atomic::xchg(&_install_list_head, (InstallRecord*)nullptr); +} + // -------------------------------------------------------------------------- // Header validation // -------------------------------------------------------------------------- @@ -2204,16 +2261,23 @@ MethodData* PortableMDO::try_import(const methodHandle& method, TRAPS) { // -------------------------------------------------------------------------- // // Called from InstanceKlass::link_class_impl as soon as a class finishes -// linking. This hook only INSTALLS imported MDOs — it never triggers -// compilation. That keeps the path fully reentrant-safe: even if a future -// drain compiles a method whose inlining causes more class loading, the -// recursive on_class_linked just installs more MDOs (cheap, no locks -// beyond the normal MDO allocation path). +// linking. This hook installs imported MDOs and records each successfully- +// installed method on the install-list for later eager compilation. It +// never triggers compilation directly — that's the drain's job. // -// Compilation is performed exclusively by eager_compile_imported_methods, -// which is invoked from Java via jdk.internal.misc.VM.waitForEagerCompilation() -// at a controlled point after the application has finished its initial -// class-loading burst. +// Why install-then-record (instead of compile inline)? +// - Compiling inline from link_class_impl means the compilation happens +// on whatever thread happened to load the class, often deep inside +// ClassLoader.loadClass paths where holding compile-broker locks would +// deadlock. +// - Recording to a side list lets us batch every method we care about +// and drain them from one well-known JavaThread (the one that calls +// VM.waitForEagerCompilation()). +// +// This path fires for ANY classloader — boot, platform, app, or custom. +// Custom-loader holders (Spring, Quarkus, Lambda, DaCapo's bundled loader) +// land on the install-list the same way as JDK classes. The drain compiles +// them without ever needing to look the loader up by name. void PortableMDO::on_class_linked(InstanceKlass* klass, TRAPS) { if (!_import_initialized) return; @@ -2234,6 +2298,27 @@ void PortableMDO::on_class_linked(InstanceKlass* klass, TRAPS) { int* entry_idx = _import_lookup_map->get(key); if (entry_idx == nullptr) continue; + ImportedMDOEntry* entry = &_import_entries[*entry_idx]; + + // Validate bytecode fingerprint before doing any work — if the method + // body has changed since export, the imported profile is stale and + // we shouldn't install it OR queue it for compilation. + uint32_t current_fp = PortableMDO::compute_bytecode_fingerprint(m); + if (current_fp != entry->bytecode_fingerprint) continue; + + // Read the stored compilation level from the entry's header fields. + // We need this to know what tier to compile at during the drain. + PortableMDOReader hdr_reader(_import_buffer, _import_buffer_size); + hdr_reader.set_position(entry->header_fields_offset); + PortableMDOHeaderFields header_fields; + if (!hdr_reader.read_struct(&header_fields, sizeof(header_fields))) continue; + + // Don't queue anything for level 0 (interpreted) — there's nothing + // to compile and CompileBroker would reject it anyway. + int level = (int)header_fields.highest_comp_level; + if (level <= CompLevel_none) continue; + if (level > CompLevel_full_optimization) level = CompLevel_full_optimization; + // Force MDO creation which routes through try_import — pure // reconstruction, no compilation. if (m->method_data() == nullptr) { @@ -2241,112 +2326,80 @@ void PortableMDO::on_class_linked(InstanceKlass* klass, TRAPS) { m->build_profiling_method_data(mh, THREAD); if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } } + if (m->method_data() == nullptr) continue; + + // Record on the install-list for the next drain to compile. + install_list_append(m, (uint8_t)level); } } // -------------------------------------------------------------------------- // Public API: eager_compile_imported_methods // -------------------------------------------------------------------------- +// +// Drains the install-list. For each (Method*, level) record produced by +// on_class_linked, queues a CompileBroker compilation at the recorded +// tier and waits for the compile queues to empty. +// +// This intentionally does NOT walk the import table directly. Anything +// the application hasn't linked yet by the time the drain runs is, by +// definition, not on the hot path of the work that's been done so far, +// and the JIT will pick it up incrementally on demand. Trying to +// pre-resolve from the import table only ever worked for boot/platform/ +// app holders anyway — it could never recover a custom-loader instance +// from a stored tag — so the install-list is strictly more capable. void PortableMDO::eager_compile_imported_methods(TRAPS) { if (!_import_initialized) return; if (!UseCompiler || !CompilationPolicy::is_compilation_enabled()) return; - ResourceMark rm(THREAD); - int compiled = 0; - int skipped = 0; - - log_info(aot, training)("PortableMDO: starting eager compilation of %d imported methods", - _import_entry_count); - - for (int i = 0; i < _import_entry_count; i++) { - ImportedMDOEntry* entry = &_import_entries[i]; - - // Resolve the holder class - if (entry->klass_ref_index < 0 || entry->klass_ref_index >= _import_klass_ref_count) { - skipped++; - continue; - } - ImportedKlassRef* kref = &_import_klass_refs[entry->klass_ref_index]; - - // Skip custom classloader classes — we can't resolve them - if (kref->loader_tag == PortableClassLoaderTag::CUSTOM) { - skipped++; - continue; - } - - // Use resolve_or_null to actually load the class if needed - Handle loader = classloader_handle_from_tag(kref->loader_tag, THREAD); - TempNewSymbol klass_sym = SymbolTable::new_symbol(kref->name->as_C_string(), - (int)kref->name->utf8_length()); - Klass* k = SystemDictionary::resolve_or_null(klass_sym, loader, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - if (k == nullptr || !k->is_instance_klass()) { - skipped++; - continue; - } - - InstanceKlass* holder = InstanceKlass::cast(k); - - // Link the class if not already linked - holder->link_class(THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - - // Find the method - Method* target = holder->find_method(entry->method_name, entry->method_sig); - if (target == nullptr || target->is_abstract() || target->is_native()) { - skipped++; - continue; - } - - // Validate bytecode fingerprint - uint32_t current_fp = PortableMDO::compute_bytecode_fingerprint(target); - if (current_fp != entry->bytecode_fingerprint) { - skipped++; - continue; - } - - // Ensure the MDO is installed (via try_import path) - methodHandle mh(THREAD, target); - if (target->method_data() == nullptr) { - target->build_profiling_method_data(mh, THREAD); - if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } - } - - if (target->method_data() == nullptr) { - skipped++; - continue; - } + // Atomically take the entire install-list. Any installs that happen + // after this point form the next list and are picked up by the next + // drain (or leak harmlessly if there is no next drain — they'll still + // tier up via the normal counter path). + InstallRecord* head = install_list_take_all(); - // Read the stored compilation level from the header fields - PortableMDOReader hdr_reader(_import_buffer, _import_buffer_size); - hdr_reader.set_position(entry->header_fields_offset); - PortableMDOHeaderFields header_fields; - if (!hdr_reader.read_struct(&header_fields, sizeof(header_fields))) { - skipped++; + int compiled = 0; + int skipped_dead = 0; + int skipped_no_mdo = 0; + + while (head != nullptr) { + InstallRecord* rec = head; + head = head->next; + + Method* m = rec->method; + + // Liveness guard: if the holder's classloader has unloaded since the + // record was appended, m is dangling and we must not touch it. + InstanceKlass* holder = m->method_holder(); + ClassLoaderData* cld = holder->class_loader_data(); + if (cld == nullptr || !cld->is_alive()) { + skipped_dead++; + delete rec; continue; } - CompLevel level = (CompLevel)header_fields.highest_comp_level; - if (level < CompLevel_none) { - level = CompLevel_none; - } else if (level > CompLevel_full_optimization) { - level = CompLevel_full_optimization; - } - // Don't eagerly compile at level 0 (interpreted) — no benefit - if (level <= CompLevel_none) { - skipped++; + if (m->method_data() == nullptr) { + // MDO install must have failed after we recorded — nothing to seed + // C2 with, so skip rather than compile blind. + skipped_no_mdo++; + delete rec; continue; } - // Queue the method for compilation - CompileBroker::compile_method(mh, InvocationEntryBci, level, + methodHandle mh(THREAD, m); + CompileBroker::compile_method(mh, InvocationEntryBci, (CompLevel)rec->level, 0, CompileTask::Reason_MustBeCompiled, THREAD); if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } compiled++; + delete rec; } - // Wait for all compilations to complete + log_info(aot, training)("PortableMDO: drain queued %d compilations " + "(skipped %d dead, %d no-mdo)", + compiled, skipped_dead, skipped_no_mdo); + + // Wait for the compile queues to empty out before returning. for (;;) { CompileBroker::wait_for_no_active_tasks(); CompileQueue* q1 = CompileBroker::c1_compile_queue(); @@ -2357,8 +2410,7 @@ void PortableMDO::eager_compile_imported_methods(TRAPS) { os::naked_short_sleep(1); } - log_info(aot, training)("PortableMDO: eager compilation done: %d compiled, %d skipped", - compiled, skipped); + log_info(aot, training)("PortableMDO: drain complete"); } // -------------------------------------------------------------------------- @@ -2408,6 +2460,13 @@ void PortableMDO::shutdown_import() { delete _import_lookup_map; _import_lookup_map = nullptr; } + // Free any install-list records the drain didn't consume. + InstallRecord* leftover = install_list_take_all(); + while (leftover != nullptr) { + InstallRecord* next = leftover->next; + delete leftover; + leftover = next; + } _import_klass_ref_count = 0; _import_method_ref_count = 0; _import_entry_count = 0;