From 874f8666d2afd38bee9dee0faa0ca7200bf2cdd9 Mon Sep 17 00:00:00 2001 From: iklam Date: Mon, 28 Apr 2025 21:07:28 -0700 Subject: [PATCH 01/23] 8355798: Implement JEP-JDK-8350022: Ahead-of-time Command Line Ergonomics --- src/hotspot/share/cds/cdsConfig.cpp | 79 +++++++--- src/hotspot/share/cds/cdsConfig.hpp | 5 + src/hotspot/share/cds/cds_globals.hpp | 4 + src/hotspot/share/cds/metaspaceShared.cpp | 118 ++++++++++++++- src/hotspot/share/cds/metaspaceShared.hpp | 1 + src/hotspot/share/runtime/arguments.cpp | 110 ++++++++++++-- src/hotspot/share/runtime/arguments.hpp | 14 +- .../share/classes/jdk/internal/misc/CDS.java | 41 ++++++ .../jtreg/runtime/cds/appcds/AOTFlags.java | 99 ++++++++++++- .../runtime/cds/appcds/AOTToolOptions.java | 136 ++++++++++++++++++ .../appcds/aotCache/SpecialCacheNames.java | 120 ++++++++++++++++ test/lib/jdk/test/lib/cds/CDSAppTester.java | 67 +++++++-- 12 files changed, 739 insertions(+), 55 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/AOTToolOptions.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 64ad07b0cf816..54d1d382e6491 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -47,6 +47,8 @@ bool CDSConfig::_is_using_optimized_module_handling = true; bool CDSConfig::_is_dumping_full_module_graph = true; bool CDSConfig::_is_using_full_module_graph = true; bool CDSConfig::_has_aot_linked_classes = false; +bool CDSConfig::_is_one_step_training = false; +bool CDSConfig::_has_temp_aot_config_file = false; bool CDSConfig::_old_cds_flags_used = false; bool CDSConfig::_new_aot_flags_used = false; bool CDSConfig::_disable_heap_dumping = false; @@ -394,37 +396,42 @@ void CDSConfig::check_aot_flags() { _old_cds_flags_used = true; } - // "New" AOT flags must not be mixed with "classic" flags such as -Xshare:dump + // "New" AOT flags must not be mixed with "classic" CDS flags such as -Xshare:dump CHECK_NEW_FLAG(AOTCache); + CHECK_NEW_FLAG(AOTCacheOutput); CHECK_NEW_FLAG(AOTConfiguration); CHECK_NEW_FLAG(AOTMode); CHECK_SINGLE_PATH(AOTCache); + CHECK_SINGLE_PATH(AOTCacheOutput); CHECK_SINGLE_PATH(AOTConfiguration); - if (FLAG_IS_DEFAULT(AOTCache) && FLAG_IS_DEFAULT(AOTConfiguration) && FLAG_IS_DEFAULT(AOTMode)) { - // AOTCache/AOTConfiguration/AOTMode not used. - return; - } else { - _new_aot_flags_used = true; + if (FLAG_IS_DEFAULT(AOTCache) && + FLAG_IS_DEFAULT(AOTMode)) { + bool has_cache_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + bool has_config = !FLAG_IS_DEFAULT(AOTConfiguration); + if (!has_cache_output && !has_config) { + // AOT flags are not used. Use classic CDS workflow + return; + } else if (has_cache_output) { + // If AOTCacheOutput has been set, default mode is "record". + // Default value for AOTConfiguration, if necessary, will be assigned in check_aotmode_record(). + FLAG_SET_ERGO(AOTMode, "record"); + } } + // At least one AOT flag has been used + _new_aot_flags_used = true; + if (FLAG_IS_DEFAULT(AOTMode) || strcmp(AOTMode, "auto") == 0 || strcmp(AOTMode, "on") == 0) { check_aotmode_auto_or_on(); } else if (strcmp(AOTMode, "off") == 0) { check_aotmode_off(); + } else if (strcmp(AOTMode, "record") == 0) { + check_aotmode_record(); } else { - // AOTMode is record or create - if (FLAG_IS_DEFAULT(AOTConfiguration)) { - vm_exit_during_initialization(err_msg("-XX:AOTMode=%s cannot be used without setting AOTConfiguration", AOTMode)); - } - - if (strcmp(AOTMode, "record") == 0) { - check_aotmode_record(); - } else { - assert(strcmp(AOTMode, "create") == 0, "checked by AOTModeConstraintFunc"); - check_aotmode_create(); - } + assert(strcmp(AOTMode, "create") == 0, "checked by AOTModeConstraintFunc"); + check_aotmode_create(); } } @@ -448,6 +455,26 @@ void CDSConfig::check_aotmode_auto_or_on() { } void CDSConfig::check_aotmode_record() { + bool has_config = !FLAG_IS_DEFAULT(AOTConfiguration); + bool has_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + + if (has_output) { + _is_one_step_training = true; + if (!has_config) { + // Too early; can't use resource allocation yet. + size_t len = strlen(AOTCacheOutput) + 10; + char* temp = AllocateHeap(len, mtArguments); + jio_snprintf(temp, len, "%s.config", AOTCacheOutput); + FLAG_SET_ERGO(AOTConfiguration, temp); + FreeHeap(temp); + _has_temp_aot_config_file = true; + } + } else { + if (!has_config) { + vm_exit_during_initialization("-XX:AOTMode=record cannot be used without setting AOTCacheOutput or AOTConfiguration"); + } + } + if (!FLAG_IS_DEFAULT(AOTCache)) { vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record"); } @@ -463,8 +490,22 @@ void CDSConfig::check_aotmode_record() { } void CDSConfig::check_aotmode_create() { - if (FLAG_IS_DEFAULT(AOTCache)) { - vm_exit_during_initialization("AOTCache must be specified when using -XX:AOTMode=create"); + if (FLAG_IS_DEFAULT(AOTConfiguration)) { + vm_exit_during_initialization("-XX:AOTMode=create cannot be used without setting AOTConfiguration"); + } + + bool has_cache = !FLAG_IS_DEFAULT(AOTCache); + bool has_cache_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + + if (!has_cache && !has_cache_output) { + vm_exit_during_initialization("AOTCache or AOTCacheOutput must be specified when using -XX:AOTMode=create"); + } else if (has_cache && has_cache_output && strcmp(AOTCache, AOTCacheOutput) != 0) { + vm_exit_during_initialization("AOTCache and AOTCacheOutput have different values"); + } + + if (!has_cache) { + precond(has_cache_output); + FLAG_SET_ERGO(AOTCache, AOTCacheOutput); } _is_dumping_final_static_archive = true; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index e96291f653487..8bf9378a3b3d2 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -41,6 +41,8 @@ class CDSConfig : public AllStatic { static bool _is_dumping_full_module_graph; static bool _is_using_full_module_graph; static bool _has_aot_linked_classes; + static bool _is_one_step_training; + static bool _has_temp_aot_config_file; const static char* _default_archive_path; const static char* _input_static_archive_path; @@ -140,6 +142,9 @@ class CDSConfig : public AllStatic { // Misc CDS features static bool allow_only_single_java_thread() NOT_CDS_RETURN_(false); + static bool is_one_step_training() { return CDS_ONLY(_is_one_step_training) NOT_CDS(false); } + static bool has_temp_aot_config_file() { return CDS_ONLY(_has_temp_aot_config_file) NOT_CDS(false); } + // This is *Legacy* optimization for lambdas before JEP 483. May be removed in the future. static bool is_dumping_lambdas_in_legacy_mode() NOT_CDS_RETURN_(false); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 2dae9b452212e..2487e21b4ca69 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -116,6 +116,10 @@ product(ccstr, AOTCache, nullptr, \ "Cache for improving start up and warm up") \ \ + product(ccstr, AOTCacheOutput, nullptr, \ + "Write AOT cache into this file (overrides AOTCache when " \ + "writing)") \ + \ product(bool, AOTInvokeDynamicLinking, false, DIAGNOSTIC, \ "AOT-link JVM_CONSTANT_InvokeDynamic entries in cached " \ "ConstantPools") \ diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index ef2a6dcb8e63c..cb6445017ef94 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -69,6 +69,7 @@ #include "memory/memoryReserver.hpp" #include "memory/metaspace.hpp" #include "memory/metaspaceClosure.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "nmt/memTracker.hpp" @@ -810,7 +811,6 @@ void MetaspaceShared::preload_and_dump(TRAPS) { // We are in the JVM that runs the training run. Continue execution, // so that it can finish all clean-up and return the correct exit // code to the OS. - tty->print_cr("AOTConfiguration recorded: %s", AOTConfiguration); } else { // The JLI launcher only recognizes the "old" -Xshare:dump flag. // When the new -XX:AOTMode=create flag is used, we can't return @@ -1005,7 +1005,16 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS VM_PopulateDumpSharedSpace op(builder); VMThread::execute(&op); - if (!write_static_archive(&builder, op.map_info(), op.heap_info())) { + bool status = write_static_archive(&builder, op.map_info(), op.heap_info()); + if (status && CDSConfig::is_dumping_preimage_static_archive()) { + tty->print_cr("%s AOTConfiguration recorded: %s", + CDSConfig::has_temp_aot_config_file() ? "Temporary" : "", AOTConfiguration); + if (CDSConfig::is_one_step_training()) { + fork_and_dump_final_static_archive(CHECK); + } + } + + if (!status) { THROW_MSG(vmSymbols::java_io_IOException(), "Encountered error while dumping"); } } @@ -1028,6 +1037,111 @@ bool MetaspaceShared::write_static_archive(ArchiveBuilder* builder, FileMapInfo* return true; } +static void print_java_launcher(outputStream* st) { + st->print("%s%sbin%sjava", Arguments::get_java_home(), os::file_separator(), os::file_separator()); +} + +static void append_args(GrowableArray* args, const char* arg, TRAPS) { + Handle string = java_lang_String::create_from_str(arg, CHECK); + args->append(string); +} + +// Pass all options in Arguments::jvm_args_array() to a child JVM process +// using the JAVA_TOOL_OPTIONS environment variable. +static int exec_jvm_with_java_tool_options(const char* java_launcher_path, TRAPS) { + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + GrowableArray args; + + const char* cp = Arguments::get_appclasspath(); + if (cp != nullptr && strlen(cp) > 0 && strcmp(cp, ".") != 0) { + // We cannot use "-cp", because "-cp" is only interpreted by the java launcher, + // and is not interpreter by arguments.cpp when it loads args from JAVA_TOOL_OPTIONS + stringStream ss; + ss.print("-Djava.class.path="); + ss.print_raw(cp); + append_args(&args, ss.freeze(), CHECK_0); + // CDS$ProcessLauncher::execWithJavaToolOptions() must unset CLASSPATH, which has + // a higher priority than -Djava.class.path= + } + + // Pass all arguments. These include those from JAVA_TOOL_OPTIONS and _JAVA_OPTIONS. + for (int i = 0; i < Arguments::num_jvm_args(); i++) { + const char* arg = Arguments::jvm_args_array()[i]; + if (strncmp("-XX:AOTMode", arg, 11) == 0) { + // Filter it out. We will set AOTMode=create below. + } else { + append_args(&args, arg, CHECK_0); + } + } + + // We don't pass Arguments::jvm_flags_array(), as those will be added by + // the child process when it loads .hotspotrc + + if (CDSConfig::has_temp_aot_config_file()) { + stringStream ss; + ss.print("-XX:AOTConfiguration="); + ss.print_raw(AOTConfiguration); + append_args(&args, ss.freeze(), CHECK_0); + } + append_args(&args, "-XX:AOTMode=create", CHECK_0); + + Symbol* klass_name = SymbolTable::new_symbol("jdk/internal/misc/CDS$ProcessLauncher"); + Klass* k = SystemDictionary::resolve_or_fail(klass_name, true, CHECK_0); + Symbol* methodName = SymbolTable::new_symbol("execWithJavaToolOptions"); + Symbol* methodSignature = SymbolTable::new_symbol("(Ljava/lang/String;[Ljava/lang/String;)I"); + + Handle launcher = java_lang_String::create_from_str(java_launcher_path, CHECK_0); + objArrayOop array = oopFactory::new_objArray(vmClasses::String_klass(), args.length(), CHECK_0); + for (int i = 0; i < args.length(); i++) { + array->obj_at_put(i, args.at(i)()); + } + objArrayHandle launcher_args(THREAD, array); + + // The following call will pass all options inside the JAVA_TOOL_OPTIONS env variable to + // the child process. It will also clear the _JAVA_OPTIONS and CLASSPATH env variables for + // the child process. + // + // Note: the env variables are set only for the child process. They are not changed + // for the current process. See java.lang.ProcessBuilder::environment(). + JavaValue result(T_OBJECT); + JavaCallArguments javacall_args(2); + javacall_args.push_oop(launcher); + javacall_args.push_oop(launcher_args); + JavaCalls::call_static(&result, + InstanceKlass::cast(k), + methodName, + methodSignature, + &javacall_args, + CHECK_0); + return result.get_jint(); +} + +void MetaspaceShared::fork_and_dump_final_static_archive(TRAPS) { + assert(CDSConfig::is_dumping_preimage_static_archive(), "sanity"); + + ResourceMark rm; + stringStream ss; + print_java_launcher(&ss); + const char* cmd = ss.freeze(); + tty->print_cr("Launching child process %s to assemble AOT cache %s using configuration %s", cmd, AOTCacheOutput, AOTConfiguration); + int status = exec_jvm_with_java_tool_options(cmd, CHECK); + if (status != 0) { + log_error(cds)("Child process failed; status = %d", status); + // We leave the temp config file for debugging + } else if (CDSConfig::has_temp_aot_config_file()) { + const char* tmp_config = AOTConfiguration; + // On Windows, need WRITE permission to remove the file. + WINDOWS_ONLY(chmod(tmp_config, _S_IREAD | _S_IWRITE)); + status = remove(tmp_config); + if (status != 0) { + log_error(cds)("Failed to remove temporary AOT configuration file %s", tmp_config); + } else { + tty->print_cr("Removed temporary AOT configuration file %s", tmp_config); + } + } +} + // Returns true if the class's status has changed. bool MetaspaceShared::try_link_class(JavaThread* current, InstanceKlass* ik) { ExceptionMark em(current); diff --git a/src/hotspot/share/cds/metaspaceShared.hpp b/src/hotspot/share/cds/metaspaceShared.hpp index e03994be1b993..1ef70abfcc122 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -177,6 +177,7 @@ class MetaspaceShared : AllStatic { private: static void read_extra_data(JavaThread* current, const char* filename) NOT_CDS_RETURN; + static void fork_and_dump_final_static_archive(TRAPS); static bool write_static_archive(ArchiveBuilder* builder, FileMapInfo* map_info, ArchiveHeapInfo* heap_info); static FileMapInfo* open_static_archive(); static FileMapInfo* open_dynamic_archive(); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 69d37a11e45ab..49d26174b5238 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1945,12 +1945,12 @@ Arguments::ArgsRange Arguments::parse_memory_size(const char* s, return check_memory_size(*long_arg, min_size, max_size); } -// Parse JavaVMInitArgs structure - +// Parse JavaVMInitArgs (in the order of the parameters to this function) jint Arguments::parse_vm_init_args(const JavaVMInitArgs *vm_options_args, const JavaVMInitArgs *java_tool_options_args, + const JavaVMInitArgs *cmd_line_args, const JavaVMInitArgs *java_options_args, - const JavaVMInitArgs *cmd_line_args) { + const JavaVMInitArgs *aot_tool_options_args) { // Save default settings for some mode flags Arguments::_AlwaysCompileLoopMethods = AlwaysCompileLoopMethods; Arguments::_UseOnStackReplacement = UseOnStackReplacement; @@ -1963,32 +1963,51 @@ jint Arguments::parse_vm_init_args(const JavaVMInitArgs *vm_options_args, // Setup flags for mixed which is the default set_mode_flags(_mixed); - // Parse args structure generated from java.base vm options resource + // Parse args generated from java.base vm options resource jint result = parse_each_vm_init_arg(vm_options_args, JVMFlagOrigin::JIMAGE_RESOURCE); if (result != JNI_OK) { return result; } - // Parse args structure generated from JAVA_TOOL_OPTIONS environment + // Parse args generated from JAVA_TOOL_OPTIONS environment // variable (if present). result = parse_each_vm_init_arg(java_tool_options_args, JVMFlagOrigin::ENVIRON_VAR); if (result != JNI_OK) { return result; } - // Parse args structure generated from the command line flags. + // Parse args generated from the command line flags. result = parse_each_vm_init_arg(cmd_line_args, JVMFlagOrigin::COMMAND_LINE); if (result != JNI_OK) { return result; } - // Parse args structure generated from the _JAVA_OPTIONS environment + // Parse args generated from the _JAVA_OPTIONS environment // variable (if present) (mimics classic VM) result = parse_each_vm_init_arg(java_options_args, JVMFlagOrigin::ENVIRON_VAR); if (result != JNI_OK) { return result; } + // Parse args generated from the AOT_TOOL_OPTIONS environment variable -- only if AOTMode is "create" + if (aot_tool_options_args->nOptions > 0) { + assert(AOTMode != nullptr && strcmp(AOTMode, "create") == 0, "Required for parsing AOT_TOOL_OPTIONS"); + for (int index = 0; index < aot_tool_options_args->nOptions; index++) { + JavaVMOption* option = aot_tool_options_args->options + index; + const char* optionString = option->optionString; + if (strncmp(optionString, "-XX:AOTMode=", 12) == 0 && + strcmp(optionString, "-XX:AOTMode=create") != 0) { + jio_fprintf(defaultStream::error_stream(), + "Option %s cannot be specified in AOT_TOOL_OPTIONS\n", optionString); + return JNI_ERR; + } + } + result = parse_each_vm_init_arg(aot_tool_options_args, JVMFlagOrigin::ENVIRON_VAR); + if (result != JNI_OK) { + return result; + } + } + // Disable CDS for exploded image if (!has_jimage()) { no_shared_spaces("CDS disabled on exploded JDK"); @@ -3075,6 +3094,52 @@ jint Arguments::parse_java_tool_options_environment_variable(ScopedVMInitArgs* a return parse_options_environment_variable("JAVA_TOOL_OPTIONS", args); } +static JavaVMOption* get_last_aotmode_arg(const JavaVMInitArgs* args) { + for (int index = args->nOptions - 1; index >= 0; index--) { + JavaVMOption* option = args->options + index; + if (strncmp(option->optionString, "-XX:AOTMode=", 12) == 0) { + return option; + } + } + + return nullptr; +} + +jint Arguments::parse_aot_tool_options_environment_variable(const JavaVMInitArgs* vm_options_args, + const JavaVMInitArgs* java_tool_options_args, + const JavaVMInitArgs* cmd_line_args, + const JavaVMInitArgs* java_options_args, + ScopedVMInitArgs* aot_tool_options_args) { + // Don't bother scanning all the args if this env variable is not set + if (::getenv("AOT_TOOL_OPTIONS") == nullptr) { + return JNI_OK; + } + + // The JavaVMInitArgs will be parsed by parse_vm_init_args() in the order of the + // parameters to this function, so let's look backwards and find the last occurrence + // of -XX:AOTMode=xxx, which will decide the value of AOTMode. + JavaVMOption* option; + if ((option = get_last_aotmode_arg(java_options_args)) != nullptr || + (option = get_last_aotmode_arg(cmd_line_args)) != nullptr || + (option = get_last_aotmode_arg(java_tool_options_args)) != nullptr || + (option = get_last_aotmode_arg(vm_options_args)) != nullptr) { + // We have found the last -XX:AOTMode=xxx in the above 4 set of args. At this point + //