From dede3532f7238d527fb89be41f1b8050bde02ee3 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 28 May 2025 22:12:14 +0000 Subject: [PATCH] 8355798: Implement JEP 514: Ahead-of-Time Command Line Ergonomics Reviewed-by: erikj, kvn, asmehra --- doc/testing.html | 34 +++ doc/testing.md | 37 ++++ make/RunTests.gmk | 59 +++-- src/hotspot/share/cds/cdsConfig.cpp | 124 +++++++++-- src/hotspot/share/cds/cdsConfig.hpp | 5 + src/hotspot/share/cds/cds_globals.hpp | 3 + src/hotspot/share/cds/filemap.cpp | 2 +- src/hotspot/share/cds/metaspaceShared.cpp | 139 +++++++++++- src/hotspot/share/cds/metaspaceShared.hpp | 1 + .../classfile/systemDictionaryShared.cpp | 2 +- src/hotspot/share/runtime/arguments.cpp | 156 +++++++++---- src/hotspot/share/runtime/arguments.hpp | 10 +- .../share/classes/jdk/internal/misc/CDS.java | 43 ++++ src/java.base/share/man/java.md | 83 +++++-- test/hotspot/jtreg/TEST.groups | 2 + .../runtime/cds/appcds/UseAppCDS_Test.java | 30 --- .../appcds/aotCache/SpecialCacheNames.java | 120 ++++++++++ .../aotClassLinking/MethodHandleTest.java | 2 +- .../aotClassLinking/WeakReferenceTest.java | 2 +- .../cds/appcds/aotCode/AOTCodeFlags.java | 2 +- .../cds/appcds/{ => aotFlags}/AOTFlags.java | 141 +++++++++++- .../appcds/aotFlags/FileNameSubstitution.java | 207 ++++++++++++++++++ .../appcds/aotFlags/JDK_AOT_VM_OPTIONS.java | 139 ++++++++++++ test/lib/jdk/test/lib/cds/CDSAppTester.java | 95 ++++++-- test/setup_aot/TestSetupAOT.java | 7 +- 25 files changed, 1275 insertions(+), 170 deletions(-) delete mode 100644 test/hotspot/jtreg/runtime/cds/appcds/UseAppCDS_Test.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java rename test/hotspot/jtreg/runtime/cds/appcds/{ => aotFlags}/AOTFlags.java (69%) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotFlags/FileNameSubstitution.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotFlags/JDK_AOT_VM_OPTIONS.java diff --git a/doc/testing.html b/doc/testing.html index c1c0f15fed9..6288a552bf0 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -72,6 +72,9 @@ id="toc-notes-for-specific-tests">Notes for Specific Tests
  • Non-US locale
  • PKCS11 Tests
  • +
  • Testing Ahead-of-time +Optimizations
  • Testing with alternative security providers
  • @@ -596,6 +599,37 @@ element of the appropriate @Artifact class. (See JTREG="JAVA_OPTIONS=-Djdk.test.lib.artifacts.nsslib-linux_aarch64=/path/to/NSS-libs"

    For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README.

    +

    Testing Ahead-of-time +Optimizations

    +

    One way to improve test coverage of ahead-of-time (AOT) optimizations +in the JDK is to run existing jtreg test cases in a special "AOT_JDK" +mode. Example:

    +
    $ make test JTREG="AOT_JDK=onestep" \
    +    TEST=open/test/hotspot/jtreg/runtime/invokedynamic
    +

    In this testing mode, we first perform an AOT training run (see +https://openjdk.org/jeps/483) of a special test program (test/setup_aot/TestSetupAOT.java) +that accesses about 5,0000 classes in the JDK core libraries. +Optimization artifacts for these classes (such as pre-linked lambda +expressions, execution profiles, and pre-generated native code) are +stored into an AOT cache file, which will be used by all the JVMs +launched by the selected jtreg test cases.

    +

    When the jtreg tests call into the core libraries classes that are in +the AOT cache, we will be able to test the AOT optimizations that were +used on those classes.

    +

    Please note that not all existing jtreg test cases can be executed +with the AOT_JDK mode. See test/hotspot/jtreg/ProblemList-AotJdk.txt +and test/jdk/ProblemList-AotJdk.txt.

    +

    Also, test cases that were written specifically to test AOT, such as +the tests under test/hotspot/jtreg/runtime/cds, +cannot be executed with the AOT_JDK mode.

    +

    Valid values for AOT_JDK are onestep and +twostep. These control how the AOT cache is generated. See +https://openjdk.org/jeps/514 for details. All other values are +ignored.

    Testing with alternative security providers

    Some security tests use a hardcoded provider for diff --git a/doc/testing.md b/doc/testing.md index f6a25457b5e..bb56c05c295 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -611,6 +611,43 @@ $ make test TEST="jtreg:sun/security/pkcs11/Secmod/AddTrustedCert.java" \ For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README. +### Testing Ahead-of-time Optimizations +------------------------------------------------------------------------------- +One way to improve test coverage of ahead-of-time (AOT) optimizations in +the JDK is to run existing jtreg test cases in a special "AOT_JDK" mode. +Example: + +``` +$ make test JTREG="AOT_JDK=onestep" \ + TEST=open/test/hotspot/jtreg/runtime/invokedynamic +``` + +In this testing mode, we first perform an AOT training run +(see https://openjdk.org/jeps/483) of a special test program +([test/setup_aot/TestSetupAOT.java](../test/setup_aot/TestSetupAOT.java)) +that accesses about 5,0000 classes in the JDK core libraries. +Optimization artifacts for these classes (such as pre-linked +lambda expressions, execution profiles, and pre-generated native code) +are stored into an AOT cache file, which will be used by all the JVMs +launched by the selected jtreg test cases. + +When the jtreg tests call into the core libraries classes that are in +the AOT cache, we will be able to test the AOT optimizations that were +used on those classes. + +Please note that not all existing jtreg test cases can be executed with +the AOT_JDK mode. See +[test/hotspot/jtreg/ProblemList-AotJdk.txt](../test/hotspot/jtreg/ProblemList-AotJdk.txt) +and [test/jdk/ProblemList-AotJdk.txt](../test/jdk/ProblemList-AotJdk.txt). + +Also, test cases that were written specifically to test AOT, such as the tests +under [test/hotspot/jtreg/runtime/cds](../test/hotspot/jtreg/runtime/cds/), +cannot be executed with the AOT_JDK mode. + +Valid values for `AOT_JDK` are `onestep` and `twostep`. These control how +the AOT cache is generated. See https://openjdk.org/jeps/514 for details. +All other values are ignored. + ### Testing with alternative security providers Some security tests use a hardcoded provider for `KeyFactory`, `Cipher`, diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 51c75cc4872..60ae1bd4763 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -725,6 +725,7 @@ endef # Parameter 1 is the name of the rule. # # Remaining parameters are named arguments. +# TRAINING The AOT training mode: onestep or twostep # VM_OPTIONS List of JVM arguments to use when creating AOT cache # # After calling this, the following variables are defined @@ -753,23 +754,39 @@ define SetupAOTBody $$($1_AOT_JDK_CACHE): $$(JDK_IMAGE_DIR)/release $$(call MakeDir, $$($1_AOT_JDK_OUTPUT_DIR)) - $$(call LogWarn, AOT: Create cache configuration) \ - $$(call ExecuteWithLog, $$($1_AOT_JDK_OUTPUT_DIR), ( \ - cd $$($1_AOT_JDK_OUTPUT_DIR); \ - $(JAR) --extract --file $(TEST_IMAGE_DIR)/setup_aot/TestSetupAOT.jar; \ - $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java $$($1_VM_OPTIONS) \ - -Xlog:class+load,aot,aot+class=debug:file=$$($1_AOT_JDK_CONF).log -Xlog:cds*=error -Xlog:aot*=error \ - -XX:AOTMode=record -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) \ - TestSetupAOT $$($1_AOT_JDK_OUTPUT_DIR) > $$($1_AOT_JDK_LOG) \ - )) + ifeq ($$($1_TRAINING), onestep) - $$(call LogWarn, AOT: Generate AOT cache $$($1_AOT_JDK_CACHE) with flags: $$($1_VM_OPTIONS)) - $$(call ExecuteWithLog, $$($1_AOT_JDK_OUTPUT_DIR), ( \ - $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java \ - $$($1_VM_OPTIONS) -Xlog:aot,aot+class=debug:file=$$($1_AOT_JDK_CACHE).log -Xlog:cds*=error -Xlog:aot*=error \ - -XX:ExtraSharedClassListFile=$(JDK_UNDER_TEST)/lib/classlist \ - -XX:AOTMode=create -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) -XX:AOTCache=$$($1_AOT_JDK_CACHE) \ - )) + $$(call LogWarn, AOT: Create AOT cache $$($1_AOT_JDK_CACHE) in one step with flags: $$($1_VM_OPTIONS)) \ + $$(call ExecuteWithLog, $$($1_AOT_JDK_OUTPUT_DIR), ( \ + cd $$($1_AOT_JDK_OUTPUT_DIR); \ + $(JAR) --extract --file $(TEST_IMAGE_DIR)/setup_aot/TestSetupAOT.jar; \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java $$($1_VM_OPTIONS) \ + -Xlog:class+load,aot,aot+class=debug:file=$$($1_AOT_JDK_CACHE).log -Xlog:cds*=error -Xlog:aot*=error \ + -XX:AOTMode=record -XX:AOTCacheOutput=$$($1_AOT_JDK_CACHE) \ + TestSetupAOT $$($1_AOT_JDK_OUTPUT_DIR) > $$($1_AOT_JDK_LOG) \ + )) + + else + + $$(call LogWarn, AOT: Create cache configuration) \ + $$(call ExecuteWithLog, $$($1_AOT_JDK_OUTPUT_DIR), ( \ + cd $$($1_AOT_JDK_OUTPUT_DIR); \ + $(JAR) --extract --file $(TEST_IMAGE_DIR)/setup_aot/TestSetupAOT.jar; \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java $$($1_VM_OPTIONS) \ + -Xlog:class+load,aot,aot+class=debug:file=$$($1_AOT_JDK_CONF).log -Xlog:cds*=error -Xlog:aot*=error \ + -XX:AOTMode=record -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) \ + TestSetupAOT $$($1_AOT_JDK_OUTPUT_DIR) > $$($1_AOT_JDK_LOG) \ + )) + + $$(call LogWarn, AOT: Generate AOT cache $$($1_AOT_JDK_CACHE) with flags: $$($1_VM_OPTIONS)) + $$(call ExecuteWithLog, $$($1_AOT_JDK_OUTPUT_DIR), ( \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java \ + $$($1_VM_OPTIONS) -Xlog:aot,aot+class=debug:file=$$($1_AOT_JDK_CACHE).log -Xlog:cds*=error -Xlog:aot*=error \ + -XX:ExtraSharedClassListFile=$(JDK_UNDER_TEST)/lib/classlist \ + -XX:AOTMode=create -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) -XX:AOTCache=$$($1_AOT_JDK_CACHE) \ + )) + + endif $1_AOT_TARGETS += $$($1_AOT_JDK_CACHE) @@ -835,7 +852,7 @@ define SetupRunJtregTestBody JTREG_RETRY_COUNT ?= 0 JTREG_REPEAT_COUNT ?= 0 JTREG_REPORT ?= files - JTREG_AOT_JDK ?= false + JTREG_AOT_JDK ?= none ifneq ($$(JTREG_RETRY_COUNT), 0) ifneq ($$(JTREG_REPEAT_COUNT), 0) @@ -975,12 +992,12 @@ define SetupRunJtregTestBody endif endif - ifeq ($$(JTREG_AOT_JDK), true) + ifneq ($$(filter $$(JTREG_AOT_JDK), onestep twostep), ) $$(call LogWarn, Add AOT target for $1) - $$(eval $$(call SetupAOT, $1, VM_OPTIONS := $$(JTREG_ALL_OPTIONS) )) - + $$(eval $$(call SetupAOT, $1, \ + TRAINING := $$(JTREG_AOT_JDK), \ + VM_OPTIONS := $$(JTREG_ALL_OPTIONS) )) $$(call LogWarn, AOT_JDK_CACHE=$$($1_AOT_JDK_CACHE)) - $1_JTREG_BASIC_OPTIONS += -vmoption:-XX:AOTCache="$$($1_AOT_JDK_CACHE)" endif diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index c8888fe6bb3..b3c13b63aff 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -49,6 +49,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_single_command_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; @@ -398,12 +400,14 @@ 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) && AOTAdapterCaching) { @@ -413,29 +417,41 @@ void CDSConfig::check_aot_flags() { log_debug(aot,codecache,init)("AOTCache is not specified - AOTStubCaching is ignored"); } - if (FLAG_IS_DEFAULT(AOTCache) && FLAG_IS_DEFAULT(AOTConfiguration) && FLAG_IS_DEFAULT(AOTMode)) { - // AOTCache/AOTConfiguration/AOTMode not used -> using the "classic CDS" workflow. + bool has_cache = !FLAG_IS_DEFAULT(AOTCache); + bool has_cache_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + bool has_config = !FLAG_IS_DEFAULT(AOTConfiguration); + bool has_mode = !FLAG_IS_DEFAULT(AOTMode); + + if (!has_cache && !has_cache_output && !has_config && !has_mode) { + // AOT flags are not used. Use classic CDS workflow return; - } else { - _new_aot_flags_used = true; } + if (has_cache && has_cache_output) { + vm_exit_during_initialization("Only one of AOTCache or AOTCacheOutput can be specified"); + } + + if (!has_cache && (!has_mode || strcmp(AOTMode, "auto") == 0)) { + if (has_cache_output) { + // If AOTCacheOutput has been set, effective mode is "record". + // Default value for AOTConfiguration, if necessary, will be assigned in check_aotmode_record(). + log_info(aot)("Selected AOTMode=record because AOTCacheOutput is specified"); + 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(); } // This is an old flag used by CDS regression testing only. It doesn't apply @@ -450,7 +466,8 @@ void CDSConfig::check_aotmode_off() { void CDSConfig::check_aotmode_auto_or_on() { if (!FLAG_IS_DEFAULT(AOTConfiguration)) { - vm_exit_during_initialization("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create"); + vm_exit_during_initialization(err_msg("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = %s)", + FLAG_IS_DEFAULT(AOTMode) ? "auto" : AOTMode)); } UseSharedSpaces = true; @@ -462,11 +479,63 @@ void CDSConfig::check_aotmode_auto_or_on() { } } +// %p substitution in AOTCache, AOTCacheOutput and AOTCacheConfiguration +static void substitute_aot_filename(JVMFlagsEnum flag_enum) { + JVMFlag* flag = JVMFlag::flag_from_enum(flag_enum); + const char* filename = flag->read(); + assert(filename != nullptr, "must not have default value"); + + // For simplicity, we don't allow %p/%t to be specified twice, because make_log_name() + // substitutes only the first occurrence. Otherwise, if we run with + // java -XX:AOTCacheOutput=%p%p.aot + // it will end up with both the pid of the training process and the assembly process. + const char* first_p = strstr(filename, "%p"); + if (first_p != nullptr && strstr(first_p + 2, "%p") != nullptr) { + vm_exit_during_initialization(err_msg("%s cannot contain more than one %%p", flag->name())); + } + const char* first_t = strstr(filename, "%t"); + if (first_t != nullptr && strstr(first_t + 2, "%t") != nullptr) { + vm_exit_during_initialization(err_msg("%s cannot contain more than one %%t", flag->name())); + } + + // Note: with single-command training, %p will be the pid of the training process, not the + // assembly process. + const char* new_filename = make_log_name(filename, nullptr); + if (strcmp(filename, new_filename) != 0) { + JVMFlag::Error err = JVMFlagAccess::set_ccstr(flag, &new_filename, JVMFlagOrigin::ERGONOMIC); + assert(err == JVMFlag::SUCCESS, "must never fail"); + } + FREE_C_HEAP_ARRAY(char, new_filename); +} + void CDSConfig::check_aotmode_record() { + bool has_config = !FLAG_IS_DEFAULT(AOTConfiguration); + bool has_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + + if (!has_output && !has_config) { + vm_exit_during_initialization("At least one of AOTCacheOutput and AOTConfiguration must be specified when using -XX:AOTMode=record"); + } + + if (has_output) { + _is_single_command_training = true; + substitute_aot_filename(FLAG_MEMBER_ENUM(AOTCacheOutput)); + 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; + } + } + if (!FLAG_IS_DEFAULT(AOTCache)) { vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record"); } + substitute_aot_filename(FLAG_MEMBER_ENUM(AOTConfiguration)); + UseSharedSpaces = false; RequireSharedSpaces = false; _is_dumping_static_archive = true; @@ -478,10 +547,27 @@ 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("AOTConfiguration must be specified when using -XX:AOTMode=create"); } + bool has_cache = !FLAG_IS_DEFAULT(AOTCache); + bool has_cache_output = !FLAG_IS_DEFAULT(AOTCacheOutput); + + assert(!(has_cache && has_cache_output), "already checked"); + + if (!has_cache && !has_cache_output) { + vm_exit_during_initialization("AOTCache or AOTCacheOutput must be specified when using -XX:AOTMode=create"); + } + + if (!has_cache) { + precond(has_cache_output); + FLAG_SET_ERGO(AOTCache, AOTCacheOutput); + } + // No need to check for (!has_cache_output), as we don't look at AOTCacheOutput after here. + + substitute_aot_filename(FLAG_MEMBER_ENUM(AOTCache)); + _is_dumping_final_static_archive = true; UseSharedSpaces = true; RequireSharedSpaces = true; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 22378f5c42c..1fd229ff34f 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_single_command_training; + static bool _has_temp_aot_config_file; const static char* _default_archive_path; const static char* _input_static_archive_path; @@ -142,6 +144,9 @@ public: // Misc CDS features static bool allow_only_single_java_thread() NOT_CDS_RETURN_(false); + static bool is_single_command_training() { return CDS_ONLY(_is_single_command_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 aab5647e0b8..e51dd26ff06 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -119,6 +119,9 @@ "Cache for improving start up and warm up") \ constraint(AOTCacheConstraintFunc, AtParse) \ \ + product(ccstr, AOTCacheOutput, nullptr, \ + "Specifies the file name for writing the AOT cache") \ + \ product(bool, AOTInvokeDynamicLinking, false, DIAGNOSTIC, \ "AOT-link JVM_CONSTANT_InvokeDynamic entries in cached " \ "ConstantPools") \ diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 7094e1c326d..375a3ccffbc 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -744,7 +744,7 @@ void FileMapInfo::open_as_output() { if (CDSConfig::is_dumping_preimage_static_archive()) { log_info(aot)("Writing binary AOTConfiguration file: %s", _full_path); } else { - log_info(aot)("Writing binary AOTConfiguration file: %s", _full_path); + log_info(aot)("Writing AOTCache file: %s", _full_path); } } else { aot_log_info(aot)("Dumping shared data to file: %s", _full_path); diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 4e82395dba9..233e64c5e3e 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -72,6 +72,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" @@ -828,7 +829,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 @@ -1027,7 +1027,16 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS CDSConfig::disable_dumping_aot_code(); } - 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_single_command_training()) { + fork_and_dump_final_static_archive(CHECK); + } + } + + if (!status) { THROW_MSG(vmSymbols::java_io_IOException(), "Encountered error while dumping"); } } @@ -1050,6 +1059,132 @@ 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 (strstr(arg, "-XX:AOTCacheOutput=") == arg || // arg starts with ... + strstr(arg, "-XX:AOTConfiguration=") == arg || + strstr(arg, "-XX:AOTMode=") == arg) { + // Filter these out. They wiill be set below. + } else { + append_args(&args, arg, CHECK_0); + } + } + + // Note: because we are running in AOTMode=record, JDK_AOT_VM_OPTIONS have not been + // parsed, so they are not in Arguments::jvm_args_array. If JDK_AOT_VM_OPTIONS is in + // the environment, it will be inherited and parsed by the child JVM process + // in Arguments::parse_java_tool_options_environment_variable(). + precond(strcmp(AOTMode, "record") == 0); + + // We don't pass Arguments::jvm_flags_array(), as those will be added by + // the child process when it loads .hotspotrc + + { + // If AOTCacheOutput contains %p, it should have been already substituted with the + // pid of the training process. + stringStream ss; + ss.print("-XX:AOTCacheOutput="); + ss.print_raw(AOTCacheOutput); + append_args(&args, ss.freeze(), CHECK_0); + } + { + // If AOTCacheConfiguration contains %p, it should have been already substituted with the + // pid of the training process. + // If AOTCacheConfiguration was not explicitly specified, it should have been assigned a + // temporary file name. + 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(aot)("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(aot)("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 2423141b8fa..130e7fe4484 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -179,6 +179,7 @@ public: 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/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index c949119d363..8bd09a0d947 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -309,7 +309,7 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { return warn_excluded(k, "Unlinked class not supported by AOTClassLinking"); } else if (CDSConfig::is_dumping_preimage_static_archive()) { // When dumping the final static archive, we will unconditionally load and link all - // classes from tje preimage. We don't want to get a VerifyError when linking this class. + // classes from the preimage. We don't want to get a VerifyError when linking this class. return warn_excluded(k, "Unlinked class not supported by AOTConfiguration"); } } else { diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 62fcc65743a..b7ce095b41b 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -136,6 +136,11 @@ static bool xshare_auto_cmd_line = false; // True if -Xint/-Xmixed/-Xcomp were specified static bool mode_flag_cmd_line = false; +struct VMInitArgsGroup { + const JavaVMInitArgs* _args; + JVMFlagOrigin _origin; +}; + bool PathString::set_value(const char *value, AllocFailType alloc_failmode) { char* new_value = AllocateHeap(strlen(value)+1, mtArguments, alloc_failmode); if (new_value == nullptr) { @@ -1950,12 +1955,7 @@ Arguments::ArgsRange Arguments::parse_memory_size(const char* s, return check_memory_size(*long_arg, min_size, max_size); } -// Parse JavaVMInitArgs structure - -jint Arguments::parse_vm_init_args(const JavaVMInitArgs *vm_options_args, - const JavaVMInitArgs *java_tool_options_args, - const JavaVMInitArgs *java_options_args, - const JavaVMInitArgs *cmd_line_args) { +jint Arguments::parse_vm_init_args(GrowableArrayCHeap* all_args) { // Save default settings for some mode flags Arguments::_AlwaysCompileLoopMethods = AlwaysCompileLoopMethods; Arguments::_UseOnStackReplacement = UseOnStackReplacement; @@ -1968,30 +1968,12 @@ 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 - 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 - // 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. - 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 - // variable (if present) (mimics classic VM) - result = parse_each_vm_init_arg(java_options_args, JVMFlagOrigin::ENVIRON_VAR); - if (result != JNI_OK) { - return result; + jint result; + for (int i = 0; i < all_args->length(); i++) { + result = parse_each_vm_init_arg(all_args->at(i)._args, all_args->at(i)._origin); + if (result != JNI_OK) { + return result; + } } // Disable CDS for exploded image @@ -3093,6 +3075,50 @@ 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 (strstr(option->optionString, "-XX:AOTMode=") == option->optionString) { + return option; + } + } + + return nullptr; +} + +jint Arguments::parse_jdk_aot_vm_options_environment_variable(GrowableArrayCHeap* all_args, + ScopedVMInitArgs* jdk_aot_vm_options_args) { + // Don't bother scanning all the args if this env variable is not set + if (::getenv("JDK_AOT_VM_OPTIONS") == nullptr) { + return JNI_OK; + } + + // Scan backwards and find the last occurrence of -XX:AOTMode=xxx, which will decide the value + // of AOTMode. + JavaVMOption* option = nullptr; + for (int i = all_args->length() - 1; i >= 0; i--) { + if ((option = get_last_aotmode_arg(all_args->at(i)._args)) != nullptr) { + break; + } + } + + if (option != nullptr) { + // We have found the last -XX:AOTMode=xxx. At this point