From 6e92aee64b40bde5b4604b512f0b550720135bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=9B=E7=B3=BB=E5=B0=91=E5=B9=B4=E4=B8=AD=E4=BA=8C?= Date: Fri, 29 Nov 2024 18:04:50 +0800 Subject: [PATCH] IB7YRU: Add jbolt and enhance jbooster --- ...RebuildRemsetPhase.java-fails-with-S.patch | 24 + Add-Fix-clear-mark-for-NativeGotJump.patch | 24 + Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch | 121 + Add-jbolt-feature.patch | 5221 +++++++++++++++++ ...zed-hashmap-version-of-the-long-type.patch | 4917 ++++++++++++++++ ...mmunciation-between-JBooster-Server-.patch | 2652 +++++++++ Implement-JBooster-RPC-byte-alignment.patch | 699 +++ ...ize-LazyAOT-klasses-sending-strategy.patch | 880 +++ SA-redact-support-password.patch | 623 ++ openjdk-17.spec | 32 +- 10 files changed, 15192 insertions(+), 1 deletion(-) create mode 100644 8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch create mode 100644 Add-Fix-clear-mark-for-NativeGotJump.patch create mode 100644 Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch create mode 100644 Add-jbolt-feature.patch create mode 100644 Add-specialized-hashmap-version-of-the-long-type.patch create mode 100644 Enable-TLS-to-communciation-between-JBooster-Server-.patch create mode 100644 Implement-JBooster-RPC-byte-alignment.patch create mode 100644 Optimize-LazyAOT-klasses-sending-strategy.patch create mode 100644 SA-redact-support-password.patch diff --git a/8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch b/8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch new file mode 100644 index 0000000..e4998b6 --- /dev/null +++ b/8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch @@ -0,0 +1,24 @@ +From 4c0d9de31c79d6e8e71fda0d8bc67c7352451dc0 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 16:48:18 +0800 +Subject: 8323066 TestSkipRebuildRemsetPhase.java fails with Skipping Remembered Set Rebuild + +--- + test/hotspot/jtreg/gc/g1/TestSkipRebuildRemsetPhase.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/hotspot/jtreg/gc/g1/TestSkipRebuildRemsetPhase.java b/test/hotspot/jtreg/gc/g1/TestSkipRebuildRemsetPhase.java +index 860d3ce48..1a4c13132 100644 +--- a/test/hotspot/jtreg/gc/g1/TestSkipRebuildRemsetPhase.java ++++ b/test/hotspot/jtreg/gc/g1/TestSkipRebuildRemsetPhase.java +@@ -45,7 +45,7 @@ public class TestSkipRebuildRemsetPhase { + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", +- "-XX:G1MixedGCLiveThresholdPercent=20", ++ "-XX:G1MixedGCLiveThresholdPercent=0", + "-Xlog:gc+marking=debug,gc+phases=debug,gc+remset+tracking=trace", + "-Xms10M", + "-Xmx10M", +-- +2.22.0 + diff --git a/Add-Fix-clear-mark-for-NativeGotJump.patch b/Add-Fix-clear-mark-for-NativeGotJump.patch new file mode 100644 index 0000000..64e7783 --- /dev/null +++ b/Add-Fix-clear-mark-for-NativeGotJump.patch @@ -0,0 +1,24 @@ +From 530c07c5f332d2bce540acb181652f64457ec6c6 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 16:00:40 +0800 +Subject: Add Fix clear mark for NativeGotJump + +--- + src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp +index fe7461964..f9438c235 100644 +--- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp ++++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.cpp +@@ -131,7 +131,7 @@ void NativePltCall::set_stub_to_clean() { + NativeLoadGot* method_loader = nativeLoadGot_at(plt_c2i_stub()); + NativeGotJump* jump = nativeGotJump_at(method_loader->next_instruction_address()); + method_loader->set_data(0); +- jump->set_jump_destination((address)-1); ++ jump->set_jump_destination((address)Universe::non_oop_word()); + } + + void NativePltCall::verify() const { +-- +2.22.0 + diff --git a/Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch b/Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch new file mode 100644 index 0000000..de8576d --- /dev/null +++ b/Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch @@ -0,0 +1,121 @@ +From e2fd78d2cb7771f7cbc884e8c36d9a91fa0f8696 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:40:37 +0800 +Subject: Add KAE-zip GzipKAEBenchmark Benchmark + +--- + test/jdk/java/util/zip/GzipKAEBenchmark.java | 102 +++++++++++++++++++ + 1 file changed, 102 insertions(+) + create mode 100644 test/jdk/java/util/zip/GzipKAEBenchmark.java + +diff --git a/test/jdk/java/util/zip/GzipKAEBenchmark.java b/test/jdk/java/util/zip/GzipKAEBenchmark.java +new file mode 100644 +index 000000000..6d753e14e +--- /dev/null ++++ b/test/jdk/java/util/zip/GzipKAEBenchmark.java +@@ -0,0 +1,102 @@ ++/* ++ * Copyright (c) 2024, Huawei Technologies Co., Ltd. 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. ++ */ ++ ++import org.openjdk.jmh.annotations.BenchmarkMode; ++import org.openjdk.jmh.annotations.Benchmark; ++import org.openjdk.jmh.annotations.Fork; ++import org.openjdk.jmh.annotations.Measurement; ++import org.openjdk.jmh.annotations.Mode; ++import org.openjdk.jmh.annotations.OutputTimeUnit; ++import org.openjdk.jmh.annotations.Scope; ++import org.openjdk.jmh.annotations.Param; ++import org.openjdk.jmh.annotations.Setup; ++import org.openjdk.jmh.annotations.State; ++import org.openjdk.jmh.annotations.Threads; ++import org.openjdk.jmh.annotations.Warmup; ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++import java.util.zip.GZIPInputStream; ++import java.util.zip.GZIPOutputStream; ++import java.util.concurrent.TimeUnit; ++import java.util.Random; ++ ++@BenchmarkMode(Mode.SampleTime) ++@OutputTimeUnit(TimeUnit.MILLISECONDS) ++@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.MILLISECONDS) ++@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.MILLISECONDS) ++@Fork(jvmArgsPrepend = {"-Xms1G", "-Xmx1G", "-XX:+AlwaysPreTouch"}, value = 1) ++@Threads(1) ++@State(Scope.Benchmark) ++public class GzipKAEBenchmark { ++ ++ private byte[] srcBytes; ++ private byte[] dstBytes; ++ ++ Random rnd = new Random(8192); ++ ByteArrayOutputStream srcBAOS = new ByteArrayOutputStream(); ++ ByteArrayOutputStream dstBAOS = new ByteArrayOutputStream(); ++ ++ @Setup ++ public void setup() throws IOException { ++ Random rnd = new Random(8192); ++ ByteArrayOutputStream srcBAOS = new ByteArrayOutputStream(); ++ ByteArrayOutputStream dstBAOS = new ByteArrayOutputStream(); ++ for (int j = 0; j < 8192; j++) { ++ byte[] src = new byte[rnd.nextInt(8192) + 1]; ++ rnd.nextBytes(src); ++ srcBAOS.write(src); ++ } ++ srcBytes = srcBAOS.toByteArray(); ++ try (GZIPOutputStream gzos = new GZIPOutputStream(dstBAOS)) { ++ gzos.write(srcBytes); ++ } ++ dstBytes = dstBAOS.toByteArray(); ++ } ++ ++ @Param({"512", "2048", "10240", "51200", "204800"}) ++ private int BuffSize; ++ ++ @Benchmark ++ public void GzipDeflateTest() throws IOException{ ++ ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream(); ++ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayInputStream, BuffSize)) { ++ gzipOutputStream.write(srcBytes); ++ } ++ } ++ ++ @Benchmark ++ public void GzipInflateTest() { ++ ByteArrayInputStream bais = new ByteArrayInputStream(dstBytes); ++ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ++ try (GZIPInputStream gzipInputStream = new GZIPInputStream(bais, BuffSize)) { ++ byte[] buffer = new byte[1024*1024]; ++ int len; ++ while ((len = gzipInputStream.read(buffer)) > 0) { ++ byteArrayOutputStream.write(buffer, 0, len); ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++} +\ No newline at end of file +-- +2.22.0 + diff --git a/Add-jbolt-feature.patch b/Add-jbolt-feature.patch new file mode 100644 index 0000000..dc0c9ef --- /dev/null +++ b/Add-jbolt-feature.patch @@ -0,0 +1,5221 @@ +From 286b012c1768abacb5f30ac6b435d74153fb5186 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:34:54 +0800 +Subject: Add jbolt feature + +--- + src/hotspot/os/linux/os_linux.cpp | 21 + + src/hotspot/os/linux/os_linux.hpp | 27 + + src/hotspot/share/ci/ciEnv.cpp | 42 +- + src/hotspot/share/code/codeBlob.hpp | 10 +- + src/hotspot/share/code/codeCache.cpp | 33 + + src/hotspot/share/code/codeCache.hpp | 16 +- + src/hotspot/share/code/nmethod.cpp | 19 + + src/hotspot/share/code/nmethod.hpp | 8 + + src/hotspot/share/compiler/compileBroker.cpp | 9 + + src/hotspot/share/compiler/compileBroker.hpp | 3 + + src/hotspot/share/compiler/compileTask.hpp | 14 +- + src/hotspot/share/jbolt/jBoltCallGraph.cpp | 474 ++++++ + src/hotspot/share/jbolt/jBoltCallGraph.hpp | 277 ++++ + .../share/jbolt/jBoltControlThread.cpp | 219 +++ + .../share/jbolt/jBoltControlThread.hpp | 69 + + src/hotspot/share/jbolt/jBoltDcmds.cpp | 221 +++ + src/hotspot/share/jbolt/jBoltDcmds.hpp | 129 ++ + src/hotspot/share/jbolt/jBoltManager.cpp | 1387 +++++++++++++++++ + src/hotspot/share/jbolt/jBoltManager.hpp | 323 ++++ + src/hotspot/share/jbolt/jBoltUtils.cpp | 38 + + src/hotspot/share/jbolt/jBoltUtils.hpp | 55 + + src/hotspot/share/jbolt/jBoltUtils.inline.hpp | 38 + + src/hotspot/share/jbolt/jbolt_globals.hpp | 62 + + src/hotspot/share/jfr/metadata/metadata.xml | 2 + + .../share/jfr/periodic/jfrPeriodic.cpp | 5 + + .../periodic/sampling/jfrThreadSampler.cpp | 11 +- + .../jfr/recorder/stacktrace/jfrStackTrace.cpp | 12 +- + .../jfr/recorder/stacktrace/jfrStackTrace.hpp | 29 + + .../stacktrace/jfrStackTraceRepository.cpp | 115 ++ + .../stacktrace/jfrStackTraceRepository.hpp | 23 + + src/hotspot/share/logging/logTag.hpp | 1 + + src/hotspot/share/opto/doCall.cpp | 4 +- + src/hotspot/share/opto/parse1.cpp | 2 +- + src/hotspot/share/runtime/flags/allFlags.hpp | 13 + + src/hotspot/share/runtime/java.cpp | 9 + + src/hotspot/share/runtime/thread.cpp | 19 + + src/hotspot/share/utilities/growableArray.hpp | 2 +- + src/hotspot/share/utilities/macros.hpp | 12 + + .../cli/common/CodeCacheCLITestCase.java | 12 +- + .../cli/common/CodeCacheOptions.java | 62 +- + .../codecache/jbolt/JBoltDumpModeTest.java | 187 +++ + .../codecache/jbolt/JBoltVMOptionsTest.java | 291 ++++ + .../jtreg/compiler/codecache/jbolt/o1.log | 2 + + .../jtreg/compiler/codecache/jbolt/o2.log | 2 + + .../jtreg/compiler/codecache/jbolt/o3.log | 4 + + .../jtreg/compiler/codecache/jbolt/o4.log | 12 + + .../runtime/cds/appcds/ClassLoaderTest.java | 2 +- + test/lib/jdk/test/whitebox/code/BlobType.java | 24 +- + 48 files changed, 4312 insertions(+), 39 deletions(-) + create mode 100644 src/hotspot/share/jbolt/jBoltCallGraph.cpp + create mode 100644 src/hotspot/share/jbolt/jBoltCallGraph.hpp + create mode 100644 src/hotspot/share/jbolt/jBoltControlThread.cpp + create mode 100644 src/hotspot/share/jbolt/jBoltControlThread.hpp + create mode 100644 src/hotspot/share/jbolt/jBoltDcmds.cpp + create mode 100644 src/hotspot/share/jbolt/jBoltDcmds.hpp + create mode 100644 src/hotspot/share/jbolt/jBoltManager.cpp + create mode 100644 src/hotspot/share/jbolt/jBoltManager.hpp + create mode 100644 src/hotspot/share/jbolt/jBoltUtils.cpp + create mode 100644 src/hotspot/share/jbolt/jBoltUtils.hpp + create mode 100644 src/hotspot/share/jbolt/jBoltUtils.inline.hpp + create mode 100644 src/hotspot/share/jbolt/jbolt_globals.hpp + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/JBoltDumpModeTest.java + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/JBoltVMOptionsTest.java + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/o1.log + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/o2.log + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/o3.log + create mode 100644 test/hotspot/jtreg/compiler/codecache/jbolt/o4.log + +diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp +index 96b92344f..a4688f0b8 100644 +--- a/src/hotspot/os/linux/os_linux.cpp ++++ b/src/hotspot/os/linux/os_linux.cpp +@@ -4611,6 +4611,11 @@ os::Linux::jboosterAggressiveCDS_do_t os::Linux::_jboosterAggressiveCDS_do; + #if INCLUDE_JBOOSTER + os::Linux::jboosterLazyAOT_do_t os::Linux::_jboosterLazyAOT_do; + #endif // INCLUDE_JBOOSTER ++#if INCLUDE_JBOLT ++os::Linux::jboltLog_precalc_t os::Linux::_jboltLog_precalc; ++os::Linux::jboltLog_do_t os::Linux::_jboltLog_do; ++os::Linux::jboltMerge_judge_t os::Linux::_jboltMerge_judge; ++#endif // INCLUDE_JBOLT + + void os::Linux::load_plugin_library() { + +@@ -4620,6 +4625,11 @@ void os::Linux::load_plugin_library() { + #if INCLUDE_JBOOSTER + _jboosterLazyAOT_do = CAST_TO_FN_PTR(jboosterLazyAOT_do_t, dlsym(RTLD_DEFAULT, "JBoosterLazyAOT_DO")); + #endif // INCLUDE_JBOOSTER ++#if INCLUDE_JBOLT ++ _jboltLog_precalc = CAST_TO_FN_PTR(jboltLog_precalc_t, dlsym(RTLD_DEFAULT, "JBoltLog_PreCalc")); ++ _jboltLog_do = CAST_TO_FN_PTR(jboltLog_do_t, dlsym(RTLD_DEFAULT, "JBoltLog_DO")); ++ _jboltMerge_judge = CAST_TO_FN_PTR(jboltMerge_judge_t, dlsym(RTLD_DEFAULT, "JBoltMerge_Judge")); ++#endif // INCLUDE_JBOLT + + _heap_dict_add = CAST_TO_FN_PTR(heap_dict_add_t, dlsym(RTLD_DEFAULT, "HeapDict_Add")); + _heap_dict_lookup = CAST_TO_FN_PTR(heap_dict_lookup_t, dlsym(RTLD_DEFAULT, "HeapDict_Lookup")); +@@ -4664,6 +4674,17 @@ void os::Linux::load_plugin_library() { + _jboosterLazyAOT_do = CAST_TO_FN_PTR(jboosterLazyAOT_do_t, dlsym(handle, "JBoosterLazyAOT_DO")); + } + #endif // INCLUDE_JBOOSTER ++#if INCLUDE_JBOLT ++ if (_jboltLog_precalc == NULL) { ++ _jboltLog_precalc = CAST_TO_FN_PTR(jboltLog_precalc_t, dlsym(handle, "JBoltLog_PreCalc")); ++ } ++ if (_jboltLog_do == NULL) { ++ _jboltLog_do = CAST_TO_FN_PTR(jboltLog_do_t, dlsym(handle, "JBoltLog_DO")); ++ } ++ if (_jboltMerge_judge == NULL) { ++ _jboltMerge_judge = CAST_TO_FN_PTR(jboltMerge_judge_t, dlsym(handle, "JBoltMerge_Judge")); ++ } ++#endif // INCLUDE_JBOLT + } + } + +diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp +index 2afecada2..da4824251 100644 +--- a/src/hotspot/os/linux/os_linux.hpp ++++ b/src/hotspot/os/linux/os_linux.hpp +@@ -235,6 +235,14 @@ class Linux { + typedef void (*jboosterLazyAOT_do_t)(int data_layout[], address methods, address tc_method_array, address nc_method_array, address klasses); + static jboosterLazyAOT_do_t _jboosterLazyAOT_do; + #endif // INCLUDE_JBOOSTER ++#if INCLUDE_JBOLT ++ typedef void (*jboltLog_precalc_t)(unsigned int topFrameIndex, unsigned int &max_frames); ++ typedef bool (*jboltLog_do_t)(uintptr_t related_data[], address stacktrace, unsigned int i, int comp_level, address new_func, address *tempfunc); ++ typedef int (*jboltMerge_judge_t)(uintptr_t data_layout[], int candidate, address clusters, address merged, address cluster); ++ static jboltLog_precalc_t _jboltLog_precalc; ++ static jboltLog_do_t _jboltLog_do; ++ static jboltMerge_judge_t _jboltMerge_judge; ++#endif + static sched_getcpu_func_t _sched_getcpu; + static numa_node_to_cpus_func_t _numa_node_to_cpus; + static numa_node_to_cpus_v2_func_t _numa_node_to_cpus_v2; +@@ -504,6 +512,25 @@ class Linux { + } + } + #endif // INCLUDE_JBOOSTER ++#if INCLUDE_JBOLT ++ static void jboltLog_precalc(unsigned int topFrameIndex, unsigned int &max_frames) { ++ if (_jboltLog_precalc != NULL) { ++ _jboltLog_precalc(topFrameIndex, max_frames); ++ } ++ } ++ static bool jboltLog_do(uintptr_t related_data[], address stacktrace, unsigned int i, int comp_level, address new_func, address *tempfunc) { ++ if (_jboltLog_do != NULL) { ++ return _jboltLog_do(related_data, stacktrace, i, comp_level, new_func, tempfunc); ++ } ++ return false; ++ } ++ static int jboltMerge_judge(uintptr_t data_layout[], int candidate, address clusters, address merged, address cluster) { ++ if (_jboltMerge_judge != NULL) { ++ return _jboltMerge_judge(data_layout, candidate, clusters, merged, cluster); ++ } ++ return -1; ++ } ++#endif // INCLUDE_JBOLT + }; + + #endif // OS_LINUX_OS_LINUX_HPP +diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp +index 597cc75fa..5fa006d96 100644 +--- a/src/hotspot/share/ci/ciEnv.cpp ++++ b/src/hotspot/share/ci/ciEnv.cpp +@@ -79,6 +79,9 @@ + #ifdef COMPILER2 + #include "opto/runtime.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif + + // ciEnv + // +@@ -1024,16 +1027,35 @@ void ciEnv::register_method(ciMethod* target, + assert(offsets->value(CodeOffsets::Deopt) != -1, "must have deopt entry"); + assert(offsets->value(CodeOffsets::Exceptions) != -1, "must have exception entry"); + +- nm = nmethod::new_nmethod(method, +- compile_id(), +- entry_bci, +- offsets, +- orig_pc_offset, +- debug_info(), dependencies(), code_buffer, +- frame_words, oop_map_set, +- handler_table, inc_table, +- compiler, task()->comp_level(), +- native_invokers); ++#if INCLUDE_JBOLT ++ if (UseJBolt && JBoltManager::reorder_phase_is_collecting_or_reordering()) { ++ int code_blob_type = JBoltManager::calc_code_blob_type(method(), task(), THREAD); ++ nm = nmethod::new_nmethod(method, ++ compile_id(), ++ entry_bci, ++ offsets, ++ orig_pc_offset, ++ debug_info(), dependencies(), code_buffer, ++ frame_words, oop_map_set, ++ handler_table, inc_table, ++ compiler, task()->comp_level(), ++ native_invokers, ++ NULL, 0, -1, NULL, NULL, ++ code_blob_type); ++ } else ++#endif // INCLUDE_JBOLT ++ { ++ nm = nmethod::new_nmethod(method, ++ compile_id(), ++ entry_bci, ++ offsets, ++ orig_pc_offset, ++ debug_info(), dependencies(), code_buffer, ++ frame_words, oop_map_set, ++ handler_table, inc_table, ++ compiler, task()->comp_level(), ++ native_invokers); ++ } + + // Free codeBlobs + code_buffer->free_blob(); +diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp +index 54258da76..52f8654e2 100644 +--- a/src/hotspot/share/code/codeBlob.hpp ++++ b/src/hotspot/share/code/codeBlob.hpp +@@ -44,10 +44,12 @@ struct CodeBlobType { + enum { + MethodNonProfiled = 0, // Execution level 1 and 4 (non-profiled) nmethods (including native nmethods) + MethodProfiled = 1, // Execution level 2 and 3 (profiled) nmethods +- NonNMethod = 2, // Non-nmethods like Buffers, Adapters and Runtime Stubs +- All = 3, // All types (No code cache segmentation) +- AOT = 4, // AOT methods +- NumTypes = 5 // Number of CodeBlobTypes ++ MethodJBoltHot = 2, // Hot methods (determined by JBolt) of level 1 and 4 nmethods ++ MethodJBoltTmp = 3, // Temporary storage of JBolt hot methods ++ NonNMethod = 4, // Non-nmethods like Buffers, Adapters and Runtime Stubs ++ All = 5, // All types (No code cache segmentation) ++ AOT = 6, // AOT methods ++ NumTypes = 7 // Number of CodeBlobTypes + }; + }; + +diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp +index a7f362589..2c74c648d 100644 +--- a/src/hotspot/share/code/codeCache.cpp ++++ b/src/hotspot/share/code/codeCache.cpp +@@ -75,6 +75,9 @@ + #if INCLUDE_AOT + #include "aot/aotLoader.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif // INCLUDE_JBOLT + + // Helper class for printing in CodeCache + class CodeBlob_sizes { +@@ -326,6 +329,16 @@ void CodeCache::initialize_heaps() { + non_nmethod_size = align_up(non_nmethod_size, alignment); + profiled_size = align_down(profiled_size, alignment); + ++#if INCLUDE_JBOLT ++ if (UseJBolt && !JBoltDumpMode) { ++ // We replace the original add-heap logic with the JBolt one. manual dump mode doesn't need that ++ JBoltManager::init_code_heaps(non_nmethod_size, profiled_size, non_profiled_size, cache_size, ps, alignment); ++ return; ++ } ++ // The following add-heap logic will not be executed if JBolt load mode is on. ++ // If the following logic is modified, remember to modify the JBolt logic accordingly. ++#endif // INCLUDE_JBOLT ++ + // Reserve one continuous chunk of memory for CodeHeaps and split it into + // parts for the individual heaps. The memory layout looks like this: + // ---------- high ----------- +@@ -378,6 +391,12 @@ ReservedCodeSpace CodeCache::reserve_heap_memory(size_t size, size_t rs_ps) { + + // Heaps available for allocation + bool CodeCache::heap_available(int code_blob_type) { ++ if (code_blob_type == CodeBlobType::MethodJBoltHot) { ++ return JBOLT_ONLY(UseJBolt && !JBoltDumpMode) NOT_JBOLT(false); ++ } else if (code_blob_type == CodeBlobType::MethodJBoltTmp) { ++ return JBOLT_ONLY(UseJBolt && !JBoltDumpMode) NOT_JBOLT(false); ++ } ++ + if (!SegmentedCodeCache) { + // No segmentation: use a single code heap + return (code_blob_type == CodeBlobType::All); +@@ -405,6 +424,12 @@ const char* CodeCache::get_code_heap_flag_name(int code_blob_type) { + case CodeBlobType::MethodProfiled: + return "ProfiledCodeHeapSize"; + break; ++ case CodeBlobType::MethodJBoltHot: ++ return "JBoltHotCodeHeapSize"; ++ break; ++ case CodeBlobType::MethodJBoltTmp: ++ return "JBoltTmpCodeHeapSize"; ++ break; + } + ShouldNotReachHere(); + return NULL; +@@ -555,6 +580,14 @@ CodeBlob* CodeCache::allocate(int size, int code_blob_type, bool handle_alloc_fa + type = CodeBlobType::MethodNonProfiled; + } + break; ++#if INCLUDE_JBOLT ++ case CodeBlobType::MethodJBoltHot: ++ type = CodeBlobType::MethodNonProfiled; ++ break; ++ case CodeBlobType::MethodJBoltTmp: ++ type = CodeBlobType::MethodNonProfiled; ++ break; ++#endif // INCLUDE_JBOLT + } + if (type != code_blob_type && type != orig_code_blob_type && heap_available(type)) { + if (PrintCodeCacheExtension) { +diff --git a/src/hotspot/share/code/codeCache.hpp b/src/hotspot/share/code/codeCache.hpp +index 8ff8dc765..af9dd5b89 100644 +--- a/src/hotspot/share/code/codeCache.hpp ++++ b/src/hotspot/share/code/codeCache.hpp +@@ -47,6 +47,10 @@ + // executed at level 2 or 3 + // - Non-Profiled nmethods: nmethods that are not profiled, i.e., those + // executed at level 1 or 4 and native methods ++// - JBolt nmethods: sorted non-profiled nmethods that are judged to be hot ++// by JBolt ++// - JBolt tmp nmethods: non-profiled nmethods that are judged to be hot by ++// JBolt but not sorted yet + // - All: Used for code of all types if code cache segmentation is disabled. + // + // In the rare case of the non-nmethod code heap getting full, non-nmethod code +@@ -84,6 +88,10 @@ class CodeCache : AllStatic { + friend class WhiteBox; + friend class CodeCacheLoader; + friend class ShenandoahParallelCodeHeapIterator; ++#if INCLUDE_JBOLT ++ friend class JBoltManager; ++#endif // INCLUDE_JBOLT ++ + private: + // CodeHeaps of the cache + static GrowableArray* _heaps; +@@ -230,13 +238,17 @@ class CodeCache : AllStatic { + } + + static bool code_blob_type_accepts_compiled(int type) { +- bool result = type == CodeBlobType::All || type <= CodeBlobType::MethodProfiled; ++ // Modified `type <= CodeBlobType::MethodProfiled` to `type < CodeBlobType::NonNMethod` ++ // after adding the JBolt heap. The two logics are still equivalent even without JBolt. ++ bool result = type == CodeBlobType::All || type < CodeBlobType::NonNMethod; + AOT_ONLY( result = result || type == CodeBlobType::AOT; ) + return result; + } + + static bool code_blob_type_accepts_nmethod(int type) { +- return type == CodeBlobType::All || type <= CodeBlobType::MethodProfiled; ++ // Modified `type <= CodeBlobType::MethodProfiled` to `type < CodeBlobType::NonNMethod` ++ // after adding the JBolt heap. The two logics are still equivalent even without JBolt. ++ return type == CodeBlobType::All || type < CodeBlobType::NonNMethod; + } + + static bool code_blob_type_accepts_allocable(int type) { +diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp +index cb5cf9574..952b0b8dd 100644 +--- a/src/hotspot/share/code/nmethod.cpp ++++ b/src/hotspot/share/code/nmethod.cpp +@@ -79,6 +79,9 @@ + #if INCLUDE_JVMCI + #include "jvmci/jvmciRuntime.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif + + #ifdef DTRACE_ENABLED + +@@ -554,6 +557,9 @@ nmethod* nmethod::new_nmethod(const methodHandle& method, + const char* nmethod_mirror_name, + FailedSpeculation** failed_speculations + #endif ++#if INCLUDE_JBOLT ++ , int code_blob_type // for jbolt ++#endif // INCLUDE_JBOLT + ) + { + assert(debug_info->oop_recorder() == code_buffer->oop_recorder(), "shared OR"); +@@ -577,7 +583,11 @@ nmethod* nmethod::new_nmethod(const methodHandle& method, + #endif + + align_up(debug_info->data_size() , oopSize); + ++#if INCLUDE_JBOLT ++ nm = new (nmethod_size, comp_level, code_blob_type) ++#else // INCLUDE_JBOLT + nm = new (nmethod_size, comp_level) ++#endif // INCLUDE_JBOLT + nmethod(method(), compiler->type(), nmethod_size, compile_id, entry_bci, offsets, + orig_pc_offset, debug_info, dependencies, code_buffer, frame_size, + oop_maps, +@@ -762,6 +772,15 @@ void* nmethod::operator new(size_t size, int nmethod_size, bool allow_NonNMethod + return CodeCache::allocate(nmethod_size, CodeBlobType::NonNMethod); + } + ++#if INCLUDE_JBOLT ++void* nmethod::operator new(size_t size, int nmethod_size, int comp_level, int code_blob_type) throw () { ++ if (code_blob_type < CodeBlobType::All) { ++ return CodeCache::allocate(nmethod_size, code_blob_type); ++ } ++ return CodeCache::allocate(nmethod_size, CodeCache::get_code_blob_type(comp_level)); ++} ++#endif // INCLUDE_JBOLT ++ + nmethod::nmethod( + Method* method, + CompilerType type, +diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp +index ea5e92845..16346b5a6 100644 +--- a/src/hotspot/share/code/nmethod.hpp ++++ b/src/hotspot/share/code/nmethod.hpp +@@ -329,6 +329,11 @@ class nmethod : public CompiledMethod { + // findable by nmethod iterators! In particular, they must not contain oops! + void* operator new(size_t size, int nmethod_size, bool allow_NonNMethod_space) throw(); + ++#if INCLUDE_JBOLT ++ // For JBolt. So the code can be allocated in code segments defined by JBolt. ++ void* operator new(size_t size, int nmethod_size, int comp_level, int code_blob_type) throw (); ++#endif // INCLUDE_JBOLT ++ + const char* reloc_string_for(u_char* begin, u_char* end); + + bool try_transition(int new_state); +@@ -375,6 +380,9 @@ class nmethod : public CompiledMethod { + const char* nmethod_mirror_name = NULL, + FailedSpeculation** failed_speculations = NULL + #endif ++#if INCLUDE_JBOLT ++ , int code_blob_type = CodeBlobType::All // for jbolt ++#endif // INCLUDE_JBOLT + ); + + // Only used for unit tests. +diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp +index f54308944..da91c50e3 100644 +--- a/src/hotspot/share/compiler/compileBroker.cpp ++++ b/src/hotspot/share/compiler/compileBroker.cpp +@@ -82,6 +82,9 @@ + #ifdef COMPILER2 + #include "opto/c2compiler.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif // INCLUDE_JBOLT + + #ifdef DTRACE_ENABLED + +@@ -2009,6 +2012,12 @@ void CompileBroker::compiler_thread_loop() { + task->set_failure_reason("breakpoints are present"); + } + ++#if INCLUDE_JBOLT ++ if (UseJBolt && JBoltLoadMode) { ++ JBoltManager::check_start_reordering(thread); ++ } ++#endif // INCLUDE_JBOLT ++ + if (UseDynamicNumberOfCompilerThreads) { + possibly_add_compiler_threads(thread); + assert(!thread->has_pending_exception(), "should have been handled"); +diff --git a/src/hotspot/share/compiler/compileBroker.hpp b/src/hotspot/share/compiler/compileBroker.hpp +index 0b721f831..8c94870f3 100644 +--- a/src/hotspot/share/compiler/compileBroker.hpp ++++ b/src/hotspot/share/compiler/compileBroker.hpp +@@ -141,6 +141,9 @@ public: + class CompileBroker: AllStatic { + friend class Threads; + friend class CompileTaskWrapper; ++#if INCLUDE_JBOLT ++ friend class JBoltManager; ++#endif // INCLUDE_JBOLT + + public: + enum { +diff --git a/src/hotspot/share/compiler/compileTask.hpp b/src/hotspot/share/compiler/compileTask.hpp +index 03beb350b..53dccf232 100644 +--- a/src/hotspot/share/compiler/compileTask.hpp ++++ b/src/hotspot/share/compiler/compileTask.hpp +@@ -54,6 +54,9 @@ class CompileTask : public CHeapObj { + Reason_Whitebox, // Whitebox API + Reason_MustBeCompiled, // Used for -Xcomp or AlwaysCompileLoopMethods (see CompilationPolicy::must_be_compiled()) + Reason_Bootstrap, // JVMCI bootstrap ++#if INCLUDE_JBOLT ++ Reason_Reorder, // JBolt reorder ++#endif + Reason_Count + }; + +@@ -66,7 +69,10 @@ class CompileTask : public CHeapObj { + "replay", + "whitebox", + "must_be_compiled", +- "bootstrap" ++ "bootstrap", ++#if INCLUDE_JBOLT ++ "reorder" ++#endif + }; + return reason_names[compile_reason]; + } +@@ -222,6 +228,12 @@ public: + print_inlining_inner(tty, method, inline_level, bci, msg); + } + static void print_inlining_ul(ciMethod* method, int inline_level, int bci, const char* msg = NULL); ++ ++#if INCLUDE_JBOLT ++ CompileReason compile_reason() { return _compile_reason; } ++ int hot_count() { return _hot_count; } ++ const char* failure_reason() { return _failure_reason; } ++#endif // INCLUDE_JBOLT + }; + + #endif // SHARE_COMPILER_COMPILETASK_HPP +diff --git a/src/hotspot/share/jbolt/jBoltCallGraph.cpp b/src/hotspot/share/jbolt/jBoltCallGraph.cpp +new file mode 100644 +index 000000000..47e11a5e7 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltCallGraph.cpp +@@ -0,0 +1,474 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 "precompiled.hpp" ++#include "jbolt/jBoltCallGraph.hpp" ++#include "jfr/utilities/jfrAllocation.hpp" ++#include "jfr/support/jfrMethodLookup.hpp" ++#include "utilities/defaultStream.hpp" ++#include "oops/method.inline.hpp" ++ ++#define PAGE_SIZE os::vm_page_size() ++ ++static GrowableArray* _clusters = NULL; ++static GrowableArray* _calls = NULL; ++static GrowableArray* _funcs = NULL; ++ ++// (JBolt hfsort optional)sort final clusters by density ++static const bool _jbolt_density_sort = false; ++// (JBolt hfsort optional)freeze merging while exceeding pagesize ++static const bool _jbolt_merge_frozen = false; ++ ++void JBoltCallGraph::initialize() { ++ ::_clusters = JBoltCallGraph::callgraph_instance().callgraph_clusters(); ++ ::_calls = JBoltCallGraph::callgraph_instance().callgraph_calls(); ++ ::_funcs = JBoltCallGraph::callgraph_instance().callgraph_funcs(); ++} ++ ++void JBoltCallGraph::deinitialize() { ++ ::_clusters = NULL; ++ ::_calls = NULL; ++ ::_funcs = NULL; ++} ++ ++int JBoltCallGraph::clear_instance() { ++ delete _clusters; ++ delete _calls; ++ delete _funcs; ++ ++ // Reinit default cluster start id ++ _init_cluster_id = 0; ++ ++ // Re-allocate ++ _clusters = create_growable_array(); ++ _calls = create_growable_array(); ++ _funcs = create_growable_array(); ++ ++ // Re-initialize ++ initialize(); ++ ++ return 0; ++} ++ ++static GrowableArray* clusters_copy() { ++ GrowableArray* copy = create_growable_array(_clusters->length()); ++ copy->appendAll(_clusters); ++ return copy; ++} ++ ++static GrowableArray* funcs_copy() { ++ GrowableArray* copy = create_growable_array(_funcs->length()); ++ copy->appendAll(_funcs); ++ return copy; ++} ++ ++static int find_func_index(const JBoltFunc* func) { ++ for (int i = 0; i < _funcs->length(); ++i) { ++ JBoltFunc& existing = _funcs->at(i); ++ if (existing == (*func)) { ++ return i; ++ } ++ } ++ return -1; ++} ++ ++// Searching for a cluster with corresponding func or creating a new one if doesn't exist ++static JBoltCluster* find_cluster(JBoltFunc* func) { ++ for (int i = 0; i < _clusters->length(); ++i) { ++ JBoltCluster& cluster = _clusters->at(i); ++ int index = cluster.func_indexes()->at(0); ++ if (_funcs->at(index) == (*func)) { ++ return &cluster; ++ } ++ } ++ _funcs->append(*func); ++ _clusters->append(JBoltCluster(*func)); ++ JBoltCluster& cluster = _clusters->at(_clusters->length() - 1); ++ _funcs->at(_funcs->length() - 1).set_cluster_id(cluster.id()); ++ return &cluster; ++} ++ ++// Creating a new call in graph or updating the weight if exists ++static void add_call_to_calls(GrowableArray* calls, const JBoltCall* call) { ++ for (int i = 0; i < calls->length(); ++i) { ++ JBoltCall& existing_call = calls->at(i); ++ if (existing_call == *call) { ++ if (existing_call.stacktrace_id() == call->stacktrace_id()) { ++ assert(call->call_count() > existing_call.call_count(), "invariant"); ++ existing_call.callee().add_heat(call->call_count() - existing_call.call_count()); ++ existing_call.set_call_count(call->call_count()); ++ } ++ else { ++ existing_call.callee().add_heat(call->call_count()); ++ existing_call.set_call_count(existing_call.call_count() + call->call_count()); ++ } ++ return; ++ } ++ } ++ ++ calls->append(*call); ++ call->callee().add_heat(call->call_count()); ++ call->callee().append_call_index(calls->length() - 1); ++} ++ ++// Getting final funcs order from an array of processed clusters ++static GrowableArray* clusters_to_funcs_order(GrowableArray* clusters) { ++ log_debug(jbolt)( "sorted clusters:\n"); ++ for (int i = 0; i < clusters->length(); ++i) { ++ log_debug(jbolt)( "cluster id: %d heats: %ld size: %dB density: %f\n", clusters->at(i).id(), clusters->at(i).heats(), clusters->at(i).size(), clusters->at(i).density()); ++ for (int j = 0; j < clusters->at(i).get_funcs_count(); ++j) { ++ JBoltFunc& func = _funcs->at(clusters->at(i).func_indexes()->at(j)); ++ const Method* const method = JfrMethodLookup::lookup(func.klass(), func.method_id()); ++ if (method != NULL) { ++ log_debug(jbolt)( "%d: method signature:%s heat: %ld size: %dB\n", ++ j, method->external_name(), func.heat(), func.size()); ++ } ++ } ++ } ++ ++ GrowableArray* order = create_growable_array(_funcs->length()); ++ // used to seperator distinct cluster, klass = NULL ++ JBoltFunc seperator_func; ++ order->append(seperator_func); ++ for (int i = 0; i < clusters->length(); ++i) { ++ JBoltCluster& cluster = clusters->at(i); ++ GrowableArray* func_indexes = cluster.func_indexes(); ++ ++ for (int j = 0; j < func_indexes->length(); ++j) { ++ int index = func_indexes->at(j); ++ order->append(_funcs->at(index)); ++ } ++ ++ order->append(seperator_func); ++ } ++ return order; ++} ++ ++// Comparing function needed to sort an array of funcs by their weights (in decreasing order) ++static int func_comparator(JBoltFunc* func1, JBoltFunc* func2) { ++ return func1->heat() < func2->heat(); ++} ++ ++// Comparing cluster needed to sort an array of clusters by their densities (in decreasing order) ++static int cluster_comparator(JBoltCluster* cluster1, JBoltCluster* cluster2) { ++ return _jbolt_density_sort ? (cluster1->density() < cluster2->density()) : (cluster1->heats() < cluster2 -> heats()); ++} ++ ++// Comparing call indexes needed to sort an array of call indexes by their call counts (in decreasing order) ++static int func_call_indexes_comparator(int* index1, int* index2) { ++ return _calls->at(*index1).call_count() < _calls->at(*index2).call_count(); ++} ++ ++JBoltCallGraph& JBoltCallGraph::callgraph_instance() { ++ static JBoltCallGraph _call_graph; ++ return _call_graph; ++} ++ ++void JBoltCallGraph::add_func(JBoltFunc* func) { ++ if (!(UseJBolt && JBoltManager::reorder_phase_is_profiling())) return; ++ JBoltCluster* cluster = find_cluster(func); ++ assert(cluster != NULL, "invariant"); ++} ++ ++void JBoltCallGraph::add_call(JBoltCall* call) { ++ if (!(UseJBolt && JBoltManager::reorder_phase_is_profiling())) return; ++ add_call_to_calls(_calls, call); ++} ++ ++uintptr_t data_layout_jbolt[] = { ++ (uintptr_t)in_bytes(JBoltCluster::id_offset()), ++ (uintptr_t)in_bytes(JBoltCluster::heats_offset()), ++ (uintptr_t)in_bytes(JBoltCluster::frozen_offset()), ++ (uintptr_t)in_bytes(JBoltCluster::size_offset()), ++ (uintptr_t)in_bytes(JBoltCluster::density_offset()), ++ (uintptr_t)in_bytes(JBoltCluster::func_indexes_offset()), ++ ++ (uintptr_t)in_bytes(GrowableArrayView
::data_offset()), ++ ++ (uintptr_t)JBoltCluster::find_cluster_by_id, ++ (uintptr_t)_jbolt_merge_frozen ++}; ++ ++static void deal_with_each_func(GrowableArray* clusters, GrowableArray* funcs, GrowableArray* merged) { ++ for (int i = 0; i < funcs->length(); ++i) { ++ JBoltFunc& func = funcs->at(i); ++ ++ JBoltCluster* cluster = JBoltCluster::find_cluster_by_id(clusters, func.cluster_id()); ++ ++ // for cluster size larger than page size, should be frozen and don't merge with any cluster ++ if (_jbolt_merge_frozen && cluster->frozen()) continue; ++ ++ // find best predecessor ++ func.call_indexes()->sort(&func_call_indexes_comparator); ++ ++ int bestPred = -1; ++ ++ for (int j = 0; j < func.call_indexes()->length(); ++j) { ++ const JBoltCall& call = _calls->at(func.call_indexes()->at(j)); ++ ++ bestPred = os::Linux::jboltMerge_judge(data_layout_jbolt, call.caller().cluster_id(), (address)clusters, (address)merged, (address)cluster); ++ ++ if (bestPred == -1) continue; ++ ++ break; ++ } ++ ++ // not merge -- no suitable caller nodes ++ if (bestPred == -1) { ++ continue; ++ } ++ ++ JBoltCluster* predCluster = JBoltCluster::find_cluster_by_id(clusters, bestPred); ++ ++ // merge callee cluster to caller cluster ++ for (int j = 0; j < cluster->func_indexes()->length(); ++j) { ++ int index = cluster->func_indexes()->at(j); ++ predCluster->append_func_index(index); ++ } ++ predCluster->add_heat(cluster->heats()); ++ predCluster->add_size(cluster->size()); ++ predCluster->update_density(); ++ merged->at(cluster->id()) = bestPred; ++ cluster->clear(); ++ } ++} ++ ++// Every node is a cluster with funcs ++// Initially each cluster has only one func inside ++GrowableArray* JBoltCallGraph::hfsort() { ++ if (!(UseJBolt && (JBoltDumpMode || JBoltManager::auto_mode()))) return NULL; ++ log_debug(jbolt)( "hfsort begin...\n"); ++ // Copies are needed for saving initial graph in memory ++ GrowableArray* clusters = clusters_copy(); ++ GrowableArray* funcs = funcs_copy(); ++ ++ // store a map for finding head of merge chain ++ GrowableArray* merged = create_growable_array(clusters->length()); ++ for (int i = 0; i < clusters->length(); ++i) { ++ merged->append(-1); ++ } ++ ++ // sorted by func(initially a node) weight(now just as 'heat') ++ funcs->sort(&func_comparator); ++ ++ // Process each function, and consider merging its cluster with the ++ // one containing its most likely predecessor. ++ deal_with_each_func(clusters, funcs, merged); ++ ++ // the set of clusters that are left ++ GrowableArray* sortedClusters = create_growable_array(); ++ for (int i = 0; i < clusters->length(); ++i) { ++ if (clusters->at(i).id() != -1) { ++ sortedClusters->append(clusters->at(i)); ++ } ++ } ++ ++ sortedClusters->sort(&cluster_comparator); ++ ++ GrowableArray* order = clusters_to_funcs_order(sortedClusters); ++ ++ delete clusters; ++ delete funcs; ++ delete merged; ++ delete sortedClusters; ++ log_debug(jbolt)( "hfsort over...\n"); ++ ++ return order; ++} ++ ++JBoltFunc::JBoltFunc() : ++ _klass(NULL), ++ _method_id(0), ++ _heat(0), ++ _size(0), ++ _cluster_id(-1), ++ _method_key(), ++ _call_indexes(create_growable_array()) {} ++ ++JBoltFunc::JBoltFunc(const JBoltFunc& func) : ++ _klass(func._klass), ++ _method_id(func._method_id), ++ _heat(func._heat), ++ _size(func._size), ++ _cluster_id(func._cluster_id), ++ _method_key(func._method_key), ++ _call_indexes(create_growable_array(func.get_calls_count())) { ++ GrowableArray* array = func.call_indexes(); ++ _call_indexes->appendAll(array); ++ } ++ ++JBoltFunc::JBoltFunc(const InstanceKlass* klass, traceid method_id, int size, JBoltMethodKey method_key) : ++ _klass(klass), ++ _method_id(method_id), ++ _heat(0), ++ _size(size), ++ _cluster_id(-1), ++ _method_key(method_key), ++ _call_indexes(create_growable_array()) { ++ // not new_symbol, need to inc reference cnt ++ _method_key.klass()->increment_refcount(); ++ _method_key.name()->increment_refcount(); ++ _method_key.sig()->increment_refcount(); ++ } ++ ++void JBoltFunc::add_heat(int64_t heat) { ++ _heat += heat; ++ assert(_cluster_id != -1, "invariant"); ++ _clusters->at(_cluster_id).add_heat(heat); ++ _clusters->at(_cluster_id).update_density(); ++} ++ ++void JBoltFunc::set_heat(int64_t heat) { ++ int64_t diff = heat - _heat; ++ _heat = heat; ++ assert(_cluster_id != -1, "invariant"); ++ _clusters->at(_cluster_id).add_heat(diff); ++ _clusters->at(_cluster_id).update_density(); ++} ++ ++void JBoltFunc::set_cluster_id(int cluster_id) { _cluster_id = cluster_id; } ++ ++void JBoltFunc::append_call_index(int index) { _call_indexes->append(index); } ++ ++JBoltFunc* JBoltFunc::constructor(const InstanceKlass* klass, traceid method_id, int size, JBoltMethodKey method_key) { ++ JBoltFunc *ret = new JBoltFunc(klass, method_id, size, method_key); ++ return ret; ++} ++ ++JBoltFunc* JBoltFunc::copy_constructor(const JBoltFunc* func) { ++ JBoltFunc *ret = new JBoltFunc(*func); ++ return ret; ++} ++ ++JBoltCluster::JBoltCluster() : ++ _id(-1), ++ _heats(0), ++ _frozen(false), ++ _size(0), ++ _density(0.0), ++ _func_indexes(create_growable_array()) {} ++ ++JBoltCluster::JBoltCluster(const JBoltFunc& func) : ++ _id(_init_cluster_id++), ++ _heats(func.heat()), ++ _frozen(false), ++ _size(func.size()), ++ _density(0.0), ++ _func_indexes(create_growable_array()) { ++ if (_size >= PAGE_SIZE) ++ freeze(); ++ ++ update_density(); ++ ++ int func_idx = find_func_index(&func); ++ assert(func_idx != -1, "invariant"); ++ _func_indexes->append(func_idx); ++ } ++ ++JBoltCluster::JBoltCluster(const JBoltCluster& cluster) : ++ _id(cluster.id()), ++ _heats(cluster.heats()), ++ _frozen(cluster.frozen()), ++ _size(cluster.size()), ++ _density(cluster.density()), ++ _func_indexes(create_growable_array(cluster.get_funcs_count())) { ++ GrowableArray* array = cluster.func_indexes(); ++ _func_indexes->appendAll(array); ++ } ++ ++void JBoltCluster::add_heat(int64_t heat) { _heats += heat; } ++ ++void JBoltCluster::freeze() { _frozen = true; } ++ ++void JBoltCluster::add_size(int size) { _size += size; } ++ ++void JBoltCluster::update_density() { _density = (double)_heats / (double)_size; } ++ ++void JBoltCluster::append_func_index(int index) { _func_indexes->append(index); } ++ ++void JBoltCluster::clear() { ++ _id = -1; ++ _heats = 0; ++ _frozen = false; ++ _size = 0; ++ _density = 0.0; ++ _func_indexes->clear(); ++} ++ ++// Searching for a cluster by its id ++JBoltCluster* JBoltCluster::find_cluster_by_id(GrowableArray* clusters, u4 id) { ++ if (id >= (u4)clusters->length()) return NULL; ++ ++ return &(clusters->at(id)); ++} ++ ++JBoltCluster* JBoltCluster::constructor(const JBoltFunc* func) { ++ JBoltCluster *ret = new JBoltCluster(*func); ++ return ret; ++} ++ ++JBoltCluster* JBoltCluster::copy_constructor(const JBoltCluster* cluster) { ++ JBoltCluster *ret = new JBoltCluster(*cluster); ++ return ret; ++} ++ ++JBoltCall::JBoltCall() : ++ _caller_index(-1), ++ _callee_index(-1), ++ _call_count(0), ++ _stacktrace_id(0) {} ++ ++JBoltCall::JBoltCall(const JBoltCall& call) : ++ _caller_index(call._caller_index), ++ _callee_index(call._callee_index), ++ _call_count(call._call_count), ++ _stacktrace_id(call._stacktrace_id) {} ++ ++JBoltCall::JBoltCall(const JBoltFunc& caller_func, const JBoltFunc& callee_func, u4 call_count, traceid stacktrace_id) : ++ _call_count(call_count), ++ _stacktrace_id(stacktrace_id) { ++ _caller_index = find_func_index(&caller_func); ++ _callee_index = find_func_index(&callee_func); ++ assert(_caller_index != -1, "invariant"); ++ assert(_callee_index != -1, "invariant"); ++ } ++ ++JBoltFunc& JBoltCall::caller() const { return _funcs->at(_caller_index); } ++ ++JBoltFunc& JBoltCall::callee() const { return _funcs->at(_callee_index); } ++ ++void JBoltCall::set_caller_index(int index) { _caller_index = index; } ++ ++void JBoltCall::set_callee_index(int index) { _callee_index = index; } ++ ++void JBoltCall::set_call_count(u4 call_count) { _call_count = call_count; } ++ ++JBoltCall* JBoltCall::constructor(const JBoltFunc* caller_func, const JBoltFunc* callee_func, u4 call_count, traceid stacktrace_id) { ++ JBoltCall *ret = new JBoltCall(*caller_func, *callee_func, call_count, stacktrace_id); ++ return ret; ++} ++ ++JBoltCall* JBoltCall::copy_constructor(const JBoltCall* call) { ++ JBoltCall *ret = new JBoltCall(*call); ++ return ret; ++} +\ No newline at end of file +diff --git a/src/hotspot/share/jbolt/jBoltCallGraph.hpp b/src/hotspot/share/jbolt/jBoltCallGraph.hpp +new file mode 100644 +index 000000000..95a1ffeba +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltCallGraph.hpp +@@ -0,0 +1,277 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTCALLGRAPH_HPP ++#define SHARE_JBOLT_JBOLTCALLGRAPH_HPP ++ ++#include "jbolt/jbolt_globals.hpp" ++#include "jbolt/jBoltManager.hpp" ++#include "jfr/utilities/jfrTypes.hpp" ++#include "utilities/growableArray.hpp" ++ ++class JBoltFunc; ++class JBoltCall; ++class JBoltCluster; ++ ++template ++static GrowableArray* create_growable_array(int size = 1) { ++ GrowableArray* array = new (ResourceObj::C_HEAP, mtTracing) GrowableArray(size, mtTracing); ++ assert(array != NULL, "invariant"); ++ return array; ++} ++ ++// initial cluster id ++static u4 _init_cluster_id = 0; ++ ++class JBoltCallGraph : public CHeapObj { ++ private: ++ GrowableArray* _clusters = NULL; ++ GrowableArray* _calls = NULL; ++ GrowableArray* _funcs = NULL; ++ ++ JBoltCallGraph() { ++ _clusters = create_growable_array(); ++ _calls = create_growable_array(); ++ _funcs = create_growable_array(); ++ } ++ ++ JBoltCallGraph(const JBoltCallGraph &) = delete; ++ JBoltCallGraph(const JBoltCallGraph &&) = delete; ++ ++ // for constructing CG ++ void add_func(JBoltFunc* func); // Node ++ void add_call(JBoltCall* call); // Edge ++ ++ public: ++ static JBoltCallGraph& callgraph_instance(); ++ // these two funcs initialize and deinitialize homonymous static array pointers in global ++ static void initialize(); ++ static void deinitialize(); ++ ++ GrowableArray* callgraph_clusters() { return _clusters; } ++ GrowableArray* callgraph_calls() { return _calls; } ++ GrowableArray* callgraph_funcs() { return _funcs; } ++ ++ static void static_add_func(JBoltFunc* func) { callgraph_instance().add_func(func); } ++ static void static_add_call(JBoltCall* call) { callgraph_instance().add_call(call); } ++ ++ // for dealing with CG ++ GrowableArray* hfsort(); ++ ++ int clear_instance(); ++ ++ virtual ~JBoltCallGraph() { ++ delete _clusters; ++ delete _calls; ++ delete _funcs; ++ ++ _clusters = NULL; ++ _calls = NULL; ++ _funcs = NULL; ++ } ++}; ++ ++class JBoltFunc : public CHeapObj { ++ private: ++ const InstanceKlass* _klass; ++ traceid _method_id; ++ int64_t _heat; ++ int _size; ++ int _cluster_id; ++ JBoltMethodKey _method_key; ++ GrowableArray* _call_indexes; ++ ++ public: ++ JBoltFunc(); ++ JBoltFunc(const JBoltFunc& func); ++ JBoltFunc(const InstanceKlass* klass, traceid method_id, int size, JBoltMethodKey method_key); ++ ++ virtual ~JBoltFunc() { ++ delete _call_indexes; ++ } ++ ++ bool operator==(const JBoltFunc& func) const { return (_klass == func._klass && _method_id == func._method_id) || (_method_key.equals(func._method_key)); } ++ bool operator!=(const JBoltFunc& func) const { return (_klass != func._klass || _method_id != func._method_id) && !(_method_key.equals(func._method_key)); } ++ ++ JBoltFunc& operator=(const JBoltFunc& func) { ++ _klass = func._klass; ++ _method_id = func._method_id; ++ _heat = func._heat; ++ _size = func._size; ++ _cluster_id = func._cluster_id; ++ _method_key = func._method_key; ++ if (_call_indexes != nullptr) { ++ delete _call_indexes; ++ } ++ _call_indexes = create_growable_array(func.get_calls_count()); ++ _call_indexes->appendAll(func.call_indexes()); ++ ++ return *this; ++ } ++ ++ const InstanceKlass* klass() const { return _klass; } ++ const traceid method_id() const { return _method_id; } ++ const int64_t heat() const { return _heat; } ++ const int size() const { return _size; } ++ const int cluster_id() const { return _cluster_id; } ++ JBoltMethodKey method_key() const { return _method_key; } ++ GrowableArray* call_indexes() const { return _call_indexes; } ++ int get_calls_count() const { return _call_indexes->length(); } ++ ++ void add_heat(int64_t heat); ++ void set_heat(int64_t heat); ++ void set_cluster_id(int cluster_id); ++ void append_call_index(int index); ++ ++ static ByteSize klass_offset() { return byte_offset_of(JBoltFunc, _klass); } ++ static ByteSize method_id_offset() { return byte_offset_of(JBoltFunc, _method_id); } ++ static ByteSize heat_offset() { return byte_offset_of(JBoltFunc, _heat); } ++ static ByteSize size_offset() { return byte_offset_of(JBoltFunc, _size); } ++ static ByteSize cluster_id_offset() { return byte_offset_of(JBoltFunc, _cluster_id); } ++ static ByteSize call_indexes_offset() { return byte_offset_of(JBoltFunc, _call_indexes); } ++ ++ static JBoltFunc* constructor(const InstanceKlass* klass, traceid method_id, int size, JBoltMethodKey method_key); ++ static JBoltFunc* copy_constructor(const JBoltFunc* func); ++}; ++ ++class JBoltCluster : public CHeapObj { ++ private: ++ int _id; ++ int64_t _heats; ++ bool _frozen; ++ int _size; ++ double _density; ++ GrowableArray* _func_indexes; ++ ++ public: ++ JBoltCluster(); ++ JBoltCluster(const JBoltFunc& func); ++ JBoltCluster(const JBoltCluster& cluster); ++ ++ bool operator==(const JBoltCluster& cluster) const { ++ if (_id != cluster.id()) return false; ++ ++ int count = get_funcs_count(); ++ if (count != cluster.get_funcs_count()) ++ return false; ++ ++ for (int i = 0; i < count; ++i) { ++ if (_func_indexes->at(i) != cluster._func_indexes->at(i)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ JBoltCluster& operator=(const JBoltCluster& cluster) { ++ _id = cluster.id(); ++ _heats = cluster.heats(); ++ _frozen = cluster.frozen(); ++ _size = cluster.size(); ++ _density = cluster.density(); ++ if (_func_indexes != nullptr) { ++ delete _func_indexes; ++ } ++ _func_indexes = create_growable_array(cluster.get_funcs_count()); ++ _func_indexes->appendAll(cluster.func_indexes()); ++ return *this; ++ } ++ ++ virtual ~JBoltCluster() { delete _func_indexes; } ++ ++ int id() const { return _id; } ++ int64_t heats() const { return _heats; } ++ bool frozen() const { return _frozen; } ++ int size() const { return _size; } ++ double density() const { return _density; } ++ GrowableArray* func_indexes() const { return _func_indexes; } ++ int get_funcs_count() const { return _func_indexes->length(); } ++ ++ void add_heat(int64_t heat); ++ void freeze(); ++ void add_size(int size); ++ void update_density(); ++ void append_func_index(int index); ++ void clear(); ++ ++ static JBoltCluster* find_cluster_by_id(GrowableArray* clusters, u4 id); ++ ++ static ByteSize id_offset() { return byte_offset_of(JBoltCluster, _id); } ++ static ByteSize heats_offset() { return byte_offset_of(JBoltCluster, _heats); } ++ static ByteSize frozen_offset() { return byte_offset_of(JBoltCluster, _frozen); } ++ static ByteSize size_offset() { return byte_offset_of(JBoltCluster, _size); } ++ static ByteSize density_offset() { return byte_offset_of(JBoltCluster, _density); } ++ static ByteSize func_indexes_offset() { return byte_offset_of(JBoltCluster, _func_indexes); } ++ ++ static JBoltCluster* constructor(const JBoltFunc* func); ++ static JBoltCluster* copy_constructor(const JBoltCluster* cluster); ++}; ++ ++class JBoltCall : public CHeapObj { ++ private: ++ int _caller_index; ++ int _callee_index; ++ u4 _call_count; ++ traceid _stacktrace_id; ++ ++ public: ++ JBoltCall(); ++ JBoltCall(const JBoltCall& call); ++ JBoltCall(const JBoltFunc& caller_func, const JBoltFunc& callee_func, u4 call_count, traceid stacktrace_id); ++ ++ bool operator==(const JBoltCall& call) const { ++ return _caller_index == call._caller_index && _callee_index == call._callee_index; ++ } ++ ++ JBoltCall& operator=(const JBoltCall& call) { ++ _caller_index = call._caller_index; ++ _callee_index = call._callee_index; ++ _call_count = call._call_count; ++ _stacktrace_id = call._stacktrace_id; ++ return *this; ++ } ++ ++ virtual ~JBoltCall() {} ++ ++ int caller_index() const { return _caller_index; } ++ int callee_index() const { return _callee_index; } ++ u4 call_count() const { return _call_count; } ++ traceid stacktrace_id() const { return _stacktrace_id; } ++ ++ JBoltFunc& caller() const; ++ JBoltFunc& callee() const; ++ void set_caller_index(int index); ++ void set_callee_index(int index); ++ void set_call_count(u4 count); ++ ++ static ByteSize caller_offset() { return byte_offset_of(JBoltCall, _caller_index); } ++ static ByteSize callee_offset() { return byte_offset_of(JBoltCall, _caller_index); } ++ static ByteSize call_count_offset() { return byte_offset_of(JBoltCall, _call_count); } ++ static ByteSize stacktrace_id_offset() { return byte_offset_of(JBoltCall, _stacktrace_id); } ++ ++ static JBoltCall* constructor(const JBoltFunc* caller_func, const JBoltFunc* callee_func, u4 call_count, traceid stacktrace_id); ++ static JBoltCall* copy_constructor(const JBoltCall* call); ++}; ++ ++#endif // SHARE_JBOLT_JBOLTCALLGRAPH_HPP +diff --git a/src/hotspot/share/jbolt/jBoltControlThread.cpp b/src/hotspot/share/jbolt/jBoltControlThread.cpp +new file mode 100644 +index 000000000..e9402b86b +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltControlThread.cpp +@@ -0,0 +1,219 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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/javaClasses.inline.hpp" ++#include "classfile/vmClasses.hpp" ++#include "classfile/vmSymbols.hpp" ++#include "jbolt/jBoltControlThread.hpp" ++#include "jbolt/jBoltManager.hpp" ++#include "logging/log.hpp" ++#include "logging/logStream.hpp" ++#include "runtime/atomic.hpp" ++#include "runtime/handles.inline.hpp" ++#include "runtime/interfaceSupport.inline.hpp" ++#include "runtime/handles.inline.hpp" ++#include "runtime/javaCalls.hpp" ++#include "runtime/jniHandles.inline.hpp" ++#include "runtime/thread.inline.hpp" ++ ++JavaThread* volatile JBoltControlThread::_the_java_thread = nullptr; ++Monitor* JBoltControlThread::_control_wait_monitor = nullptr; ++Monitor* JBoltControlThread::_sample_wait_monitor = nullptr; ++jobject JBoltControlThread::_thread_obj = nullptr; ++int volatile JBoltControlThread::_signal = JBoltControlThread::SIG_NULL; ++bool volatile JBoltControlThread::_abort = false; ++intx volatile JBoltControlThread::_interval = 0; ++ ++static bool not_first = false; ++ ++void JBoltControlThread::init(TRAPS) { ++ Handle string = java_lang_String::create_from_str("JBolt Control", CATCH); ++ Handle thread_group(THREAD, Universe::system_thread_group()); ++ Handle thread_oop = JavaCalls::construct_new_instance( ++ vmClasses::Thread_klass(), ++ vmSymbols::threadgroup_string_void_signature(), ++ thread_group, ++ string, ++ CATCH); ++ _thread_obj = JNIHandles::make_global(thread_oop); ++ _control_wait_monitor = new Monitor(Mutex::nonleaf, "JBoltControlMonitor"); ++ _sample_wait_monitor = new Monitor(Mutex::nonleaf, "JBoltSampleMonitor"); ++ Atomic::release_store(&_interval, JBoltSampleInterval); ++} ++ ++void JBoltControlThread::start_thread(TRAPS) { ++ guarantee(Atomic::load_acquire(&_the_java_thread) == nullptr, "sanity"); ++ JavaThread* new_thread = new JavaThread(&thread_entry); ++ if (new_thread->osthread() == nullptr) { ++ fatal("Failed to create JBoltControlThread as no os thread!"); ++ return; ++ } ++ ++ Handle thread_oop(THREAD, JNIHandles::resolve_non_null(_thread_obj)); ++ JavaThread::start_internal_daemon(THREAD, new_thread, thread_oop, MinPriority); ++ guarantee(Atomic::cmpxchg(&_the_java_thread, (JavaThread*) nullptr, new_thread) == nullptr, "sanity"); ++} ++ ++intx JBoltControlThread::sample_interval() { ++ return Atomic::load_acquire(&_interval); ++} ++ ++// Work to do before restarting a control schedule, twice and after only ++bool JBoltControlThread::prev_control_schdule(TRAPS) { ++ guarantee(JBoltManager::auto_mode(), "sanity"); ++ // Clear obsolete data structures ++ if (JBoltManager::clear_last_sample_datas() != 0) { ++ log_error(jbolt)("Something wrong happened in data clean, not going on..."); ++ return false; ++ } ++ ++ // Restart JFR ++ bufferedStream output; ++ DCmd::parse_and_execute(DCmd_Source_Internal, &output, "JFR.start name=jbolt-jfr", ' ', THREAD); ++ if (HAS_PENDING_EXCEPTION) { ++ ResourceMark rm; ++ log_warning(jbolt)("unable to start jfr jbolt-jfr"); ++ log_warning(jbolt)("exception type: %s", PENDING_EXCEPTION->klass()->external_name()); ++ // don't unwind this exception ++ CLEAR_PENDING_EXCEPTION; ++ } ++ ++ return true; ++} ++ ++void JBoltControlThread::control_schdule(TRAPS) { ++ guarantee(JBoltManager::auto_mode(), "sanity"); ++ { MonitorLocker locker(_sample_wait_monitor); ++ // Perform time wait ++ log_info(jbolt)("JBolt Starting Sample for %lds!!!", sample_interval()); ++ const jlong interval = (jlong) sample_interval(); ++ jlong cur_time = os::javaTimeMillis(); ++ const jlong end_time = cur_time + (interval * 1000); ++ while ((end_time > cur_time) && Atomic::load_acquire(&_signal) != SIG_STOP_PROFILING) { ++ int64_t timeout = (int64_t) (end_time - cur_time); ++ locker.wait(timeout); ++ cur_time = os::javaTimeMillis(); ++ } ++ } ++ // Close JFR ++ bufferedStream output; ++ DCmd::parse_and_execute(DCmd_Source_Internal, &output, "JFR.stop name=jbolt-jfr", ' ', THREAD); ++ if (HAS_PENDING_EXCEPTION) { ++ ResourceMark rm; ++ // JFR.stop maybe failed if a jfr recording is already stopped ++ // but it's nothing worry, jbolt should continue to work normally ++ log_warning(jbolt)("unable to stop jfr jbolt-jfr"); ++ log_warning(jbolt)("exception type: %s", PENDING_EXCEPTION->klass()->external_name()); ++ // don't unwind this exception ++ CLEAR_PENDING_EXCEPTION; ++ } ++ if (Atomic::cmpxchg(&_abort, true, false) == /* should abort */ true) { ++ return; ++ } ++ ++ size_t total_nmethod_size = 0; ++ // Init structures for load phase ++ JBoltManager::init_auto_transition(&total_nmethod_size, CATCH); ++ ++ if (total_nmethod_size > JBoltCodeHeapSize) { ++ log_warning(jbolt)("JBolt reordering not complete because JBolt CodeHeap is too small to place all ordered methods. Please use -XX:JBoltCodeHeapSize to enlarge"); ++ log_warning(jbolt)("JBoltCodeHeapSize=" UINTX_FORMAT " B ( need " UINTX_FORMAT " B).", JBoltCodeHeapSize, total_nmethod_size); ++ } ++ ++ if (not_first) { ++ // Exchange Hot Segment primary and secondary relationships ++ JBoltManager::swap_semi_jbolt_segs(); ++ } ++ ++ guarantee(JBoltManager::reorder_phase_profiling_to_reordering(), "sanity"); ++ Atomic::release_store(&_signal, SIG_NULL); ++ ++ // Start reorder ++ JBoltManager::reorder_all_methods(CATCH); ++} ++ ++// Work to do after reordering, twice and after only ++void JBoltControlThread::post_control_schdule(TRAPS) { ++ JBoltManager::clear_secondary_hot_seg(THREAD); ++} ++ ++void JBoltControlThread::thread_run(TRAPS) { ++ if (JBoltManager::auto_mode()) { ++ do { ++ guarantee(JBoltManager::reorder_phase_available_to_profiling(), "sanity"); ++ Atomic::release_store(&_signal, SIG_NULL); ++ if (not_first && !prev_control_schdule(THREAD)) continue; ++ control_schdule(THREAD); ++ JBoltManager::clear_structures(); ++ if (!JBoltManager::reorder_phase_reordering_to_available()) { ++ // abort logic ++ guarantee(JBoltManager::reorder_phase_profiling_to_available(), "sanity"); ++ guarantee(Atomic::cmpxchg(&_signal, SIG_STOP_PROFILING, SIG_NULL) == SIG_STOP_PROFILING, "sanity"); ++ } ++ else if (not_first) { ++ post_control_schdule(THREAD); ++ } ++ not_first = true; ++ MonitorLocker locker(_control_wait_monitor); ++ while (Atomic::load_acquire(&_signal) != SIG_START_PROFILING) { ++ locker.wait(60 * 1000); ++ } ++ } while(true); ++ } else { ++ guarantee(JBoltManager::can_reorder_now(), "sanity"); ++ guarantee(JBoltManager::reorder_phase_collecting_to_reordering(), "sanity"); ++ JBoltManager::reorder_all_methods(CATCH); ++ JBoltManager::clear_structures(); ++ guarantee(JBoltManager::reorder_phase_reordering_to_end(), "sanity"); ++ assert(JBoltLoadMode, "Only manual JBoltLoadMode can reach here"); ++ } ++} ++ ++bool JBoltControlThread::notify_sample_wait(bool abort) { ++ int old_sig = Atomic::cmpxchg(&_signal, SIG_NULL, SIG_STOP_PROFILING); ++ if (old_sig == SIG_NULL) { ++ MonitorLocker locker(_sample_wait_monitor); ++ // abort implementation maybe not in order in extreme cases ++ // add fence? or delete abort() if not so useful. ++ Atomic::release_store(&_abort, abort); ++ locker.notify(); ++ return true; ++ } ++ return false; ++} ++ ++bool JBoltControlThread::notify_control_wait(intx interval) { ++ int old_sig = Atomic::cmpxchg(&_signal, SIG_NULL, SIG_START_PROFILING); ++ if (old_sig == SIG_NULL) { ++ // this lock will be grabbed by ControlThread until it's waiting ++ MonitorLocker locker(_control_wait_monitor); ++ Atomic::release_store(&_interval, interval); ++ locker.notify(); ++ return true; ++ } ++ return false; ++} ++ ++JavaThread* JBoltControlThread::get_thread() { ++ return Atomic::load_acquire(&_the_java_thread); ++} +diff --git a/src/hotspot/share/jbolt/jBoltControlThread.hpp b/src/hotspot/share/jbolt/jBoltControlThread.hpp +new file mode 100644 +index 000000000..e63dd1ea9 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltControlThread.hpp +@@ -0,0 +1,69 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTCONTROLTHREAD_HPP ++#define SHARE_JBOLT_JBOLTCONTROLTHREAD_HPP ++ ++#include "runtime/thread.hpp" ++ ++/** ++ * Control JBolt how to run in this thread. ++ */ ++class JBoltControlThread : public AllStatic { ++public: ++ static const int SIG_NULL = 0; ++ static const int SIG_START_PROFILING = 1; ++ static const int SIG_STOP_PROFILING = 2; ++ ++private: ++ static JavaThread* volatile _the_java_thread; ++ // Can be notified by jcmd JBolt.start, restart a control schedule ++ static Monitor* _control_wait_monitor; ++ // Can be notified by jcmd JBolt.stop/abort, stop a running JFR ++ static Monitor* _sample_wait_monitor; ++ static jobject _thread_obj; ++ static int volatile _signal; ++ static bool volatile _abort; ++ static intx volatile _interval; ++ ++ static void thread_entry(JavaThread* thread, TRAPS) { thread_run(thread); } ++ static void thread_run(TRAPS); ++ ++ static intx sample_interval(); ++ static bool prev_control_schdule(TRAPS); ++ static void control_schdule(TRAPS); ++ static void post_control_schdule(TRAPS); ++ ++public: ++ static void init(TRAPS); ++ ++ static void start_thread(TRAPS); ++ ++ static bool notify_sample_wait(bool abort = false); ++ ++ static bool notify_control_wait(intx interval); ++ ++ static JavaThread* get_thread(); ++}; ++ ++#endif // SHARE_JBOLT_JBOLTCONTROLTHREAD_HPP +diff --git a/src/hotspot/share/jbolt/jBoltDcmds.cpp b/src/hotspot/share/jbolt/jBoltDcmds.cpp +new file mode 100644 +index 000000000..0cf1c75b4 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltDcmds.cpp +@@ -0,0 +1,221 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 "jbolt/jBoltDcmds.hpp" ++#include "jbolt/jBoltControlThread.hpp" ++#include "jbolt/jBoltManager.hpp" ++ ++bool register_jbolt_dcmds() { ++ uint32_t full_export = DCmd_Source_Internal | DCmd_Source_AttachAPI | DCmd_Source_MBean; ++ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); ++ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); ++ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); ++ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); ++ return true; ++} ++ ++JBoltStartDCmd::JBoltStartDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), ++ _duration("duration", "Duration of time(second) in this sample.", "INT", false, "600") { ++ _dcmdparser.add_dcmd_option(&_duration); ++} ++ ++int JBoltStartDCmd::num_arguments() { ++ ResourceMark rm; ++ JBoltStartDCmd* dcmd = new JBoltStartDCmd(NULL, false); ++ if (dcmd != NULL) { ++ DCmdMark mark(dcmd); ++ return dcmd->_dcmdparser.num_arguments(); ++ } else { ++ return 0; ++ } ++} ++ ++void JBoltStartDCmd::execute(DCmdSource source, TRAPS) { ++ if (!UseJBolt) { ++ output()->print_cr("Unable to execute because \"UseJBolt\" is disabled."); ++ return; ++ } ++ ++ if (!JBoltManager::auto_mode()) { ++ output()->print_cr("JBolt JCMD can only be used in auto mode."); ++ return; ++ } ++ ++ if (!JBoltManager::reorder_phase_is_available()) { ++ output()->print_cr("Unable to start because it's working now. Stop it first."); ++ return; ++ } ++ ++ intx interval = _duration.is_set() ? _duration.value() : JBoltSampleInterval; ++ ++ if (interval < 0) { ++ output()->print_cr("duration is set to %ld which is above range, should be in [0, %d]", interval, max_jint); ++ return; ++ } ++ ++ if (JBoltControlThread::notify_control_wait(interval)) { ++ output()->print_cr("OK. Start a new JBolt schedule, duration=%lds.", interval); ++ } ++ else { ++ output()->print_cr("It's busy now. Please try again later..."); ++ } ++} ++ ++void JBoltStartDCmd::print_help(const char* name) const { ++ output()->print_cr( ++ "Syntax : %s [options]\n" ++ "\n" ++ "Options:\n" ++ "\n" ++ " duration (Optional) Duration of time(second) in this sample. (INT, default value=600)\n" ++ "\n" ++ "Options must be specified using the or = syntax.\n" ++ "\n" ++ "Example usage:\n" ++ " $ jcmd JBolt.start\n" ++ " $ jcmd JBolt.start duration=900", name); ++} ++ ++void JBoltStopDCmd::execute(DCmdSource source, TRAPS) { ++ if (!UseJBolt) { ++ output()->print_cr("Unable to execute because \"UseJBolt\" is disabled."); ++ return; ++ } ++ ++ if (!JBoltManager::auto_mode()) { ++ output()->print_cr("JBolt JCMD can only be used in auto mode."); ++ return; ++ } ++ ++ if (!JBoltManager::reorder_phase_is_profiling()) { ++ output()->print_cr("Unable to stop because it's not sampling now."); ++ return; ++ } ++ ++ if (JBoltControlThread::notify_sample_wait()) { ++ output()->print_cr("OK.\"jbolt-jfr\" would be stopped and turn to reorder."); ++ } else { ++ output()->print_cr("It's busy now. Please try again later..."); ++ } ++} ++ ++void JBoltStopDCmd::print_help(const char* name) const { ++ output()->print_cr( ++ "Syntax : %s\n" ++ "\n" ++ "Example usage:\n" ++ " $ jcmd JBolt.stop", name); ++} ++ ++void JBoltAbortDCmd::execute(DCmdSource source, TRAPS) { ++ if (!UseJBolt) { ++ output()->print_cr("Unable to execute because \"UseJBolt\" is disabled."); ++ return; ++ } ++ ++ if (!JBoltManager::auto_mode()) { ++ output()->print_cr("JBolt JCMD can only be used in auto mode."); ++ return; ++ } ++ ++ if (!JBoltManager::reorder_phase_is_profiling()) { ++ output()->print_cr("Unable to abort because it's not sampling now."); ++ return; ++ } ++ ++ if (JBoltControlThread::notify_sample_wait(true)) { ++ output()->print_cr("OK.\"jbolt-jfr\" would be aborted."); ++ } else { ++ output()->print_cr("It's busy now. Please try again later..."); ++ } ++} ++ ++void JBoltAbortDCmd::print_help(const char* name) const { ++ output()->print_cr( ++ "Syntax : %s\n" ++ "\n" ++ "Example usage:\n" ++ " $ jcmd JBolt.abort", name); ++} ++ ++JBoltDumpDCmd::JBoltDumpDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), ++ _filename("filename", "Name of the file to which the flight recording data is dumped", "STRING", true, NULL) { ++ _dcmdparser.add_dcmd_option(&_filename); ++} ++ ++int JBoltDumpDCmd::num_arguments() { ++ ResourceMark rm; ++ JBoltDumpDCmd* dcmd = new JBoltDumpDCmd(NULL, false); ++ if (dcmd != NULL) { ++ DCmdMark mark(dcmd); ++ return dcmd->_dcmdparser.num_arguments(); ++ } else { ++ return 0; ++ } ++} ++ ++void JBoltDumpDCmd::execute(DCmdSource source, TRAPS) { ++ if (!UseJBolt) { ++ output()->print_cr("Unable to execute because \"UseJBolt\" is disabled."); ++ return; ++ } ++ ++ if (!JBoltManager::auto_mode()) { ++ output()->print_cr("JBolt JCMD can only be used in auto mode."); ++ return; ++ } ++ ++ const char* path = _filename.value(); ++ char buffer[PATH_MAX]; ++ char* rp = NULL; ++ ++ JBoltErrorCode ec = JBoltManager::dump_order_in_jcmd(path); ++ switch (ec) { ++ case JBoltOrderNULL: ++ output()->print_cr("Failed: No order applied by JBolt now."); ++ break; ++ case JBoltOpenFileError: ++ output()->print_cr("Failed: File open error or NULL: %s", path); ++ break; ++ case JBoltOK: ++ rp = realpath(path, buffer); ++ output()->print_cr("Successful: Dump to %s", buffer); ++ break; ++ default: ++ ShouldNotReachHere(); ++ } ++} ++ ++void JBoltDumpDCmd::print_help(const char* name) const { ++ output()->print_cr( ++ "Syntax : %s [options]\n" ++ "\n" ++ "Options:\n" ++ "\n" ++ " filename Name of the file to which the flight recording data is dumped. (STRING, no default value)\n" ++ "\n" ++ "Options must be specified using the or = syntax.\n" ++ "\n" ++ "Example usage:\n" ++ " $ jcmd JBolt.dump filename=order.log", name); ++} +\ No newline at end of file +diff --git a/src/hotspot/share/jbolt/jBoltDcmds.hpp b/src/hotspot/share/jbolt/jBoltDcmds.hpp +new file mode 100644 +index 000000000..f73fc01e6 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltDcmds.hpp +@@ -0,0 +1,129 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTDCMDS_HPP ++#define SHARE_JBOLT_JBOLTDCMDS_HPP ++ ++#include "services/diagnosticCommand.hpp" ++ ++class JBoltStartDCmd : public DCmdWithParser { ++ protected: ++ DCmdArgument _duration; ++ public: ++ JBoltStartDCmd(outputStream* output, bool heap); ++ ++ static const char* name() { ++ return "JBolt.start"; ++ } ++ static const char* description() { ++ return "Starts a new JBolt sample schedule(fail if sampling)"; ++ } ++ static const char* impact() { ++ return "Medium: Depending on JFR that JBolt rely on, the impact can range from low to high."; ++ } ++ static const JavaPermission permission() { ++ JavaPermission p = {"java.lang.management.ManagementPermission", "control", NULL}; ++ return p; ++ } ++ static int num_arguments(); ++ virtual void execute(DCmdSource source, TRAPS); ++ virtual void print_help(const char* name) const; ++}; ++ ++class JBoltStopDCmd : public DCmd { ++ public: ++ JBoltStopDCmd(outputStream* output, bool heap) : DCmd(output, heap) {} ++ ++ static const char* name() { ++ return "JBolt.stop"; ++ } ++ static const char* description() { ++ return "Stop a running JBolt sample schedule and reorder immediately(fail if not sampling)"; ++ } ++ static const char* impact() { ++ return "Low"; ++ } ++ static const JavaPermission permission() { ++ JavaPermission p = {"java.lang.management.ManagementPermission", "control", NULL}; ++ return p; ++ } ++ static int num_arguments() { ++ return 0; ++ } ++ ++ virtual void execute(DCmdSource source, TRAPS); ++ virtual void print_help(const char* name) const; ++}; ++ ++class JBoltAbortDCmd : public DCmd { ++ public: ++ JBoltAbortDCmd(outputStream* output, bool heap) : DCmd(output, heap) {} ++ ++ static const char* name() { ++ return "JBolt.abort"; ++ } ++ static const char* description() { ++ return "Stop a running JBolt sample schedule but don't reorder(fail if not sampling)"; ++ } ++ static const char* impact() { ++ return "Low"; ++ } ++ static const JavaPermission permission() { ++ JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; ++ return p; ++ } ++ static int num_arguments() { ++ return 0; ++ } ++ ++ virtual void execute(DCmdSource source, TRAPS); ++ virtual void print_help(const char* name) const; ++}; ++ ++class JBoltDumpDCmd : public DCmdWithParser { ++ protected: ++ DCmdArgument _filename; ++ public: ++ JBoltDumpDCmd(outputStream* output, bool heap); ++ ++ static const char* name() { ++ return "JBolt.dump"; ++ } ++ static const char* description() { ++ return "dump an effective order to file(fail if no order)"; ++ } ++ static const char* impact() { ++ return "Low"; ++ } ++ static const JavaPermission permission() { ++ JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; ++ return p; ++ } ++ static int num_arguments(); ++ virtual void execute(DCmdSource source, TRAPS); ++ virtual void print_help(const char* name) const; ++}; ++ ++bool register_jbolt_dcmds(); ++ ++#endif // SHARE_JBOLT_JBOLTDCMDS_HPP +\ No newline at end of file +diff --git a/src/hotspot/share/jbolt/jBoltManager.cpp b/src/hotspot/share/jbolt/jBoltManager.cpp +new file mode 100644 +index 000000000..abead8167 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltManager.cpp +@@ -0,0 +1,1387 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 ++#include ++#include ++ ++#include "classfile/javaClasses.inline.hpp" ++#include "classfile/symbolTable.hpp" ++#include "classfile/vmSymbols.hpp" ++#include "code/codeBlob.hpp" ++#include "code/codeCache.hpp" ++#include "compiler/compileBroker.hpp" ++#include "jbolt/jBoltCallGraph.hpp" ++#include "jbolt/jBoltControlThread.hpp" ++#include "jbolt/jBoltManager.hpp" ++#include "jbolt/jBoltUtils.inline.hpp" ++#include "jfr/jfr.hpp" ++#include "jfr/support/jfrMethodLookup.hpp" ++#include "logging/log.hpp" ++#include "logging/logStream.hpp" ++#include "memory/resourceArea.hpp" ++#include "oops/klass.inline.hpp" ++#include "oops/method.inline.hpp" ++#include "runtime/arguments.hpp" ++#include "runtime/atomic.hpp" ++#include "runtime/globals_extension.hpp" ++#include "runtime/handles.inline.hpp" ++#include "runtime/jniHandles.hpp" ++#include "runtime/os.hpp" ++#include "runtime/safepointVerifiers.hpp" ++#include "runtime/sweeper.hpp" ++#include "utilities/formatBuffer.hpp" ++ ++static constexpr int LINE_BUF_SIZE = 8192; // used to parse JBolt order file ++static constexpr int MIN_FRAMESCOUNT = 2; // used as default stacktrace depth ++static constexpr int ILL_NM_STATE = -2; // used to present nmethod illegal state ++ ++#define B_TF(b) (b ? "V" : "X") ++ ++GrowableArray* JBoltManager::_hot_methods_sorted = nullptr; ++JBoltManager::MethodKeyMap* JBoltManager::_hot_methods_vis = nullptr; ++int JBoltManager::_reorder_method_threshold_cnt = 0; ++ ++volatile int JBoltManager::_reorder_phase = JBoltReorderPhase::Available; ++volatile int JBoltManager::_reorderable_method_cnt = 0; ++Method* volatile JBoltManager::_cur_reordering_method = nullptr; ++ ++Thread* JBoltManager::_start_reordering_thread = nullptr; ++ ++JBoltManager::StackFrameKeyMap* JBoltManager::_sampled_methods_refs = nullptr; ++ ++bool JBoltManager::_auto_mode = false; ++ ++// swap between MethodJBoltHot and MethodJBoltTmp ++volatile int JBoltManager::_primary_hot_seg = CodeBlobType::MethodJBoltHot; ++volatile int JBoltManager::_secondary_hot_seg = CodeBlobType::MethodJBoltTmp; ++ ++GrowableArray* _order_stored = nullptr; ++ ++// This is a tmp obj used only in initialization phases. ++// We cannot alloc Symbol in phase 1 so we have to parses the order file again ++// in phase 2. ++// This obj will be freed after initialization. ++static FILE* _order_fp = nullptr; ++ ++// The threshold to trigger JBolt reorder in load mode. ++static const double _jbolt_reorder_threshold = 0.8; ++ ++static bool read_line(FILE* fp, char* buf, int buf_len, int* res_len) { ++ if (fgets(buf, buf_len, fp) == nullptr) { ++ return false; ++ } ++ int len = (int) strcspn(buf, "\r\n"); ++ buf[len] = '\0'; ++ *res_len = len; ++ return true; ++} ++ ++static bool read_a_size(char* buf, size_t* res) { ++ char* t = strchr(buf, ' '); ++ if (t == nullptr) return false; ++ *t = '\0'; ++ julong v; ++ if (!Arguments::atojulong(buf, &v)) { ++ *t = ' '; ++ return false; ++ } ++ *t = ' '; ++ *res = (size_t) v; ++ return true; ++} ++ ++static void replace_all(char* s, char from, char to) { ++ char* begin = s; ++ while (true) { ++ char* t = strchr(begin, from); ++ if (t == nullptr) { ++ break; ++ } ++ *t = to; ++ begin = t + 1; ++ } ++} ++ ++JBoltMethodValue::~JBoltMethodValue() { ++ if (_comp_info != nullptr) delete get_comp_info(); ++} ++ ++CompileTaskInfo* JBoltMethodValue::get_comp_info() { ++ return Atomic::load_acquire(&_comp_info); ++} ++ ++bool JBoltMethodValue::set_comp_info(CompileTaskInfo* info) { ++ return Atomic::cmpxchg(&_comp_info, (CompileTaskInfo*) nullptr, info) == nullptr; ++} ++ ++void JBoltMethodValue::clear_comp_info_but_not_release() { ++ Atomic::release_store(&_comp_info, (CompileTaskInfo*) nullptr); ++} ++ ++JBoltStackFrameValue::~JBoltStackFrameValue() { ++ if (_method_holder != nullptr) { ++ if (JNIHandles::is_weak_global_handle(_method_holder)) { ++ JNIHandles::destroy_weak_global(_method_holder); ++ } else { ++ JNIHandles::destroy_global(_method_holder); ++ } ++ } ++} ++ ++jobject JBoltStackFrameValue::get_method_holder() { return _method_holder; } ++ ++void JBoltStackFrameValue::clear_method_holder_but_not_release() { _method_holder = nullptr; } ++ ++CompileTaskInfo::CompileTaskInfo(Method* method, int osr_bci, int comp_level, int comp_reason, Method* hot_method, int hot_cnt): ++ _method(method), _osr_bci(osr_bci), _comp_level(comp_level), _comp_reason(comp_reason), _hot_method(hot_method), _hot_count(hot_cnt) { ++ Thread* thread = Thread::current(); ++ ++ assert(_method != nullptr, "sanity"); ++ // _method_holder can be null for boot loader (the null loader) ++ _method_holder = JNIHandles::make_weak_global(Handle(thread, _method->method_holder()->klass_holder())); ++ ++ if (_hot_method != nullptr && _hot_method != _method) { ++ _hot_method_holder = JNIHandles::make_weak_global(Handle(thread, _hot_method->method_holder()->klass_holder())); ++ } else { ++ _hot_method_holder = nullptr; ++ } ++} ++ ++CompileTaskInfo::~CompileTaskInfo() { ++ if (_method_holder != nullptr) { ++ if (JNIHandles::is_weak_global_handle(_method_holder)) { ++ JNIHandles::destroy_weak_global(_method_holder); ++ } else { ++ JNIHandles::destroy_global(_method_holder); ++ } ++ } ++ if (_hot_method_holder != nullptr) { ++ if (JNIHandles::is_weak_global_handle(_hot_method_holder)) { ++ JNIHandles::destroy_weak_global(_hot_method_holder); ++ } else { ++ JNIHandles::destroy_global(_hot_method_holder); ++ } ++ } ++} ++ ++/** ++ * Set the weak reference to strong reference if the method is not unloaded. ++ * It seems that the life cycle of Method is consistent with that of the Klass and CLD. ++ * @see CompileTask::select_for_compilation() ++ */ ++bool CompileTaskInfo::try_select() { ++ NoSafepointVerifier nsv; ++ Thread* thread = Thread::current(); ++ // is unloaded ++ if (_method_holder != nullptr && JNIHandles::is_weak_global_handle(_method_holder) && JNIHandles::is_global_weak_cleared(_method_holder)) { ++ if (log_is_enabled(Debug, jbolt)) { ++ log_debug(jbolt)("Some method has been unloaded so skip reordering for it: p=%p.", _method); ++ } ++ return false; ++ } ++ ++ assert(_method->method_holder()->is_loader_alive(), "should be alive"); ++ Handle method_holder(thread, _method->method_holder()->klass_holder()); ++ JNIHandles::destroy_weak_global(_method_holder); ++ _method_holder = JNIHandles::make_global(method_holder); ++ ++ if (_hot_method_holder != nullptr) { ++ Handle hot_method_holder(thread, _hot_method->method_holder()->klass_holder()); ++ JNIHandles::destroy_weak_global(_hot_method_holder); ++ _hot_method_holder = JNIHandles::make_global(Handle(thread, _hot_method->method_holder()->klass_holder())); ++ } ++ return true; ++} ++ ++static void check_arg_not_set(JVMFlagsEnum flag) { ++ if (JVMFlag::is_cmdline(flag)) { ++ vm_exit_during_initialization(err_msg("Do not set VM option %s without UseJBolt enabled.", ++ JVMFlag::flag_from_enum(flag)->name())); ++ } ++} ++ ++static const char *method_type_to_string(u1 type) { ++ switch (type) { ++ case JfrStackFrame::FRAME_INTERPRETER: ++ return "Interpreted"; ++ case JfrStackFrame::FRAME_JIT: ++ return "JIT compiled"; ++ case JfrStackFrame::FRAME_INLINE: ++ return "Inlined"; ++ case JfrStackFrame::FRAME_NATIVE: ++ return "Native"; ++ default: ++ ShouldNotReachHere(); ++ return "Unknown"; ++ } ++} ++ ++uintptr_t related_data_jbolt[] = { ++ (uintptr_t)in_bytes(JfrStackTrace::hash_offset()), ++ (uintptr_t)in_bytes(JfrStackTrace::id_offset()), ++ (uintptr_t)in_bytes(JfrStackTrace::hotcount_offset()), ++ (uintptr_t)in_bytes(JfrStackTrace::frames_offset()), ++ (uintptr_t)in_bytes(JfrStackTrace::frames_count_offset()), ++ ++ (uintptr_t)in_bytes(JfrStackFrame::klass_offset()), ++ (uintptr_t)in_bytes(JfrStackFrame::methodid_offset()), ++ (uintptr_t)in_bytes(JfrStackFrame::bci_offset()), ++ (uintptr_t)in_bytes(JfrStackFrame::type_offset()), ++ ++ (uintptr_t)JBoltFunc::constructor, ++ (uintptr_t)JBoltFunc::copy_constructor, ++ (uintptr_t)JBoltCall::constructor, ++ (uintptr_t)JBoltCall::copy_constructor, ++ (uintptr_t)JBoltCallGraph::static_add_func, ++ (uintptr_t)JBoltCallGraph::static_add_call ++}; ++ ++/** ++ * Invoked in JfrStackTraceRepository::add_jbolt(). ++ * Each time JFR record a valid stacktrace, ++ * we log a weak ptr of each unique method in _sampled_methods_refs. ++ */ ++void JBoltManager::log_stacktrace(const JfrStackTrace& stacktrace) { ++ Thread* thread = Thread::current(); ++ HandleMark hm(thread); ++ ++ const JfrStackFrame* frames = stacktrace.get_frames(); ++ unsigned int framesCount = stacktrace.get_framesCount(); ++ ++ for (u4 i = 0; i < framesCount; ++i) { ++ const JfrStackFrame& frame = frames[i]; ++ ++ JBoltStackFrameKey stackframe_key(const_cast(frame.get_klass()), frame.get_methodId()); ++ ++ if (!_sampled_methods_refs->contains(stackframe_key)) { ++ jobject method_holder = JNIHandles::make_weak_global(Handle(thread, frame.get_klass()->klass_holder())); ++ JBoltStackFrameValue stackframe_value(method_holder); ++ _sampled_methods_refs->put(stackframe_key, stackframe_value); ++ // put() transmits method_holder ownership to element in map ++ // set the method_holder to nullptr in temp variable stackframe_value, to avoid double free ++ stackframe_value.clear_method_holder_but_not_release(); ++ } ++ } ++} ++ ++methodHandle JBoltManager::lookup_method(InstanceKlass* klass, traceid method_id) { ++ Thread* thread = Thread::current(); ++ JBoltStackFrameKey stackframe_key(klass, method_id); ++ JBoltStackFrameValue* stackframe_value = _sampled_methods_refs->get(stackframe_key); ++ if (stackframe_value == nullptr) { ++ return methodHandle(); ++ } ++ ++ jobject method_holder = stackframe_value->get_method_holder(); ++ if (method_holder != nullptr && JNIHandles::is_weak_global_handle(method_holder) && JNIHandles::is_global_weak_cleared(method_holder)) { ++ log_debug(jbolt)("method klass at %p is unloaded", (void*)klass); ++ return methodHandle(); ++ } ++ ++ const Method* const lookup_method = JfrMethodLookup::lookup(klass, method_id); ++ if (lookup_method == NULL) { ++ // stacktrace obsolete ++ return methodHandle(); ++ } ++ assert(lookup_method != NULL, "invariant"); ++ methodHandle method(thread, const_cast(lookup_method)); ++ ++ return method; ++} ++ ++void JBoltManager::construct_stacktrace(const JfrStackTrace& stacktrace) { ++ NoSafepointVerifier nsv; ++ if (stacktrace.get_framesCount() < MIN_FRAMESCOUNT) ++ return; ++ ++ u4 topFrameIndex = 0; ++ u4 max_frames = 0; ++ ++ const JfrStackFrame* frames = stacktrace.get_frames(); ++ unsigned int framesCount = stacktrace.get_framesCount(); ++ ++ // Native method subsidence ++ while (topFrameIndex < framesCount) { ++ const JfrStackFrame& frame = frames[topFrameIndex]; ++ ++ if (method_type_to_string(frame.get_type()) != "Native") { ++ break; ++ } ++ ++ topFrameIndex++; ++ } ++ ++ if (framesCount - topFrameIndex < MIN_FRAMESCOUNT) { ++ return; ++ } ++ ++ os::Linux::jboltLog_precalc(topFrameIndex, max_frames); ++ ++ JBoltFunc **tempfunc = NULL; ++ ++ for (u4 i = 0; i < max_frames; ++i) { ++ const JfrStackFrame& frame = frames[topFrameIndex + i]; ++ ++ methodHandle method = lookup_method(const_cast(frame.get_klass()), frame.get_methodId()); ++ if (method.is_null()) { ++ break; ++ } ++ const CompiledMethod* const compiled = method->code(); ++ ++ log_trace(jbolt)( ++ "Method id - %lu\n\tBytecode index - %hu\n\tSignature - %s\n\tType - %s\n\tCompiler - %s\n\tCompile Level - %d\n\tSize - %dB\n", ++ frame.get_methodId(), ++ frame.get_byteCodeIndex(), ++ method->external_name(), ++ method_type_to_string(frame.get_type()), ++ compiled != NULL ? compiled->compiler_name() : "None", ++ compiled != NULL ? compiled->comp_level() : -1, ++ compiled != NULL ? compiled->size() : 0); ++ ++ if (compiled == NULL) break; ++ ++ JBoltMethodKey method_key(method->constants()->pool_holder()->name(), method->name(), method->signature()); ++ JBoltFunc* func = JBoltFunc::constructor(frame.get_klass(), frame.get_methodId(), compiled->size(), method_key); ++ ++ if (!os::Linux::jboltLog_do(related_data_jbolt, (address)&stacktrace, i, compiled->comp_level(), (address)func, (address*)&tempfunc)) { ++ delete func; ++ func = NULL; ++ break; ++ } ++ } ++ ++ log_trace(jbolt)( ++ "StackTrace hash - %u hotcount - %d\n==============================\n", stacktrace.hash(), stacktrace.hotcount()); ++} ++ ++/** ++ * Invoked in JfrStackTraceRepository::write(). ++ * Each time JfrChunkWrite do write and clear stacktrace table, ++ * we update the CG by invoke construct_stacktrace(). ++ */ ++void JBoltManager::construct_cg_once() { ++ guarantee((UseJBolt && JBoltManager::reorder_phase_is_profiling()), "sanity"); ++ ++ GrowableArray* traces = create_growable_array(); ++ ++ { ++ MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag); ++ const JfrStackTraceRepository& repository = JfrStackTraceRepository::instance(); ++ ++ if (repository.get_entries_count_jbolt() == 0) { ++ return; ++ } ++ ++ const JfrStackTrace* const * table = repository.get_stacktrace_table_jbolt(); ++ for (uint i = 0; i < repository.TABLE_SIZE; ++i) { ++ for (const JfrStackTrace* trace = table[i]; trace != nullptr; trace = trace->next()) { ++ traces->append(const_cast(trace)); ++ } ++ } ++ } ++ ++ for (int i = 0; i < traces->length(); ++i) { ++ construct_stacktrace(*(traces->at(i))); ++ } ++ ++ log_trace(jbolt)( ++ "+++++++ one time log over ++++++\n\n"); ++ delete traces; ++} ++ ++static void write_order(const GrowableArray* order, fileStream& fs) { ++ assert(order != nullptr, "sanity"); ++ const char* methodFlag = "M"; ++ const char* segmentor = "C\n"; ++ ++ log_debug(jbolt)("+============================+\n\t\t\tORDER\n"); ++ ++ for (int i = 0; i < order->length(); ++i) { ++ const JBoltFunc& func = order->at(i); ++ if (func.klass() == NULL) { ++ fs.write(segmentor, strlen(segmentor)); ++ continue; ++ } ++ ++ char* holder_name = func.method_key().klass()->as_C_string(); ++ char* name = func.method_key().name()->as_C_string(); ++ char* signature = func.method_key().sig()->as_C_string(); ++ char size[LINE_BUF_SIZE] = {0}; ++ snprintf(size, sizeof(size), "%d", func.size()); ++ ++ log_debug(jbolt)("order %d --- Method - %s %s %s\n", i, holder_name, name, signature); ++ ++ fs.write(methodFlag, strlen(methodFlag)); ++ fs.write(" ", 1); ++ fs.write(size, strlen(size)); ++ fs.write(" ", 1); ++ fs.write(holder_name, strlen(holder_name)); ++ fs.write(" ", 1); ++ fs.write(name, strlen(name)); ++ fs.write(" ", 1); ++ fs.write(signature, strlen(signature)); ++ fs.write("\n", 1); ++ } ++} ++ ++/** ++ * Invoked in before_exit(). ++ * Only use in manual mode. ++ * Dump the order to JBoltOrderFile before vm exit. ++ */ ++void JBoltManager::dump_order_in_manual() { ++ guarantee((UseJBolt && JBoltDumpMode), "sanity"); ++ NoSafepointVerifier nsv; ++ ResourceMark rm; ++ GrowableArray* order = JBoltCallGraph::callgraph_instance().hfsort(); ++ ++ fileStream orderFile(JBoltOrderFile, "w+"); ++ ++ if (JBoltOrderFile == NULL || !orderFile.is_open()) { ++ log_error(jbolt)("JBoltOrderFile open error"); ++ vm_exit_during_initialization("JBoltOrderFile open error"); ++ } ++ ++ write_order(order, orderFile); ++ ++ log_info(jbolt)("order generate successful !!"); ++ log_debug(jbolt)("+============================+\n"); ++ delete order; ++ delete _sampled_methods_refs; ++ _sampled_methods_refs = nullptr; ++ JBoltCallGraph::deinitialize(); ++} ++ ++JBoltErrorCode JBoltManager::dump_order_in_jcmd(const char* filename) { ++ guarantee(UseJBolt, "sanity"); ++ NoSafepointVerifier nsv; ++ ResourceMark rm; ++ ++ if (_order_stored == nullptr) return JBoltOrderNULL; ++ ++ fileStream orderFile(filename, "w+"); ++ ++ if (filename == NULL || !orderFile.is_open()) return JBoltOpenFileError; ++ ++ write_order(_order_stored, orderFile); ++ ++ return JBoltOK; ++} ++ ++/** ++ * Do not set the JBolt-related flags manually if UseJBolt is not enabled. ++ */ ++void JBoltManager::check_arguments_not_set() { ++ if (UseJBolt) return; ++ ++ check_arg_not_set(FLAG_MEMBER_ENUM(JBoltDumpMode)); ++ check_arg_not_set(FLAG_MEMBER_ENUM(JBoltLoadMode)); ++ check_arg_not_set(FLAG_MEMBER_ENUM(JBoltOrderFile)); ++ check_arg_not_set(FLAG_MEMBER_ENUM(JBoltSampleInterval)); ++ check_arg_not_set(FLAG_MEMBER_ENUM(JBoltCodeHeapSize)); ++} ++ ++/** ++ * Check which mode is JBolt in. ++ * If JBoltDumpMode or JBoltLoadMode is set manually then do nothing, else it will be fully auto sched by JBolt itself. ++ */ ++void JBoltManager::check_mode() { ++ if (!(JBoltDumpMode || JBoltLoadMode)) { ++ _auto_mode = true; ++ return; ++ } ++ ++ if (!FLAG_IS_DEFAULT(JBoltSampleInterval)) { ++ log_warning(jbolt)("JBoltSampleInterval is ignored because it is not in auto mode."); ++ } ++ ++ if (JBoltDumpMode && JBoltLoadMode) { ++ vm_exit_during_initialization("Do not set both JBoltDumpMode and JBoltLoadMode!"); ++ } ++ ++ guarantee((JBoltDumpMode ^ JBoltLoadMode), "Must set either JBoltDumpMode or JBoltLoadMode!"); ++} ++ ++/** ++ * If in auto mode, JBoltOrderFile will be ignored ++ * If in any manual mode, then JBoltOrderFile will be necessary. ++ * Check whether the order file exists or is accessable. ++ */ ++void JBoltManager::check_order_file() { ++ if (auto_mode()) { ++ if (JBoltOrderFile != nullptr) log_warning(jbolt)("JBoltOrderFile is ignored because it is in auto mode."); ++ return; ++ } ++ ++ if (JBoltOrderFile == nullptr) { ++ vm_exit_during_initialization("JBoltOrderFile is not set!"); ++ } ++ ++ bool file_exist = (::access(JBoltOrderFile, F_OK) == 0); ++ if (file_exist) { ++ if (JBoltDumpMode) { ++ log_warning(jbolt)("JBoltOrderFile to dump already exists and will be overwritten: file=%s.", JBoltOrderFile); ++ ::remove(JBoltOrderFile); ++ } ++ } else { ++ if (JBoltLoadMode) { ++ vm_exit_during_initialization(err_msg("JBoltOrderFile does not exist or cannot be accessed! file=\"%s\".", JBoltOrderFile)); ++ } ++ } ++} ++ ++void JBoltManager::check_dependency() { ++ if (FLAG_IS_CMDLINE(FlightRecorder) ? !FlightRecorder : false) { ++ vm_exit_during_initialization("JBolt depends on JFR!"); ++ } ++ ++ if (!CompilerConfig::is_c2_enabled()) { ++ vm_exit_during_initialization("JBolt depends on C2!"); ++ } ++ ++ if (!SegmentedCodeCache) { ++ vm_exit_during_initialization("JBolt depends on SegmentedCodeCache!"); ++ } ++} ++ ++size_t JBoltManager::calc_nmethod_size_with_padding(size_t nmethod_size) { ++ return align_up(nmethod_size, (size_t) CodeCacheSegmentSize); ++} ++ ++size_t JBoltManager::calc_segment_size_with_padding(size_t segment_size) { ++ size_t page_size = CodeCache::page_size(); ++ if (segment_size < page_size) return page_size; ++ return align_down(segment_size, page_size); ++} ++ ++/** ++ * We have to parse the file twice because SymbolTable is not inited in phase 1... ++ */ ++void JBoltManager::load_order_file_phase1(int* method_cnt, size_t* segment_size) { ++ assert(JBoltOrderFile != nullptr, "sanity"); ++ ++ _order_fp = os::fopen(JBoltOrderFile, "r"); ++ if (_order_fp == nullptr) { ++ vm_exit_during_initialization(err_msg("Cannot open file JBoltOrderFile! file=\"%s\".", JBoltOrderFile)); ++ } ++ ++ int mth_cnt = 0; ++ size_t seg_size = 0; ++ ++ char line[LINE_BUF_SIZE]; ++ int len = -1; ++ while (read_line(_order_fp, line, sizeof(line), &len)) { ++ if (len <= 2) continue; ++ if (line[0] != 'M' || line[1] != ' ') continue; ++ char* left_start = line + 2; ++ ++ // parse nmethod size ++ size_t nmethod_size; ++ if (!read_a_size(left_start, &nmethod_size)) { ++ vm_exit_during_initialization(err_msg("Wrong format of JBolt order line! line=\"%s\".", line)); ++ } ++ ++mth_cnt; ++ seg_size += calc_nmethod_size_with_padding(nmethod_size); ++ } ++ ++ *method_cnt = mth_cnt; ++ *segment_size = seg_size; ++ log_trace(jbolt)("Read order file method_cnt=%d, estimated_segment_size=" SIZE_FORMAT ".", mth_cnt, seg_size); ++} ++ ++bool JBoltManager::parse_method_line_phase2(char* const line, const int len) { ++ // Skip "M ". ++ char* left_start = line + 2; ++ ++ // Skip nmethod size (has parsed in phase1). ++ { ++ char* t = strchr(left_start, ' '); ++ if (t == nullptr) return false; ++ left_start = t + 1; ++ } ++ ++ // Modify "java.lang.Obj" to "java/lang/Obj". ++ replace_all(left_start, '.', '/'); ++ ++ // Parse the three symbols: class name, method name, signature. ++ Symbol* three_symbols[3]; ++ for (int i = 0; i < 2; ++i) { ++ char* t = strchr(left_start, ' '); ++ if (t == nullptr) return false; ++ Symbol* sym = SymbolTable::new_symbol(left_start, t - left_start); ++ three_symbols[i] = sym; ++ left_start = t + 1; ++ } ++ Symbol* sym = SymbolTable::new_symbol(left_start, line + len - left_start); ++ three_symbols[2] = sym; ++ if (log_is_enabled(Trace, jbolt)) { ++ log_trace(jbolt)("HotMethod init: key={%s %s %s}", ++ three_symbols[0]->as_C_string(), ++ three_symbols[1]->as_C_string(), ++ three_symbols[2]->as_C_string()); ++ } ++ ++ // Add to data structure. ++ JBoltMethodKey method_key(three_symbols[0], three_symbols[1], three_symbols[2]); ++ _hot_methods_sorted->append(method_key); ++ JBoltMethodValue method_value; ++ bool put = _hot_methods_vis->put(method_key, method_value); ++ if (!put) { ++ vm_exit_during_initialization(err_msg("Duplicated method: {%s %s %s}!", ++ three_symbols[0]->as_C_string(), ++ three_symbols[1]->as_C_string(), ++ three_symbols[2]->as_C_string())); ++ } ++ ++ return true; ++} ++ ++bool JBoltManager::parse_connected_component_line_phase2(char* const line, const int len) { return true; } ++ ++void JBoltManager::load_order_file_phase2(TRAPS) { ++ guarantee(_order_fp != nullptr, "sanity"); ++ ++ // re-scan ++ fseek(_order_fp, 0, SEEK_SET); ++ ++ char line[LINE_BUF_SIZE]; ++ int len = -1; ++ while (read_line(_order_fp, line, sizeof(line), &len)) { ++ if (len <= 0) continue; ++ bool success = false; ++ switch (line[0]) { ++ case '#': success = true; break; // ignore comments ++ case 'M': success = parse_method_line_phase2(line, len); break; ++ case 'C': success = parse_connected_component_line_phase2(line, len); break; ++ default: break; ++ } ++ if (!success) { ++ vm_exit_during_initialization(err_msg("Wrong format of JBolt order line! line=\"%s\".", line)); ++ } ++ } ++ fclose(_order_fp); ++ _order_fp = nullptr; ++} ++ ++void JBoltManager::init_load_mode_phase1() { ++ if (!(auto_mode() || JBoltLoadMode)) return; ++ ++ if (auto_mode()) { ++ // auto mode has no order now, initialize as default. ++ _hot_methods_sorted = new (ResourceObj::C_HEAP, mtCompiler) GrowableArray(1, mtCompiler); ++ _hot_methods_vis = new (ResourceObj::C_HEAP, mtCompiler) MethodKeyMap(); ++ log_info(jbolt)("Default set JBoltCodeHeapSize=" UINTX_FORMAT " B (" UINTX_FORMAT " MB).", JBoltCodeHeapSize, JBoltCodeHeapSize / 1024 / 1024); ++ return; ++ } ++ guarantee(reorder_phase_available_to_collecting(), "sanity"); ++ size_t total_nmethod_size = 0; ++ int method_cnt = 0; ++ load_order_file_phase1(&method_cnt, &total_nmethod_size); ++ ++ _hot_methods_sorted = new (ResourceObj::C_HEAP, mtCompiler) GrowableArray(method_cnt, mtCompiler); ++ _hot_methods_vis = new (ResourceObj::C_HEAP, mtCompiler) MethodKeyMap(); ++ ++ if (FLAG_IS_DEFAULT(JBoltCodeHeapSize)) { ++ FLAG_SET_ERGO(JBoltCodeHeapSize, calc_segment_size_with_padding(total_nmethod_size)); ++ log_info(jbolt)("Auto set JBoltCodeHeapSize=" UINTX_FORMAT " B (" UINTX_FORMAT " MB).", JBoltCodeHeapSize, JBoltCodeHeapSize / 1024 / 1024); ++ } ++} ++ ++void JBoltManager::init_load_mode_phase2(TRAPS) { ++ // Only manual load mode need load phase2 ++ if (!JBoltLoadMode) return; ++ ++ load_order_file_phase2(CHECK); ++ _reorderable_method_cnt = 0; ++ _reorder_method_threshold_cnt = _hot_methods_sorted->length() * _jbolt_reorder_threshold; ++} ++ ++void JBoltManager::init_dump_mode_phase2(TRAPS) { ++ if (!(auto_mode() || JBoltDumpMode)) return; ++ ++ JBoltCallGraph::initialize(); ++ _sampled_methods_refs = new (ResourceObj::C_HEAP, mtTracing) StackFrameKeyMap(); ++ ++ // JBolt will create a JFR by itself ++ // In auto mode, will stop in JBoltControlThread::start_thread() after JBoltSampleInterval. ++ // In manual dump mode, won't stop until program exit. ++ log_info(jbolt)("JBolt in dump mode now, start a JFR recording named \"jbolt-jfr\"."); ++ bufferedStream output; ++ DCmd::parse_and_execute(DCmd_Source_Internal, &output, "JFR.start name=jbolt-jfr", ' ', THREAD); ++ if (HAS_PENDING_EXCEPTION) { ++ ResourceMark rm; ++ log_warning(jbolt)("unable to start jfr jbolt-jfr"); ++ log_warning(jbolt)("exception type: %s", PENDING_EXCEPTION->klass()->external_name()); ++ // don't unwind this exception ++ CLEAR_PENDING_EXCEPTION; ++ } ++} ++ ++static void update_stored_order(const GrowableArray* order) { ++ if (_order_stored != nullptr) { ++ // use a tmp for releasing space to provent _order_stored from being a wild pointer ++ GrowableArray* tmp = _order_stored; ++ _order_stored = nullptr; ++ delete tmp; ++ } ++ _order_stored = new (ResourceObj::C_HEAP, mtTracing) GrowableArray(order->length(), mtTracing); ++ _order_stored->appendAll(order); ++} ++ ++static CompileTaskInfo* create_compile_task_info(methodHandle& method) { ++ CompiledMethod* compiled = method->code(); ++ if (compiled == nullptr) { ++ log_warning(jbolt)("Recompilation Task init failed because of null nmethod. func: %s.", method->external_name()); ++ return nullptr; ++ } ++ int osr_bci = compiled->is_osr_method() ? compiled->osr_entry_bci() : InvocationEntryBci; ++ int comp_level = compiled->comp_level(); ++ // comp_level adaptation for deoptmization ++ if (comp_level > CompLevel_simple && comp_level <= CompLevel_full_optimization) comp_level = CompLevel_full_optimization; ++ CompileTask::CompileReason comp_reason = CompileTask::Reason_Reorder; ++ CompileTaskInfo* ret = new CompileTaskInfo(method(), osr_bci, comp_level, (int)comp_reason, ++ nullptr, 0); ++ return ret; ++} ++ ++/** ++ * This function is invoked by JBoltControlThread. ++ * Do initialization for converting dump mode to load mode. ++ */ ++void JBoltManager::init_auto_transition(size_t* segment_size, TRAPS) { ++ guarantee(UseJBolt && auto_mode(), "sanity"); ++ NoSafepointVerifier nsv; ++ ResourceMark rm; ++ ++ GrowableArray* order = JBoltCallGraph::callgraph_instance().hfsort(); ++ update_stored_order(order); ++ ++ size_t seg_size = 0; ++ for (int i = 0; i < order->length(); ++i) { ++ const JBoltFunc& func = order->at(i); ++ if (func.klass() == NULL) { ++ continue; ++ } ++ ++ methodHandle method = lookup_method(const_cast(func.klass()), func.method_id()); ++ if (method.is_null()) { ++ continue; ++ } ++ ++ CompileTaskInfo* cti = create_compile_task_info(method); ++ if (cti == nullptr) { ++ continue; ++ } ++ ++ JBoltMethodKey method_key = func.method_key(); ++ JBoltMethodValue method_value; ++ if (!method_value.set_comp_info(cti)) { ++ delete cti; ++ continue; ++ } ++ ++ seg_size += calc_nmethod_size_with_padding(func.size()); ++ _hot_methods_sorted->append(method_key); ++ bool put = _hot_methods_vis->put(method_key, method_value); ++ if (!put) { ++ vm_exit_during_initialization(err_msg("Duplicated method: {%s %s %s}!", ++ method_key.klass()->as_C_string(), ++ method_key.name()->as_C_string(), ++ method_key.sig()->as_C_string())); ++ } ++ method_value.clear_comp_info_but_not_release(); ++ } ++ log_info(jbolt)("order generate successful !!"); ++ *segment_size = calc_segment_size_with_padding(seg_size); ++ delete order; ++} ++ ++/** ++ * This function must be invoked after CompilerConfig::ergo_initialize() in Arguments::apply_ergo(). ++ * This function must be invoked before CodeCache::initialize_heaps() in codeCache_init() in init_globals(). ++ * Thread and SymbolTable is not inited now! ++ */ ++void JBoltManager::init_phase1() { ++ if (!UseJBolt) return; ++ check_mode(); ++ check_dependency(); ++ check_order_file(); ++ ++ /* dump mode has nothing to do in phase1 */ ++ init_load_mode_phase1(); ++} ++ ++void JBoltManager::init_phase2(TRAPS) { ++ if (!UseJBolt) return; ++ ++ ResourceMark rm(THREAD); ++ init_dump_mode_phase2(CHECK); ++ init_load_mode_phase2(CHECK); ++ ++ // Manual dump mode doesn't need JBoltControlThread, directly go to profiling phase ++ if (JBoltDumpMode) { ++ guarantee(JBoltManager::reorder_phase_available_to_profiling(), "sanity"); ++ return; ++ } ++ ++ JBoltControlThread::init(CHECK); ++ // Auto mode will start control thread earlier. ++ // Manual load mode start later in check_start_reordering() ++ if (auto_mode()) { ++ JBoltControlThread::start_thread(CHECK_AND_CLEAR); ++ } ++} ++ ++/** ++ * Code heaps are initialized between init phase 1 and init phase 2. ++ */ ++void JBoltManager::init_code_heaps(size_t non_nmethod_size, size_t profiled_size, size_t non_profiled_size, size_t cache_size, size_t ps, size_t alignment) { ++ assert(UseJBolt && !JBoltDumpMode, "sanity"); ++ if(!is_aligned(JBoltCodeHeapSize, alignment)) { ++ vm_exit_during_initialization(err_msg("JBoltCodeHeapSize should be %ld aligned, please adjust", alignment)); ++ } ++ ++ size_t jbolt_hot_size = JBoltCodeHeapSize; ++ size_t jbolt_tmp_size = JBoltCodeHeapSize; ++ size_t jbolt_total_size = jbolt_hot_size + jbolt_tmp_size; ++ if (non_profiled_size <= jbolt_total_size) { ++ vm_exit_during_initialization(err_msg("Not enough space in non-profiled code heap to split out JBolt heap(s): " SIZE_FORMAT "K <= " SIZE_FORMAT "K", non_profiled_size/K, jbolt_total_size/K)); ++ } ++ non_profiled_size -= jbolt_total_size; ++ non_profiled_size = align_down(non_profiled_size, alignment); ++ FLAG_SET_ERGO(NonProfiledCodeHeapSize, non_profiled_size); ++ ++ // Memory layout with JBolt: ++ // ---------- high ----------- ++ // Non-profiled nmethods ++ // JBolt tmp nmethods ++ // JBolt hot nmethods ++ // Non-nmethods ++ // Profiled nmethods ++ // ---------- low ------------ ++ ReservedCodeSpace rs = CodeCache::reserve_heap_memory(cache_size, ps); ++ ReservedSpace profiled_space = rs.first_part(profiled_size); ++ ReservedSpace r1 = rs.last_part(profiled_size); ++ ReservedSpace non_nmethod_space = r1.first_part(non_nmethod_size); ++ ReservedSpace r2 = r1.last_part(non_nmethod_size); ++ ReservedSpace jbolt_hot_space = r2.first_part(jbolt_hot_size); ++ ReservedSpace r3 = r2.last_part(jbolt_hot_size); ++ ReservedSpace jbolt_tmp_space = r3.first_part(jbolt_tmp_size); ++ ReservedSpace non_profiled_space = r3.last_part(jbolt_tmp_size); ++ ++ CodeCache::add_heap(non_nmethod_space, "CodeHeap 'non-nmethods'", CodeBlobType::NonNMethod); ++ CodeCache::add_heap(profiled_space, "CodeHeap 'profiled nmethods'", CodeBlobType::MethodProfiled); ++ CodeCache::add_heap(non_profiled_space, "CodeHeap 'non-profiled nmethods'", CodeBlobType::MethodNonProfiled); ++ const char* no_space = nullptr; ++ CodeCache::add_heap(jbolt_hot_space, "CodeHeap 'jbolt hot nmethods'", CodeBlobType::MethodJBoltHot); ++ if (jbolt_hot_size != jbolt_hot_space.size()) { ++ no_space = "hot"; ++ } ++ CodeCache::add_heap(jbolt_tmp_space, "CodeHeap 'jbolt tmp nmethods'", CodeBlobType::MethodJBoltTmp); ++ if (jbolt_tmp_size != jbolt_tmp_space.size()) { ++ no_space = "tmp"; ++ } ++ if (no_space != nullptr) { ++ vm_exit_during_initialization(FormatBuffer<1024>( ++ "No enough space for JBolt %s heap: \n" ++ "Expect: cache_size=" SIZE_FORMAT "K, profiled_size=" SIZE_FORMAT "K, non_nmethod_size=" SIZE_FORMAT "K, jbolt_hot_size=" SIZE_FORMAT "K, non_profiled_size=" SIZE_FORMAT "K, jbolt_tmp_size=" SIZE_FORMAT "K\n" ++ "Actual: cache_size=" SIZE_FORMAT "K, profiled_size=" SIZE_FORMAT "K, non_nmethod_size=" SIZE_FORMAT "K, jbolt_hot_size=" SIZE_FORMAT "K, non_profiled_size=" SIZE_FORMAT "K, jbolt_tmp_size=" SIZE_FORMAT "K\n" ++ "alignment=" SIZE_FORMAT, ++ no_space, ++ cache_size/K, profiled_size/K, non_nmethod_size/K, jbolt_hot_size/K, non_profiled_size/K, jbolt_tmp_size/K, ++ rs.size()/K, profiled_space.size()/K, non_nmethod_space.size()/K, jbolt_hot_space.size()/K, non_profiled_space.size()/K, jbolt_tmp_space.size()/K, ++ alignment)); ++ } ++} ++ ++int JBoltManager::reorder_phase() { ++ return Atomic::load_acquire(&_reorder_phase); ++} ++ ++bool JBoltManager::reorder_phase_available_to_collecting() { ++ assert(!auto_mode(), "two-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Available, JBoltReorderPhase::Collecting) == JBoltReorderPhase::Available; ++} ++ ++bool JBoltManager::reorder_phase_collecting_to_reordering() { ++ assert(!auto_mode(), "two-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Collecting, JBoltReorderPhase::Reordering) == JBoltReorderPhase::Collecting; ++} ++ ++bool JBoltManager::reorder_phase_available_to_profiling() { ++ assert(auto_mode(), "one-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Available, JBoltReorderPhase::Profiling) == JBoltReorderPhase::Available; ++} ++ ++bool JBoltManager::reorder_phase_profiling_to_reordering() { ++ assert(auto_mode(), "one-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Profiling, JBoltReorderPhase::Reordering) == JBoltReorderPhase::Profiling; ++} ++ ++bool JBoltManager::reorder_phase_reordering_to_available() { ++ assert(auto_mode(), "one-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Reordering, JBoltReorderPhase::Available) == JBoltReorderPhase::Reordering; ++} ++ ++bool JBoltManager::reorder_phase_profiling_to_available() { ++ assert(auto_mode(), "one-phase only"); ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Profiling, JBoltReorderPhase::Available) == JBoltReorderPhase::Profiling; ++} ++ ++bool JBoltManager::reorder_phase_reordering_to_end() { ++ return Atomic::cmpxchg(&_reorder_phase, JBoltReorderPhase::Reordering, JBoltReorderPhase::End) == JBoltReorderPhase::Reordering; ++} ++ ++bool JBoltManager::reorder_phase_is_available() { ++ bool res = (Atomic::load_acquire(&_reorder_phase) == JBoltReorderPhase::Available); ++ assert(!res || auto_mode(), "one-phase only"); ++ return res; ++} ++ ++bool JBoltManager::reorder_phase_is_collecting() { ++ bool res = (Atomic::load_acquire(&_reorder_phase) == JBoltReorderPhase::Collecting); ++ assert(!res || !auto_mode(), "two-phase only"); ++ return res; ++} ++ ++bool JBoltManager::reorder_phase_is_profiling() { ++ bool res = (Atomic::load_acquire(&_reorder_phase) == JBoltReorderPhase::Profiling); ++ assert(!res || auto_mode(), "one-phase only"); ++ return res; ++} ++ ++bool JBoltManager::reorder_phase_is_reordering() { ++ return Atomic::load_acquire(&_reorder_phase) == JBoltReorderPhase::Reordering; ++} ++ ++bool JBoltManager::reorder_phase_is_collecting_or_reordering() { ++ int p = Atomic::load_acquire(&_reorder_phase); ++ assert(p != JBoltReorderPhase::Collecting || !auto_mode(), "two-phase only"); ++ return p == JBoltReorderPhase::Collecting || p == JBoltReorderPhase::Reordering; ++} ++ ++Method* JBoltManager::cur_reordering_method() { ++ return Atomic::load_acquire(&_cur_reordering_method); ++} ++ ++void JBoltManager::set_cur_reordering_method(Method* method) { ++ Atomic::release_store(&_cur_reordering_method, method); ++} ++ ++int JBoltManager::inc_reorderable_method_cnt() { ++ return Atomic::add(&_reorderable_method_cnt, +1); ++} ++ ++bool JBoltManager::can_reorder_now() { ++ return Atomic::load_acquire(&_reorderable_method_cnt) >= _reorder_method_threshold_cnt; ++} ++ ++bool JBoltManager::should_reorder_now() { ++ return Atomic::load_acquire(&_reorderable_method_cnt) == _reorder_method_threshold_cnt; ++} ++ ++int JBoltManager::primary_hot_seg() { ++ return Atomic::load_acquire(&_primary_hot_seg); ++} ++ ++int JBoltManager::secondary_hot_seg() { ++ return Atomic::load_acquire(&_secondary_hot_seg); ++} ++ ++int JBoltManager::clear_manager() { ++ /* _hot_methods_sorted, _hot_methods_vis and _sampled_methods_refs have been cleared in other pos, don't delete again */ ++ guarantee(_hot_methods_sorted == nullptr, "sanity"); ++ guarantee(_hot_methods_vis == nullptr, "sanity"); ++ guarantee(_sampled_methods_refs == nullptr, "sanity"); ++ // Re-allocate them ++ _hot_methods_sorted = new (ResourceObj::C_HEAP, mtCompiler) GrowableArray(1, mtCompiler); ++ _hot_methods_vis = new (ResourceObj::C_HEAP, mtCompiler) MethodKeyMap(); ++ _sampled_methods_refs = new (ResourceObj::C_HEAP, mtTracing) StackFrameKeyMap(); ++ ++ return 0; ++} ++ ++/** ++ * Invoked in JBoltControlThread::prev_control_schedule(). ++ * Expect to only execute in auto mode while JBolt.start triggered. ++ * Clear JBolt related data structures to restore a initial env same as sample never happening. ++*/ ++int JBoltManager::clear_last_sample_datas() { ++ int ret = 0; ++ // Clear _table_jbolt in JfrStackTraceRepository ++ ret = JfrStackTraceRepository::clear_jbolt(); ++ // Clear JBoltCallGraph ++ ret = JBoltCallGraph::callgraph_instance().clear_instance(); ++ // Clear JBoltManager ++ ret = clear_manager(); ++ ++ return ret; ++} ++ ++/** ++ * Invoked in JBoltControlThread::prev_control_schedule(). ++ * Swap primary hot segment with secondary hot segment ++ */ ++void JBoltManager::swap_semi_jbolt_segs() { ++ guarantee(reorder_phase_is_profiling(), "swap must happen in reorder phase Profiling."); ++ int tmp = Atomic::xchg(&_secondary_hot_seg, Atomic::load_acquire(&_primary_hot_seg)); ++ Atomic::xchg(&_primary_hot_seg, tmp); ++} ++ ++/** ++ * Invoked in JBoltControlThread::post_control_schdule(). ++ * Free scondary hot segment space for next reorder. ++ */ ++void JBoltManager::clear_secondary_hot_seg(TRAPS) { ++ guarantee(reorder_phase_is_available(), "secondary clear must happen in reorder phase Available."); ++ // scan secondary hot seg and recompile alive nmethods to non-profiled ++ ResourceMark rm(THREAD); ++ // We cannot alloc weak handle within CodeCache_lock because of the mutex rank check. ++ // So instead we keep the methods alive only within the scope of this method. ++ JBoltUtils::MetaDataKeepAliveMark mdm(THREAD); ++ const GrowableArray& to_recompile = mdm.kept(); ++ ++ { ++ MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); ++ CodeHeap* sec_hot = CodeCache::get_code_heap(secondary_hot_seg()); ++ for (CodeBlob* cb = (CodeBlob*) sec_hot->first(); cb != nullptr; cb = (CodeBlob*) sec_hot->next(cb)) { ++ nmethod* nm = cb->as_nmethod_or_null(); ++ Method* m = nm->method(); ++ if (nm && nm->get_state() == CompiledMethod::in_use && m != nullptr) { ++ mdm.add(m); ++ } ++ } ++ } ++ ++ for (int i = 0; i < to_recompile.length(); ++i) { ++ Method* m = (Method*) to_recompile.at(i); ++ methodHandle method(THREAD, m); ++ CompileTaskInfo* cti = create_compile_task_info(method); ++ if (cti == nullptr) continue; ++ guarantee(cti->try_select(), "method is on stack, should be ok"); ++ assert(cti->hot_method() == nullptr, "sanity"); ++ methodHandle hot_method; ++ ++ bool recompile_result = enqueue_recompile_task(cti, method, hot_method, THREAD); ++ if(recompile_result) { ++ check_compiled_result(method(), CodeBlobType::MethodNonProfiled, THREAD); ++ } ++ delete cti; ++ } ++ ++ // need three times force_sweep to thoroughly clear secondary hot segment ++ NMethodSweeper::force_sweep(); ++ NMethodSweeper::force_sweep(); ++ NMethodSweeper::force_sweep(); ++ log_info(jbolt)("Sweep secondary segment"); ++ print_code_heaps(); ++} ++ ++/** ++ * Invoked in ciEnv::register_method() in CompilerThread. ++ * Controls where the new nmethod should be allocated. ++ * ++ * Returns CodeBlobType::All if it is not determined by JBolt logic. ++ */ ++int JBoltManager::calc_code_blob_type(Method* method, CompileTask* task, TRAPS) { ++ assert(UseJBolt && reorder_phase_is_collecting_or_reordering(), "sanity"); ++ const int not_care = CodeBlobType::All; ++ ++ // Only cares about non-profiled segment. ++ int lvl = task->comp_level(); ++ if (lvl != CompLevel_full_optimization && lvl != CompLevel_simple) { ++ return not_care; ++ } ++ ++ // Ignore on-stack-replacement. ++ if (task->osr_bci() != InvocationEntryBci) { ++ return not_care; ++ } ++ ++ int cur_reorder_phase = reorder_phase(); ++ // Do nothing after reordering. ++ if (cur_reorder_phase != JBoltReorderPhase::Collecting && cur_reorder_phase != JBoltReorderPhase::Reordering) { ++ return not_care; ++ } ++ // Only cares about the current reordering method. ++ if (cur_reorder_phase == JBoltReorderPhase::Reordering) { ++ if (cur_reordering_method() == method) { ++ log_trace(jbolt)("Compiling to JBolt heap: method=%s.", method->name_and_sig_as_C_string()); ++ return primary_hot_seg(); ++ } ++ return not_care; ++ } ++ guarantee(cur_reorder_phase == JBoltReorderPhase::Collecting, "sanity"); ++ assert(!auto_mode(), "sanity"); ++ ++ JBoltMethodKey method_key(method); ++ JBoltMethodValue* method_value = _hot_methods_vis->get(method_key); ++ if (method_value == nullptr) { ++ return not_care; ++ } ++ ++ // Register the method and the compile task. ++ if (method_value->get_comp_info() == nullptr) { ++ CompileTaskInfo* cti = new CompileTaskInfo(method, task->osr_bci(), task->comp_level(), (int) task->compile_reason(), ++ task->hot_method(), task->hot_count()); ++ if (method_value->set_comp_info(cti)) { ++ int cnt = inc_reorderable_method_cnt(); ++ log_trace(jbolt)("Reorderable method found: cnt=%d, lvl=%d, p=%p, method=%s.", ++ cnt, task->comp_level(), method, method->name_and_sig_as_C_string()); ++ if (is_power_of_2(_reorder_method_threshold_cnt - cnt)) { ++ log_debug(jbolt)("Reorderable cnt: %d/%d/%d", cnt, _reorder_method_threshold_cnt, _hot_methods_sorted->length()); ++ } ++ if (cnt == _reorder_method_threshold_cnt) { ++ log_info(jbolt)("Time to reorder: %d/%d/%d", cnt, _reorder_method_threshold_cnt, _hot_methods_sorted->length()); ++ _start_reordering_thread = THREAD; ++ } ++ } else { ++ delete cti; ++ } ++ } ++ ++ return secondary_hot_seg(); ++} ++ ++/** ++ * Check if reordering should start. ++ * The reordering should only start once (for now). ++ * We don't do this check in "if (cnt == _reorder_method_threshold_cnt)" in calc_code_blob_type() ++ * because it will cause an assert error: "Possible safepoint reached by thread that does not allow it". ++ */ ++void JBoltManager::check_start_reordering(TRAPS) { ++ // _start_reordering_thread is set and tested in the same thread. No need to be atomic. ++ if (_start_reordering_thread == THREAD) { ++ _start_reordering_thread = nullptr; ++ if (JBoltControlThread::get_thread() == nullptr) { ++ assert(can_reorder_now(), "sanity"); ++ log_info(jbolt)("Starting JBoltControlThread to reorder."); ++ JBoltControlThread::start_thread(CHECK_AND_CLEAR); ++ } ++ } ++} ++ ++/** ++ * The task will be added to the compile queue and be compiled just like other tasks. ++ */ ++CompileTask* JBoltManager::create_a_task_instance(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS) { ++ int osr_bci = cti->osr_bci(); ++ int comp_level = cti->comp_level(); ++ CompileTask::CompileReason comp_reason = (CompileTask::CompileReason) cti->comp_reason(); ++ int hot_count = cti->hot_count(); ++ bool is_blocking = true; ++ ++ // init a task (@see CompileBroker::create_compile_task()) ++ CompileTask* task = CompileTask::allocate(); ++ int compile_id = CompileBroker::assign_compile_id(method, osr_bci); ++ task->initialize(compile_id, method, osr_bci, comp_level, ++ hot_method, hot_count, comp_reason, ++ is_blocking); ++ return task; ++} ++ ++/** ++ * Print the failure reason if something is wrong in recompilation. ++ */ ++void JBoltManager::check_compiled_result(Method* method, int check_blob_type, TRAPS) { ++ CompiledMethod* cm = method->code(); ++ if (cm == nullptr) { ++ log_warning(jbolt)("Recompilation failed because of null nmethod."); ++ return; ++ } ++ nmethod* nm = cm->as_nmethod_or_null(); ++ if (nm == nullptr) { ++ log_warning(jbolt)("Recompilation failed because the code is not a nmethod."); ++ return; ++ } ++ int code_blob_type = CodeCache::get_code_blob_type(nm); ++ if (code_blob_type != check_blob_type) { ++ log_warning(jbolt)("Recompilation failed because the nmethod is not in heap [%s]: it's in [%s].", ++ CodeCache::get_code_heap_name(check_blob_type), CodeCache::get_code_heap_name(code_blob_type)); ++ return; ++ } ++ log_trace(jbolt)("Recompilation good: code=%p, size=%d, method=%s, heap=%s.", ++ nm, nm->size(), method->name_and_sig_as_C_string(), CodeCache::get_code_heap_name(check_blob_type)); ++} ++ ++/** ++ * Create the compile task instance and enqueue into compile queue ++ */ ++bool JBoltManager::enqueue_recompile_task(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS) { ++ CompileTask* task = nullptr; ++ CompileQueue* queue = CompileBroker::compile_queue(cti->comp_level()); ++ { MutexLocker locker(THREAD, MethodCompileQueue_lock); ++ if (CompileBroker::compilation_is_in_queue(method)) { ++ log_warning(jbolt)("JBOLT won't compile as \"compilation is in queue\": method=%s.", method->name_and_sig_as_C_string()); ++ return false; ++ } ++ ++ task = create_a_task_instance(cti, method, hot_method, CHECK_AND_CLEAR_false); ++ if (task == nullptr) { ++ log_warning(jbolt)("JBOLT won't compile as \"task instance is NULL\": method=%s.", method->name_and_sig_as_C_string()); ++ return false; ++ } ++ queue->add(task); ++ } ++ ++ // Same waiting logic as CompileBroker::wait_for_completion(). ++ { MonitorLocker ml(THREAD, task->lock()); ++ while (!task->is_complete() && !CompileBroker::is_compilation_disabled_forever()) { ++ ml.wait(); ++ } ++ } ++ ++ CompileBroker::wait_for_completion(task); ++ task = nullptr; // freed ++ return true; ++} ++ ++/** ++ * Recompilation is to move the nmethod to _primary_hot_seg. ++ */ ++bool JBoltManager::recompile_one(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS) { ++ ResourceMark rm(THREAD); ++ ++ if (cti->osr_bci() != InvocationEntryBci) { ++ log_trace(jbolt)("We don't handle on-stack-replacement nmethods: method=%s.", method->name_and_sig_as_C_string()); ++ return false; ++ } ++ ++ if (log_is_enabled(Trace, jbolt)) { ++ const char* heap_name = nullptr; ++ CompiledMethod* cm = method->code(); ++ if (cm == nullptr) heap_name = ""; ++ else if (!cm->is_nmethod()) heap_name = ""; ++ else heap_name = CodeCache::get_code_heap_name(CodeCache::get_code_blob_type(cm)); ++ log_trace(jbolt)("Start to recompile & reorder: heap=%s, method=%s.", heap_name, method->name_and_sig_as_C_string()); ++ } ++ ++ // Add a compilation task. ++ set_cur_reordering_method(method()); ++ enqueue_recompile_task(cti, method, hot_method, CHECK_AND_CLEAR_false); ++ check_compiled_result(method(), primary_hot_seg(), CHECK_AND_CLEAR_false); ++ ++ return true; ++} ++ ++/** ++ * This method is invoked in a new thread JBoltControlThread. ++ * Recompiles the methods in the order list one by one (serially) based on the hot order. ++ * The methods to recompile were almost all in MethodJBoltTmp, and will in install in ++ * MethodJBoltHot after recompilation. ++ */ ++void JBoltManager::reorder_all_methods(TRAPS) { ++ guarantee(UseJBolt && reorder_phase_is_reordering(), "sanity"); ++ log_info(jbolt)("Start to reorder!"); ++ print_code_heaps(); ++ ++ ResourceMark rm(THREAD); ++ for (int i = 0; i < _hot_methods_sorted->length(); ++i) { ++ JBoltMethodKey k = _hot_methods_sorted->at(i); ++ JBoltMethodValue* v = _hot_methods_vis->get(k); ++ if (v == nullptr) continue; ++ CompileTaskInfo* cti = v->get_comp_info(); ++ if (cti == nullptr) continue; ++ if (!cti->try_select()) continue; ++ ++ methodHandle method(THREAD, cti->method()); ++ methodHandle hot_method(THREAD, cti->hot_method()); ++ ++ recompile_one(cti, method, hot_method, THREAD); ++ if (HAS_PENDING_EXCEPTION) { ++ Handle ex(THREAD, PENDING_EXCEPTION); ++ CLEAR_PENDING_EXCEPTION; ++ LogTarget(Warning, jbolt) lt; ++ if (lt.is_enabled()) { ++ LogStream ls(lt); ++ ls.print("Failed to recompile the method: %s.", method->name_and_sig_as_C_string()); ++ java_lang_Throwable::print(ex(), &ls); ++ } ++ } ++ } ++ ++ log_info(jbolt)("JBolt reordering succeeds."); ++ print_code_heaps(); ++ ++} ++ ++void JBoltManager::clear_structures() { ++ delete _sampled_methods_refs; ++ _sampled_methods_refs = nullptr; ++ JBoltCallGraph::deinitialize(); ++ set_cur_reordering_method(nullptr); ++ delete _hot_methods_sorted; ++ _hot_methods_sorted = nullptr; ++ delete _hot_methods_vis; ++ _hot_methods_vis = nullptr; ++} ++ ++void JBoltManager::print_code_heap(outputStream& ls, CodeHeap* heap, const char* name) { ++ for (CodeBlob* cb = (CodeBlob*) heap->first(); cb != nullptr; cb = (CodeBlob*) heap->next(cb)) { ++ nmethod* nm = cb->as_nmethod_or_null(); ++ Method* m = nm != nullptr ? nm->method() : nullptr; ++ ls.print_cr("%s %p %d alive=%s, zombie=%s, nmethod=%s, entrant=%s, name=[%s %s %s]", ++ name, ++ cb, cb->size(), ++ B_TF(cb->is_alive()), ++ B_TF(cb->is_zombie()), ++ B_TF(cb->is_nmethod()), ++ nm ? B_TF(!nm->is_not_entrant()) : "?", ++ m ? m->method_holder()->name()->as_C_string() : cb->name(), ++ m ? m->name()->as_C_string() : nullptr, ++ m ? m->signature()->as_C_string() : nullptr); ++ } ++} ++ ++void JBoltManager::print_code_heaps() { ++ { ++ LogTarget(Debug, jbolt) lt; ++ if (!lt.is_enabled()) return; ++ LogStream ls(lt); ++ MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); ++ CodeCache::print_summary(&ls, true); ++ } ++ ++ { ++ LogTarget(Trace, jbolt) lt; ++ if (!lt.is_enabled()) return; ++ LogStream ls(lt); ++ CodeHeap* hot_heap = CodeCache::get_code_heap(CodeBlobType::MethodJBoltHot); ++ CodeHeap* tmp_heap = CodeCache::get_code_heap(CodeBlobType::MethodJBoltTmp); ++ ++ ResourceMark rm; ++ if (hot_heap == nullptr) { ++ ls.print_cr("The jbolt hot heap is null."); ++ } else { ++ print_code_heap(ls, hot_heap, "hot"); ++ } ++ if (tmp_heap == nullptr) { ++ ls.print_cr("The jbolt tmp heap is null."); ++ } else { ++ print_code_heap(ls, tmp_heap, "tmp"); ++ } ++ } ++} ++ ++#undef B_TF +\ No newline at end of file +diff --git a/src/hotspot/share/jbolt/jBoltManager.hpp b/src/hotspot/share/jbolt/jBoltManager.hpp +new file mode 100644 +index 000000000..1718075c4 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltManager.hpp +@@ -0,0 +1,323 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTMANAGER_HPP ++#define SHARE_JBOLT_JBOLTMANAGER_HPP ++ ++#include "compiler/compileTask.hpp" ++#include "jbolt/jbolt_globals.hpp" ++#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" ++#include "jfr/dcmd/jfrDcmds.hpp" ++#include "memory/allocation.hpp" ++#include "memory/heap.hpp" ++#include "oops/symbol.hpp" ++#include "runtime/handles.hpp" ++#include "utilities/growableArray.hpp" ++#include "utilities/resourceHash.hpp" ++ ++class CompileTask; ++class CompileTaskInfo; ++class Method; ++class Thread; ++ ++enum JBoltErrorCode { ++ JBoltOK = 0, ++ JBoltOrderNULL = 1, ++ JBoltOpenFileError = 2 ++}; ++ ++struct JBoltReorderPhase { ++ static const int Available = 0; // JBolt logic is not working or is done (can be reordered again now). ++ static const int Collecting = 1; // Collecting methods in the order file (this phase is for two-phase only). ++ static const int Profiling = 2; // JFR is working (this phase is for one-phase only). ++ static const int Reordering = 3; // Recompiling and re-laying. ++ static const int End = 4; // JBolt is not available anymore (for two-phase, or error happened on one-phase). ++}; ++ ++class JBoltMethodKey : public StackObj { ++ Symbol* _klass; ++ Symbol* _name; ++ Symbol* _sig; ++ ++ void inc_ref_cnt() { ++ Symbol* arr[] = { _klass, _name, _sig }; ++ for (int i = 0; i < (int) (sizeof(arr) / sizeof(arr[0])); ++i) { ++ if (arr[i] != nullptr) arr[i]->increment_refcount(); ++ } ++ } ++ ++ void dec_ref_cnt() { ++ Symbol* arr[] = { _klass, _name, _sig }; ++ for (int i = 0; i < (int) (sizeof(arr) / sizeof(arr[0])); ++i) { ++ if (arr[i] != nullptr) arr[i]->decrement_refcount(); ++ } ++ } ++public: ++ ++ JBoltMethodKey(Symbol* klass, Symbol* name, Symbol* sig): _klass(klass), _name(name), _sig(sig) { /* no inc_ref_cnt() here for SymbolTable::new_symbol() */ } ++ JBoltMethodKey(Method* method): _klass(method->method_holder()->name()), _name(method->name()), _sig(method->signature()) { inc_ref_cnt(); } ++ JBoltMethodKey(const JBoltMethodKey& other): _klass(other._klass), _name(other._name), _sig(other._sig) { inc_ref_cnt(); } ++ JBoltMethodKey(): _klass(nullptr), _name(nullptr), _sig(nullptr) {} ++ ~JBoltMethodKey() { dec_ref_cnt(); } ++ ++ JBoltMethodKey& operator = (const JBoltMethodKey& other) { ++ dec_ref_cnt(); ++ _klass = other._klass; ++ _name = other._name; ++ _sig = other._sig; ++ inc_ref_cnt(); ++ return *this; ++ } ++ ++ unsigned hash() const { ++ int v = primitive_hash(_klass); ++ v = v * 31 + primitive_hash(_name); ++ v = v * 31 + primitive_hash(_sig); ++ return v; ++ } ++ bool equals(const JBoltMethodKey& other) const { ++ return _klass == other._klass && _name == other._name && _sig == other._sig; ++ } ++ ++ static unsigned calc_hash(const JBoltMethodKey& k) { ++ return k.hash(); ++ } ++ static bool calc_equals(const JBoltMethodKey& k1, const JBoltMethodKey& k2) { ++ return k1.equals(k2); ++ } ++ ++ Symbol* klass() const { return _klass; } ++ Symbol* name() const { return _name; } ++ Symbol* sig() const { return _sig; } ++}; ++ ++class JBoltMethodValue : public StackObj { ++private: ++ CompileTaskInfo* volatile _comp_info; ++ ++public: ++ JBoltMethodValue(): _comp_info(nullptr) {} ++ ~JBoltMethodValue(); ++ ++ CompileTaskInfo* get_comp_info(); ++ bool set_comp_info(CompileTaskInfo* info); ++ void clear_comp_info_but_not_release(); ++}; ++ ++class CompileTaskInfo : public CHeapObj { ++ Method* const _method; ++ jobject _method_holder; ++ const int _osr_bci; ++ const int _comp_level; ++ const int _comp_reason; ++ Method* const _hot_method; ++ jobject _hot_method_holder; ++ const int _hot_count; ++ ++public: ++ CompileTaskInfo(Method* method, int osr_bci, int comp_level, int comp_reason, Method* hot_method, int hot_cnt); ++ ~CompileTaskInfo(); ++ ++ bool try_select(); ++ ++ Method* method() const { return _method; } ++ int osr_bci() const { return _osr_bci; } ++ int comp_level() const { return _comp_level; } ++ int comp_reason() const { return _comp_reason; } ++ Method* hot_method() const { return _hot_method; } ++ int hot_count() const { return _hot_count; } ++}; ++ ++class JBoltStackFrameKey : public StackObj { ++ InstanceKlass* _klass; ++ traceid _methodid; ++ ++public: ++ JBoltStackFrameKey(InstanceKlass* klass, traceid methodid): _klass(klass), _methodid(methodid) {} ++ JBoltStackFrameKey(const JBoltStackFrameKey& other): _klass(other._klass), _methodid(other._methodid) {} ++ JBoltStackFrameKey(): _klass(NULL), _methodid(0) {} ++ ~JBoltStackFrameKey() { /* nothing to do as _klass is a softcopy of JfrStackFrame::_klass */ } ++ ++ ++ JBoltStackFrameKey& operator = (const JBoltStackFrameKey& other) { ++ _klass = other._klass; ++ _methodid = other._methodid; ++ return *this; ++ } ++ ++ unsigned hash() const { ++ int v = primitive_hash(_klass); ++ v = v * 31 + primitive_hash(_methodid); ++ return v; ++ } ++ ++ bool equals(const JBoltStackFrameKey& other) const { ++ return _klass == other._klass && _methodid == other._methodid; ++ } ++ ++ static unsigned calc_hash(const JBoltStackFrameKey& k) { ++ return k.hash(); ++ } ++ ++ static bool calc_equals(const JBoltStackFrameKey& k1, const JBoltStackFrameKey& k2) { ++ return k1.equals(k2); ++ } ++}; ++ ++class JBoltStackFrameValue : public StackObj { ++private: ++ jobject _method_holder; ++ ++public: ++ JBoltStackFrameValue(jobject method_holder): _method_holder(method_holder) {} ++ ~JBoltStackFrameValue(); ++ ++ jobject get_method_holder(); ++ void clear_method_holder_but_not_release(); ++}; ++ ++class JBoltManager : public AllStatic { ++ friend class JBoltControlThread; ++ ++ typedef ResourceHashtable MethodKeyMap; ++ ++ typedef ResourceHashtable StackFrameKeyMap; ++ ++ static GrowableArray* _hot_methods_sorted; ++ static MethodKeyMap* _hot_methods_vis; ++ static int _reorder_method_threshold_cnt; ++ ++ static volatile int _reorder_phase; ++ static volatile int _reorderable_method_cnt; ++ static Method* volatile _cur_reordering_method; ++ ++ // the CompilerThread to start the new JBoltControlThread ++ static Thread* _start_reordering_thread; ++ ++ static StackFrameKeyMap* _sampled_methods_refs; ++ ++ // when not set JBoltDumpMode or JBoltLoadMode, JBolt will be in one-step auto mode. ++ static bool _auto_mode; ++ ++ // use MethodJBoltHot and MethodJBoltTmp as two semi hot space. ++ // each time restart a schedule, we exchange primary and secondary ++ static volatile int _primary_hot_seg; ++ static volatile int _secondary_hot_seg; ++ ++private: ++ // Used in dump mode. ++ static methodHandle lookup_method(InstanceKlass* klass, traceid method_id); ++ static void construct_stacktrace(const JfrStackTrace &stacktrace); ++ ++ // Used in init phase 1. ++ static void check_mode(); ++ static void check_order_file(); ++ static void check_dependency(); ++ static size_t calc_nmethod_size_with_padding(size_t nmethod_size); ++ static size_t calc_segment_size_with_padding(size_t segment_size); ++ static void load_order_file_phase1(int* method_cnt , size_t* total_nmethod_size); ++ static void init_load_mode_phase1(); ++ ++ // Used in init phase 2. ++ static bool parse_method_line_phase2(char* const line, const int len); ++ static bool parse_connected_component_line_phase2(char* const line, const int len); ++ static void load_order_file_phase2(TRAPS); ++ static void init_load_mode_phase2(TRAPS); ++ static void init_dump_mode_phase2(TRAPS); ++ ++ // Used in auto mode. ++ static int primary_hot_seg(); ++ static int secondary_hot_seg(); ++ ++ // Used in auto mode prev_control_schedule ++ static int clear_last_sample_datas(); ++ static void swap_semi_jbolt_segs(); ++ static int clear_manager(); ++ ++ // Used in auto mode control_schedule ++ static void init_auto_transition(size_t* segment_size, TRAPS); ++ ++ // Used in auto mode post_control_schedule ++ static void clear_secondary_hot_seg(TRAPS); ++ ++ // JBolt phases ++ ++ static int reorder_phase(); ++ ++ static bool reorder_phase_available_to_collecting(); ++ static bool reorder_phase_collecting_to_reordering(); ++ ++ static bool reorder_phase_available_to_profiling(); ++ static bool reorder_phase_profiling_to_reordering(); ++ static bool reorder_phase_reordering_to_available(); ++ static bool reorder_phase_profiling_to_available(); ++ ++ static bool reorder_phase_reordering_to_end(); ++ ++ static Method* cur_reordering_method(); ++ static void set_cur_reordering_method(Method* method); ++ static int inc_reorderable_method_cnt(); ++ ++ // Used in reordering phase. ++ static CompileTask* create_a_task_instance(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS); ++ static void check_compiled_result(Method* method, int check_blob_type, TRAPS); ++ static bool enqueue_recompile_task(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS); ++ static bool recompile_one(CompileTaskInfo* cti, methodHandle& method, methodHandle& hot_method, TRAPS); ++ ++ static void print_code_heap(outputStream& ls, CodeHeap* heap, const char* name); ++public: ++ static void log_stacktrace(const JfrStackTrace &stacktrace); ++ static void construct_cg_once(); ++ static void dump_order_in_manual(); ++ static JBoltErrorCode dump_order_in_jcmd(const char* filename); ++ ++ static void check_arguments_not_set(); ++ static void init_phase1(); ++ static void init_phase2(TRAPS); ++ static void init_code_heaps(size_t non_nmethod_size, size_t profiled_size, size_t non_profiled_size, size_t cache_size, size_t ps, size_t alignment); ++ ++ static bool auto_mode() { return _auto_mode; } ++ ++ static bool reorder_phase_is_available(); ++ static bool reorder_phase_is_collecting(); ++ static bool reorder_phase_is_profiling(); ++ static bool reorder_phase_is_reordering(); ++ static bool reorder_phase_is_collecting_or_reordering(); ++ ++ static bool can_reorder_now(); ++ static bool should_reorder_now(); ++ ++ static int calc_code_blob_type(Method* method, CompileTask* task, TRAPS); ++ ++ static void check_start_reordering(TRAPS); ++ static void reorder_all_methods(TRAPS); ++ static void clear_structures(); ++ ++ static void print_code_heaps(); ++}; ++ ++#endif // SHARE_JBOLT_JBOLTMANAGER_HPP +diff --git a/src/hotspot/share/jbolt/jBoltUtils.cpp b/src/hotspot/share/jbolt/jBoltUtils.cpp +new file mode 100644 +index 000000000..e48d3b046 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltUtils.cpp +@@ -0,0 +1,38 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 "jbolt/jBoltUtils.hpp" ++ ++JBoltUtils::MetaDataKeepAliveMark::MetaDataKeepAliveMark(Thread* thread) : _thread(thread), _kept() { ++ assert(thread == Thread::current(), "Must be current thread"); ++ assert(_thread->is_in_live_stack((address)this), "not on stack?"); ++} ++ ++JBoltUtils::MetaDataKeepAliveMark::~MetaDataKeepAliveMark() { ++ for (int i = _kept.length() - 1; i >= 0; --i) { ++ Metadata* md = _kept.at(i); ++ int idx = _thread->metadata_handles()->find_from_end(md); ++ assert(idx != -1, "not in metadata_handles list"); ++ _thread->metadata_handles()->remove_at(idx); ++ } ++} +diff --git a/src/hotspot/share/jbolt/jBoltUtils.hpp b/src/hotspot/share/jbolt/jBoltUtils.hpp +new file mode 100644 +index 000000000..c2da35519 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltUtils.hpp +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTUTILS_HPP ++#define SHARE_JBOLT_JBOLTUTILS_HPP ++ ++#include "memory/allocation.hpp" ++#include "oops/metadata.hpp" ++#include "runtime/thread.hpp" ++#include "utilities/growableArray.hpp" ++ ++class JBoltUtils : public AllStatic { ++public: ++ /** ++ * Keep the metadata alive. ++ * ++ * @see KeepAliveRegistrar ++ * @see methodHandle ++ */ ++ class MetaDataKeepAliveMark : public StackObj { ++ private: ++ Thread* _thread; ++ GrowableArray _kept; ++ ++ public: ++ MetaDataKeepAliveMark(Thread* thread); ++ ~MetaDataKeepAliveMark(); ++ ++ void add(Metadata* md); ++ ++ const GrowableArray& kept() { return _kept; } ++ }; ++}; ++ ++#endif // SHARE_JBOLT_JBOLTUTILS_HPP +diff --git a/src/hotspot/share/jbolt/jBoltUtils.inline.hpp b/src/hotspot/share/jbolt/jBoltUtils.inline.hpp +new file mode 100644 +index 000000000..abd7501ca +--- /dev/null ++++ b/src/hotspot/share/jbolt/jBoltUtils.inline.hpp +@@ -0,0 +1,38 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLTUTILS_INLINE_HPP ++#define SHARE_JBOLT_JBOLTUTILS_INLINE_HPP ++ ++#include "jbolt/jBoltUtils.hpp" ++ ++// Register a metadata as 'in-use' by the thread. It's fine to register a ++// metadata multiple times (though perhaps inefficient). ++inline void JBoltUtils::MetaDataKeepAliveMark::add(Metadata* md) { ++ assert(md->is_valid(), "obj is valid"); ++ assert(_thread == Thread::current(), "thread must be current"); ++ _kept.push(md); ++ _thread->metadata_handles()->push(md); ++} ++ ++#endif // SHARE_JBOLT_JBOLTUTILS_INLINE_HPP +diff --git a/src/hotspot/share/jbolt/jbolt_globals.hpp b/src/hotspot/share/jbolt/jbolt_globals.hpp +new file mode 100644 +index 000000000..355e79672 +--- /dev/null ++++ b/src/hotspot/share/jbolt/jbolt_globals.hpp +@@ -0,0 +1,62 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOLT_JBOLT_GLOBALS_HPP ++#define SHARE_JBOLT_JBOLT_GLOBALS_HPP ++ ++#include "runtime/globals_shared.hpp" ++ ++#define JBOLT_FLAGS(develop, \ ++ develop_pd, \ ++ product, \ ++ product_pd, \ ++ notproduct, \ ++ range, \ ++ constraint) \ ++ \ ++ product(bool, UseJBolt, false, EXPERIMENTAL, \ ++ "Enable JBolt feature.") \ ++ \ ++ product(bool, JBoltDumpMode, false, EXPERIMENTAL, \ ++ "Trial run of JBolt. Collect profiling and dump it.") \ ++ \ ++ product(bool, JBoltLoadMode, false, EXPERIMENTAL, \ ++ "Second run of JBolt. Load the profiling and reorder nmethods.") \ ++ \ ++ product(ccstr, JBoltOrderFile, NULL, EXPERIMENTAL, \ ++ "The JBolt method order file to dump or load.") \ ++ \ ++ product(intx, JBoltSampleInterval, 600, EXPERIMENTAL, \ ++ "Sample interval(second) of JBolt dump mode" \ ++ "only useful in auto mode.") \ ++ range(0, max_jint) \ ++ \ ++ product(uintx, JBoltCodeHeapSize, 8*M , EXPERIMENTAL, \ ++ "Code heap size of MethodJBoltHot and MethodJBoltTmp heaps.") \ ++ \ ++ ++// end of JBOLT_FLAGS ++ ++DECLARE_FLAGS(JBOLT_FLAGS) ++ ++#endif // SHARE_JBOLT_JBOLT_GLOBALS_HPP +diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml +index 2b3cc8812..4dae6949b 100644 +--- a/src/hotspot/share/jfr/metadata/metadata.xml ++++ b/src/hotspot/share/jfr/metadata/metadata.xml +@@ -930,6 +930,8 @@ + + + ++ ++ + + + +diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +index 905f1e53a..51b06efd8 100644 +--- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp ++++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +@@ -72,6 +72,9 @@ + #if INCLUDE_SHENANDOAHGC + #include "gc/shenandoah/shenandoahJfrSupport.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jbolt_globals.hpp" ++#endif // INCLUDE_JBOLT + /** + * JfrPeriodic class + * Implementation of declarations in +@@ -610,6 +613,8 @@ TRACE_REQUEST_FUNC(CodeCacheConfiguration) { + event.set_nonNMethodSize(NonNMethodCodeHeapSize); + event.set_profiledSize(ProfiledCodeHeapSize); + event.set_nonProfiledSize(NonProfiledCodeHeapSize); ++ event.set_jboltHotSize(JBoltCodeHeapSize); ++ event.set_jboltTmpSize(JBoltCodeHeapSize); + event.set_expansionSize(CodeCacheExpansionSize); + event.set_minBlockLength(CodeCacheMinBlockLength); + event.set_startAddress((u8)CodeCache::low_bound()); +diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp +index a95cccf1a..736fdddb8 100644 +--- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp ++++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp +@@ -40,6 +40,9 @@ + #include "runtime/semaphore.hpp" + #include "runtime/thread.inline.hpp" + #include "runtime/threadSMR.hpp" ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif + + enum JfrSampleType { + NO_SAMPLE = 0, +@@ -262,7 +265,13 @@ bool JfrThreadSampleClosure::sample_thread_in_java(JavaThread* thread, JfrStackF + return false; + } + EventExecutionSample *event = &_events[_added_java - 1]; +- traceid id = JfrStackTraceRepository::add(sampler.stacktrace()); ++ traceid id = 0; ++#if INCLUDE_JBOLT ++ if (UseJBolt && JBoltManager::reorder_phase_is_profiling()) { ++ id = JfrStackTraceRepository::add_jbolt(sampler.stacktrace()); ++ } else ++#endif ++ id = JfrStackTraceRepository::add(sampler.stacktrace()); + assert(id != 0, "Stacktrace id should not be 0"); + event->set_stackTrace(id); + return true; +diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp +index ab5fa276c..dc832517b 100644 +--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp ++++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.cpp +@@ -57,7 +57,11 @@ JfrStackTrace::JfrStackTrace(JfrStackFrame* frames, u4 max_frames) : + _frames_ownership(false), + _reached_root(false), + _lineno(false), +- _written(false) {} ++ _written(false) ++#if INCLUDE_JBOLT ++ , _hotcount(1) ++#endif ++ {} + + JfrStackTrace::JfrStackTrace(traceid id, const JfrStackTrace& trace, const JfrStackTrace* next) : + _next(next), +@@ -69,7 +73,11 @@ JfrStackTrace::JfrStackTrace(traceid id, const JfrStackTrace& trace, const JfrSt + _frames_ownership(true), + _reached_root(trace._reached_root), + _lineno(trace._lineno), +- _written(false) { ++ _written(false) ++#if INCLUDE_JBOLT ++ , _hotcount(trace._hotcount) ++#endif ++{ + copy_frames(&_frames, trace._nr_of_frames, trace._frames); + } + +diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp +index c3aabca40..53a82cc58 100644 +--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp ++++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTrace.hpp +@@ -52,6 +52,17 @@ class JfrStackFrame { + void write(JfrCheckpointWriter& cpw) const; + void resolve_lineno() const; + ++#if INCLUDE_JBOLT ++ const InstanceKlass* get_klass() const { return _klass; } ++ traceid get_methodId() const { return _methodid; } ++ int get_byteCodeIndex() const { return _bci; } ++ u1 get_type() const { return _type; } ++ ++ static ByteSize klass_offset() { return byte_offset_of(JfrStackFrame, _klass ); } ++ static ByteSize methodid_offset() { return byte_offset_of(JfrStackFrame, _methodid ); } ++ static ByteSize bci_offset() { return byte_offset_of(JfrStackFrame, _bci ); } ++ static ByteSize type_offset() { return byte_offset_of(JfrStackFrame, _type ); } ++#endif + enum { + FRAME_INTERPRETER = 0, + FRAME_JIT, +@@ -68,6 +79,10 @@ class JfrStackTrace : public JfrCHeapObj { + friend class ObjectSampler; + friend class OSThreadSampler; + friend class StackTraceResolver; ++#if INCLUDE_JBOLT ++ friend class JBoltManager; ++#endif ++ + private: + const JfrStackTrace* _next; + JfrStackFrame* _frames; +@@ -79,6 +94,9 @@ class JfrStackTrace : public JfrCHeapObj { + bool _reached_root; + mutable bool _lineno; + mutable bool _written; ++#if INCLUDE_JBOLT ++ u4 _hotcount; ++#endif + + const JfrStackTrace* next() const { return _next; } + +@@ -106,6 +124,17 @@ class JfrStackTrace : public JfrCHeapObj { + public: + unsigned int hash() const { return _hash; } + traceid id() const { return _id; } ++#if INCLUDE_JBOLT ++ u4 hotcount() const { return _hotcount; } ++ const JfrStackFrame* get_frames() const { return _frames; } ++ u4 get_framesCount() const { return _nr_of_frames; } ++ ++ static ByteSize hash_offset() { return byte_offset_of(JfrStackTrace, _hash ); } ++ static ByteSize id_offset() { return byte_offset_of(JfrStackTrace, _id ); } ++ static ByteSize hotcount_offset() { return byte_offset_of(JfrStackTrace, _hotcount ); } ++ static ByteSize frames_offset() { return byte_offset_of(JfrStackTrace, _frames ); } ++ static ByteSize frames_count_offset() { return byte_offset_of(JfrStackTrace, _nr_of_frames ); } ++#endif + }; + + #endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACE_HPP +diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp +index 1e940ef6f..93fce8bab 100644 +--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp ++++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp +@@ -29,6 +29,9 @@ + #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" + #include "jfr/support/jfrThreadLocal.hpp" + #include "runtime/mutexLocker.hpp" ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif + + /* + * There are two separate repository instances. +@@ -51,9 +54,16 @@ static JfrStackTraceRepository& leak_profiler_instance() { + return *_leak_profiler_instance; + } + ++#if INCLUDE_JBOLT ++JfrStackTraceRepository::JfrStackTraceRepository() : _last_entries(0), _entries(0), _last_entries_jbolt(0), _entries_jbolt(0) { ++ memset(_table, 0, sizeof(_table)); ++ memset(_table_jbolt, 0, sizeof(_table_jbolt)); ++} ++#else + JfrStackTraceRepository::JfrStackTraceRepository() : _last_entries(0), _entries(0) { + memset(_table, 0, sizeof(_table)); + } ++#endif + + JfrStackTraceRepository* JfrStackTraceRepository::create() { + assert(_instance == NULL, "invariant"); +@@ -98,10 +108,16 @@ bool JfrStackTraceRepository::is_modified() const { + } + + size_t JfrStackTraceRepository::write(JfrChunkWriter& sw, bool clear) { ++#if INCLUDE_JBOLT ++ if (clear && (UseJBolt && JBoltManager::reorder_phase_is_profiling())) { ++ JBoltManager::construct_cg_once(); ++ } ++#endif + MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag); + if (_entries == 0) { + return 0; + } ++ + int count = 0; + for (u4 i = 0; i < TABLE_SIZE; ++i) { + JfrStackTrace* stacktrace = _table[i]; +@@ -120,6 +136,21 @@ size_t JfrStackTraceRepository::write(JfrChunkWriter& sw, bool clear) { + if (clear) { + memset(_table, 0, sizeof(_table)); + _entries = 0; ++#if INCLUDE_JBOLT ++ for (u4 i = 0; i < TABLE_SIZE; ++i) { ++ JfrStackTrace* stacktrace = _table_jbolt[i]; ++ while (stacktrace != NULL) { ++ JfrStackTrace* next = const_cast(stacktrace->next()); ++ delete stacktrace; ++ stacktrace = next; ++ } ++ } ++ memset(_table_jbolt, 0, sizeof(_table_jbolt)); ++ _entries_jbolt = 0; ++ } ++ _last_entries_jbolt = _entries_jbolt; ++ { ++#endif + } + _last_entries = _entries; + return count; +@@ -142,6 +173,21 @@ size_t JfrStackTraceRepository::clear(JfrStackTraceRepository& repo) { + const size_t processed = repo._entries; + repo._entries = 0; + repo._last_entries = 0; ++#if INCLUDE_JBOLT ++ if (repo._entries_jbolt != 0) { ++ for (u4 i = 0; i < TABLE_SIZE; ++i) { ++ JfrStackTrace* stacktrace = repo._table_jbolt[i]; ++ while (stacktrace != NULL) { ++ JfrStackTrace* next = const_cast(stacktrace->next()); ++ delete stacktrace; ++ stacktrace = next; ++ } ++ } ++ memset(repo._table_jbolt, 0, sizeof(repo._table_jbolt)); ++ repo._entries_jbolt = 0; ++ repo._last_entries_jbolt = 0; ++ } ++#endif + return processed; + } + +@@ -231,6 +277,75 @@ const JfrStackTrace* JfrStackTraceRepository::lookup_for_leak_profiler(unsigned + return trace; + } + ++#if INCLUDE_JBOLT ++size_t JfrStackTraceRepository::clear_jbolt(JfrStackTraceRepository& repo) { ++ MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag); ++ if (repo._entries_jbolt == 0) { ++ return 0; ++ } ++ ++ for (u4 i = 0; i < TABLE_SIZE; ++i) { ++ JfrStackTrace* stacktrace = repo._table_jbolt[i]; ++ while (stacktrace != NULL) { ++ JfrStackTrace* next = const_cast(stacktrace->next()); ++ delete stacktrace; ++ stacktrace = next; ++ } ++ } ++ memset(repo._table_jbolt, 0, sizeof(repo._table_jbolt)); ++ const size_t processed = repo._entries; ++ repo._entries_jbolt = 0; ++ repo._last_entries_jbolt = 0; ++ ++ return processed; ++} ++ ++size_t JfrStackTraceRepository::clear_jbolt() { ++ clear_jbolt(leak_profiler_instance()); ++ return clear_jbolt(instance()); ++} ++ ++traceid JfrStackTraceRepository::add_jbolt(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace) { ++ traceid tid = repo.add_trace_jbolt(stacktrace); ++ if (tid == 0) { ++ stacktrace.resolve_linenos(); ++ tid = repo.add_trace_jbolt(stacktrace); ++ } ++ assert(tid != 0, "invariant"); ++ return tid; ++} ++ ++traceid JfrStackTraceRepository::add_jbolt(const JfrStackTrace& stacktrace) { ++ JBoltManager::log_stacktrace(stacktrace); ++ return add_jbolt(instance(), stacktrace); ++} ++ ++traceid JfrStackTraceRepository::add_trace_jbolt(const JfrStackTrace& stacktrace) { ++ traceid id = add_trace(stacktrace); ++ MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag); ++ const size_t index = stacktrace._hash % TABLE_SIZE; ++ ++ if (UseJBolt && JBoltManager::reorder_phase_is_profiling()) { ++ const JfrStackTrace* table_jbolt_entry = _table_jbolt[index]; ++ while (table_jbolt_entry != NULL) { ++ if (table_jbolt_entry->equals(stacktrace)) { ++ // [jbolt]: each time add an old trace, inc its hotcount ++ const_cast(table_jbolt_entry)->_hotcount++; ++ return table_jbolt_entry->id(); ++ } ++ table_jbolt_entry = table_jbolt_entry->next(); ++ } ++ } ++ ++ if (id != 0 && UseJBolt && JBoltManager::reorder_phase_is_profiling()) { ++ _table_jbolt[index] = new JfrStackTrace(id, stacktrace, _table_jbolt[index]); ++ ++_entries_jbolt; ++ } ++ ++ return id; ++} ++#endif ++ + void JfrStackTraceRepository::clear_leak_profiler() { + clear(leak_profiler_instance()); + } +diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp +index 92ac39c15..d6d65593b 100644 +--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp ++++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp +@@ -42,6 +42,9 @@ class JfrStackTraceRepository : public JfrCHeapObj { + friend class RecordStackTrace; + friend class StackTraceBlobInstaller; + friend class StackTraceRepository; ++#if INCLUDE_JBOLT ++ friend class JBoltManager; ++#endif + + private: + static const u4 TABLE_SIZE = 2053; +@@ -49,6 +52,19 @@ class JfrStackTraceRepository : public JfrCHeapObj { + u4 _last_entries; + u4 _entries; + ++#if INCLUDE_JBOLT ++ // [jbolt]: an exclusive table for jbolt. It should be a subset of _table ++ JfrStackTrace* _table_jbolt[TABLE_SIZE]; ++ u4 _last_entries_jbolt; ++ u4 _entries_jbolt; ++ ++ static size_t clear_jbolt(); ++ static size_t clear_jbolt(JfrStackTraceRepository& repo); ++ traceid add_trace_jbolt(const JfrStackTrace& stacktrace); ++ static traceid add_jbolt(JfrStackTraceRepository& repo, const JfrStackTrace& stacktrace); ++ static traceid add_jbolt(const JfrStackTrace& stacktrace); ++#endif ++ + JfrStackTraceRepository(); + static JfrStackTraceRepository& instance(); + static JfrStackTraceRepository* create(); +@@ -71,6 +87,13 @@ class JfrStackTraceRepository : public JfrCHeapObj { + + public: + static traceid record(Thread* thread, int skip = 0); ++#if INCLUDE_JBOLT ++ const JfrStackTrace* const * get_stacktrace_table() const { return _table; } ++ u4 get_entries_count() const { return _entries; } ++ ++ const JfrStackTrace* const * get_stacktrace_table_jbolt() const { return _table_jbolt; } ++ u4 get_entries_count_jbolt() const { return _entries_jbolt; } ++#endif + }; + + #endif // SHARE_JFR_RECORDER_STACKTRACE_JFRSTACKTRACEREPOSITORY_HPP +diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp +index 8ac082bf1..2e4352592 100644 +--- a/src/hotspot/share/logging/logTag.hpp ++++ b/src/hotspot/share/logging/logTag.hpp +@@ -93,6 +93,7 @@ + LOG_TAG(install) \ + LOG_TAG(interpreter) \ + LOG_TAG(itables) \ ++ JBOLT_ONLY(LOG_TAG(jbolt)) \ + JBOOSTER_ONLY(LOG_TAG(jbooster)) \ + LOG_TAG(jfr) \ + LOG_TAG(jit) \ +diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp +index b91e61a21..e8d681845 100644 +--- a/src/hotspot/share/opto/doCall.cpp ++++ b/src/hotspot/share/opto/doCall.cpp +@@ -1049,8 +1049,8 @@ void Parse::catch_inline_exceptions(SafePointNode* ex_map) { + + #ifndef PRODUCT + void Parse::count_compiled_calls(bool at_method_entry, bool is_inline) { +- if( CountCompiledCalls ) { +- if( at_method_entry ) { ++ if(CountCompiledCalls) { ++ if(at_method_entry) { + // bump invocation counter if top method (for statistics) + if (CountCompiledCalls && depth() == 1) { + const TypePtr* addr_type = TypeMetadataPtr::make(method()); +diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp +index 8ce2bdeb7..2635a4531 100644 +--- a/src/hotspot/share/opto/parse1.cpp ++++ b/src/hotspot/share/opto/parse1.cpp +@@ -1189,7 +1189,7 @@ void Parse::do_method_entry() { + set_parse_bci(InvocationEntryBci); // Pseudo-BCP + set_sp(0); // Java Stack Pointer + +- NOT_PRODUCT( count_compiled_calls(true/*at_method_entry*/, false/*is_inline*/); ) ++ NOT_PRODUCT(count_compiled_calls(true/* at_method_entry */, false/* is_inline */);) + + if (C->env()->dtrace_method_probes()) { + make_dtrace_method_entry(method()); +diff --git a/src/hotspot/share/runtime/flags/allFlags.hpp b/src/hotspot/share/runtime/flags/allFlags.hpp +index b0e7465cb..4768fff12 100644 +--- a/src/hotspot/share/runtime/flags/allFlags.hpp ++++ b/src/hotspot/share/runtime/flags/allFlags.hpp +@@ -30,6 +30,9 @@ + #include "gc/shared/tlab_globals.hpp" + #include "runtime/flags/debug_globals.hpp" + #include "runtime/globals.hpp" ++#if INCLUDE_JBOLT ++#include "jbolt/jbolt_globals.hpp" ++#endif // INCLUDE_JBOLT + #if INCLUDE_JBOOSTER + #include "jbooster/jbooster_globals.hpp" + #endif // INCLUDE_JBOOSTER +@@ -143,6 +146,16 @@ + range, \ + constraint) \ + \ ++ JBOLT_ONLY( \ ++ JBOLT_FLAGS( \ ++ develop, \ ++ develop_pd, \ ++ product, \ ++ product_pd, \ ++ notproduct, \ ++ range, \ ++ constraint)) \ ++ \ + JBOOSTER_ONLY( \ + JBOOSTER_FLAGS( \ + develop, \ +diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp +index f57a95533..43da20b3f 100644 +--- a/src/hotspot/share/runtime/java.cpp ++++ b/src/hotspot/share/runtime/java.cpp +@@ -100,6 +100,9 @@ + #if INCLUDE_AOT + #include "aot/aotLoader.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltManager.hpp" ++#endif + + GrowableArray* collected_profiled_methods; + +@@ -544,6 +547,12 @@ void before_exit(JavaThread* thread, bool halt) { + } + #endif // INCLUDE_JBOOSTER + ++#if INCLUDE_JBOLT ++ if (UseJBolt && JBoltDumpMode) { ++ JBoltManager::dump_order_in_manual(); ++ } ++#endif ++ + print_statistics(); + Universe::heap()->print_tracing_info(); + +diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp +index 470615601..1970a23b5 100644 +--- a/src/hotspot/share/runtime/thread.cpp ++++ b/src/hotspot/share/runtime/thread.cpp +@@ -155,6 +155,10 @@ + #if INCLUDE_AOT + #include "aot/aotLoader.hpp" + #endif ++#if INCLUDE_JBOLT ++#include "jbolt/jBoltDcmds.hpp" ++#include "jbolt/jBoltManager.hpp" ++#endif // INCLUDE_JBOLT + + // Initialization after module runtime initialization + void universe_post_module_init(); // must happen after call_initPhase2 +@@ -2911,6 +2915,14 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { + ObjectMonitor::Initialize(); + ObjectSynchronizer::initialize(); + ++#if INCLUDE_JBOLT ++ if (UseJBolt) { ++ JBoltManager::init_phase1(); ++ } else { ++ JBoltManager::check_arguments_not_set(); ++ } ++#endif // INCLUDE_JBOLT ++ + // Initialize global modules + jint status = init_globals(); + if (status != JNI_OK) { +@@ -3166,6 +3178,13 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { + } + #endif // INCLUDE_JBOOSTER + ++#if INCLUDE_JBOLT ++ register_jbolt_dcmds(); ++ if (UseJBolt) { ++ JBoltManager::init_phase2(CATCH); ++ } ++#endif // INCLUDE_JBOLT ++ + return JNI_OK; + } + +diff --git a/src/hotspot/share/utilities/growableArray.hpp b/src/hotspot/share/utilities/growableArray.hpp +index fa078a349..a643ccc8c 100644 +--- a/src/hotspot/share/utilities/growableArray.hpp ++++ b/src/hotspot/share/utilities/growableArray.hpp +@@ -129,7 +129,7 @@ protected: + + public: + const static GrowableArrayView EMPTY; +-#if INCLUDE_JBOOSTER ++#if INCLUDE_JBOOSTER || INCLUDE_JBOLT + static ByteSize data_offset() { return byte_offset_of(GrowableArrayView, _data); } + #endif // INCLUDE_JBOOSTER + +diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp +index c25da52ed..729dec122 100644 +--- a/src/hotspot/share/utilities/macros.hpp ++++ b/src/hotspot/share/utilities/macros.hpp +@@ -119,6 +119,18 @@ + #define NOT_CDS_RETURN_(code) { return code; } + #endif // INCLUDE_CDS + ++#ifndef INCLUDE_JBOLT ++#define INCLUDE_JBOLT 1 ++#endif ++ ++#if INCLUDE_JBOLT ++#define JBOLT_ONLY(x) x ++#define NOT_JBOLT(x) ++#else ++#define JBOLT_ONLY(x) ++#define NOT_JBOLT(x) x ++#endif // INCLUDE_JBOLT ++ + #ifndef INCLUDE_JBOOSTER + #define INCLUDE_JBOOSTER 1 + #endif +diff --git a/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheCLITestCase.java b/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheCLITestCase.java +index eca5c70e0..39f633361 100644 +--- a/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheCLITestCase.java ++++ b/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheCLITestCase.java +@@ -68,12 +68,12 @@ public class CodeCacheCLITestCase { + * Verifies that in interpreted mode PrintCodeCache output contains + * the whole code cache. Int mode disables SegmentedCodeCache with a warning. + */ +- INT_MODE(ONLY_SEGMENTED, EnumSet.of(BlobType.All), USE_INT_MODE), ++ INT_MODE(ONLY_SEGMENTED, EnumSet.copyOf(CodeCacheOptions.NON_SEGMENTED_HEAPS), USE_INT_MODE), + /** + * Verifies that with disabled SegmentedCodeCache PrintCodeCache output + * contains only CodeCache's entry. + */ +- NON_SEGMENTED(options -> !options.segmented, EnumSet.of(BlobType.All), ++ NON_SEGMENTED(options -> !options.segmented, EnumSet.copyOf(CodeCacheOptions.NON_SEGMENTED_HEAPS), + CommandLineOptionTest.prepareBooleanFlag(SEGMENTED_CODE_CACHE, + false)), + /** +@@ -82,7 +82,7 @@ public class CodeCacheCLITestCase { + * profiled-nmethods heap and non-segmented CodeCache. + */ + NON_TIERED(ONLY_SEGMENTED, +- EnumSet.of(BlobType.NonNMethod, BlobType.MethodNonProfiled), ++ EnumSet.copyOf(CodeCacheOptions.SEGMENTED_HEAPS_WO_PROFILED), + CommandLineOptionTest.prepareBooleanFlag(TIERED_COMPILATION, + false)), + /** +@@ -91,7 +91,7 @@ public class CodeCacheCLITestCase { + * heaps only. + */ + TIERED_LEVEL_0(SEGMENTED_SERVER, +- EnumSet.of(BlobType.All), ++ EnumSet.copyOf(CodeCacheOptions.NON_SEGMENTED_HEAPS), + CommandLineOptionTest.prepareBooleanFlag(TIERED_COMPILATION, + true), + CommandLineOptionTest.prepareNumericFlag(TIERED_STOP_AT, 0)), +@@ -101,7 +101,7 @@ public class CodeCacheCLITestCase { + * heaps only. + */ + TIERED_LEVEL_1(SEGMENTED_SERVER, +- EnumSet.of(BlobType.NonNMethod, BlobType.MethodNonProfiled), ++ EnumSet.copyOf(CodeCacheOptions.SEGMENTED_HEAPS_WO_PROFILED), + CommandLineOptionTest.prepareBooleanFlag(TIERED_COMPILATION, + true), + CommandLineOptionTest.prepareNumericFlag(TIERED_STOP_AT, 1)), +@@ -110,7 +110,7 @@ public class CodeCacheCLITestCase { + * contain information about all three code heaps. + */ + TIERED_LEVEL_4(SEGMENTED_SERVER, +- EnumSet.complementOf(EnumSet.of(BlobType.All)), ++ EnumSet.copyOf(CodeCacheOptions.ALL_SEGMENTED_HEAPS), + CommandLineOptionTest.prepareBooleanFlag(TIERED_COMPILATION, + true), + CommandLineOptionTest.prepareNumericFlag(TIERED_STOP_AT, 4)); +diff --git a/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheOptions.java b/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheOptions.java +index f5243aaa4..1830911a9 100644 +--- a/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheOptions.java ++++ b/test/hotspot/jtreg/compiler/codecache/cli/common/CodeCacheOptions.java +@@ -33,20 +33,27 @@ import java.util.List; + public class CodeCacheOptions { + public static final String SEGMENTED_CODE_CACHE = "SegmentedCodeCache"; + +- private static final EnumSet NON_SEGMENTED_HEAPS ++ public static final EnumSet NON_SEGMENTED_HEAPS + = EnumSet.of(BlobType.All); +- private static final EnumSet ALL_SEGMENTED_HEAPS +- = EnumSet.complementOf(NON_SEGMENTED_HEAPS); +- private static final EnumSet SEGMENTED_HEAPS_WO_PROFILED ++ public static final EnumSet JBOLT_HEAPS ++ = EnumSet.of(BlobType.MethodJBoltHot, BlobType.MethodJBoltTmp); ++ public static final EnumSet ALL_SEGMENTED_HEAPS ++ = EnumSet.complementOf(union(NON_SEGMENTED_HEAPS, JBOLT_HEAPS)); ++ public static final EnumSet ALL_SEGMENTED_HEAPS_WITH_JBOLT ++ = union(ALL_SEGMENTED_HEAPS, JBOLT_HEAPS); ++ public static final EnumSet SEGMENTED_HEAPS_WO_PROFILED + = EnumSet.of(BlobType.NonNMethod, BlobType.MethodNonProfiled); +- private static final EnumSet ONLY_NON_METHODS_HEAP ++ public static final EnumSet ONLY_NON_METHODS_HEAP + = EnumSet.of(BlobType.NonNMethod); + + public final long reserved; + public final long nonNmethods; + public final long nonProfiled; + public final long profiled; ++ public final long jboltHot; ++ public final long jboltTmp; + public final boolean segmented; ++ public final boolean useJBolt; + + public static long mB(long val) { + return CodeCacheOptions.kB(val) * 1024L; +@@ -56,12 +63,21 @@ public class CodeCacheOptions { + return val * 1024L; + } + ++ public static > EnumSet union(EnumSet e1, EnumSet e2) { ++ EnumSet res = EnumSet.copyOf(e1); ++ res.addAll(e2); ++ return res; ++ } ++ + public CodeCacheOptions(long reserved) { + this.reserved = reserved; + this.nonNmethods = 0; + this.nonProfiled = 0; + this.profiled = 0; ++ this.jboltHot = 0; ++ this.jboltTmp = 0; + this.segmented = false; ++ this.useJBolt = false; + } + + public CodeCacheOptions(long reserved, long nonNmethods, long nonProfiled, +@@ -70,7 +86,25 @@ public class CodeCacheOptions { + this.nonNmethods = nonNmethods; + this.nonProfiled = nonProfiled; + this.profiled = profiled; ++ this.jboltHot = 0; ++ this.jboltTmp = 0; + this.segmented = true; ++ this.useJBolt = false; ++ } ++ ++ /** ++ * No tests for JBolt yet as the related VM options are experimental now. ++ */ ++ public CodeCacheOptions(long reserved, long nonNmethods, long nonProfiled, ++ long profiled, long jboltHot, long jboltTmp) { ++ this.reserved = reserved; ++ this.nonNmethods = nonNmethods; ++ this.nonProfiled = nonProfiled; ++ this.profiled = profiled; ++ this.jboltHot = jboltHot; ++ this.jboltTmp = jboltTmp; ++ this.segmented = true; ++ this.useJBolt = true; + } + + public long sizeForHeap(BlobType heap) { +@@ -83,6 +117,10 @@ public class CodeCacheOptions { + return this.nonProfiled; + case MethodProfiled: + return this.profiled; ++ case MethodJBoltHot: ++ return this.jboltHot; ++ case MethodJBoltTmp: ++ return this.jboltTmp; + default: + throw new Error("Unknown heap: " + heap.name()); + } +@@ -107,14 +145,26 @@ public class CodeCacheOptions { + CommandLineOptionTest.prepareNumericFlag( + BlobType.MethodProfiled.sizeOptionName, profiled)); + } ++ ++ if (useJBolt) { ++ Collections.addAll(options, ++ CommandLineOptionTest.prepareNumericFlag( ++ BlobType.MethodJBoltHot.sizeOptionName, jboltHot), ++ CommandLineOptionTest.prepareNumericFlag( ++ BlobType.MethodJBoltTmp.sizeOptionName, jboltTmp)); ++ } ++ + return options.toArray(new String[options.size()]); + } + + public CodeCacheOptions mapOptions(EnumSet involvedCodeHeaps) { + if (involvedCodeHeaps.isEmpty() + || involvedCodeHeaps.equals(NON_SEGMENTED_HEAPS) +- || involvedCodeHeaps.equals(ALL_SEGMENTED_HEAPS)) { ++ || involvedCodeHeaps.equals(ALL_SEGMENTED_HEAPS_WITH_JBOLT)) { + return this; ++ } else if (involvedCodeHeaps.equals(ALL_SEGMENTED_HEAPS)) { ++ return new CodeCacheOptions(reserved, nonNmethods, ++ nonProfiled + jboltHot + jboltTmp, profiled); + } else if (involvedCodeHeaps.equals(SEGMENTED_HEAPS_WO_PROFILED)) { + return new CodeCacheOptions(reserved, nonNmethods, + profiled + nonProfiled, 0L); +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltDumpModeTest.java b/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltDumpModeTest.java +new file mode 100644 +index 000000000..b5bdb19bc +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltDumpModeTest.java +@@ -0,0 +1,187 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 JBolt dump mode functions. ++ * @library /test/lib ++ * @requires vm.flagless ++ * ++ * @run driver compiler.codecache.jbolt.JBoltDumpModeTest ++ */ ++ ++package compiler.codecache.jbolt; ++ ++import java.io.File; ++import java.io.IOException; ++import jdk.test.lib.process.OutputAnalyzer; ++import jdk.test.lib.process.ProcessTools; ++import jdk.test.lib.Utils; ++ ++public class JBoltDumpModeTest { ++ public static final String SRC_DIR = Utils.TEST_SRC; ++ public static final String ORDER_FILE = SRC_DIR + "/order.log"; ++ ++ private static void createOrderFile() { ++ try { ++ File order = new File(ORDER_FILE); ++ if (!order.exists()) { ++ order.createNewFile(); ++ } ++ } ++ catch (IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ private static void clearOrderFile() { ++ File order = new File(ORDER_FILE); ++ if (order.exists()) { ++ order.delete(); ++ } ++ } ++ ++ private static void OrderFileShouldExist() throws Exception { ++ File order = new File(ORDER_FILE); ++ if (order.exists()) { ++ order.delete(); ++ } ++ else { ++ throw new RuntimeException(ORDER_FILE + " doesn't exist as expect."); ++ } ++ } ++ ++ private static void OrderFileShouldNotExist() throws Exception { ++ File order = new File(ORDER_FILE); ++ if (order.exists()) { ++ throw new RuntimeException(ORDER_FILE + " exists while expect not."); ++ } ++ } ++ ++ private static void testNormalUse() throws Exception { ++ ProcessBuilder pb1 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + ORDER_FILE, ++ "-XX:+JBoltDumpMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ ProcessBuilder pb2 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + ORDER_FILE, ++ "-XX:+JBoltDumpMode", ++ "-XX:StartFlightRecording", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ ProcessBuilder pb3 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + ORDER_FILE, ++ "-XX:+JBoltDumpMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ clearOrderFile(); ++ ++ String stdout; ++ ++ OutputAnalyzer out1 = new OutputAnalyzer(pb1.start()); ++ stdout = out1.getStdout(); ++ if (!stdout.contains("JBolt in dump mode now, start a JFR recording named \"jbolt-jfr\".")) { ++ throw new RuntimeException(stdout); ++ } ++ out1.shouldHaveExitValue(0); ++ OrderFileShouldExist(); ++ ++ OutputAnalyzer out2 = new OutputAnalyzer(pb2.start()); ++ stdout = out2.getStdout(); ++ if (!stdout.contains("JBolt in dump mode now, start a JFR recording named \"jbolt-jfr\".")) { ++ throw new RuntimeException(stdout); ++ } ++ out2.shouldHaveExitValue(0); ++ OrderFileShouldExist(); ++ ++ createOrderFile(); ++ OutputAnalyzer out3 = new OutputAnalyzer(pb3.start()); ++ stdout = out3.getStdout(); ++ if (!stdout.contains("JBoltOrderFile to dump already exists and will be overwritten:")) { ++ throw new RuntimeException(stdout); ++ } ++ out3.shouldHaveExitValue(0); ++ OrderFileShouldExist(); ++ } ++ ++ private static void testUnabletoRun() throws Exception { ++ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + ORDER_FILE, ++ "-XX:+JBoltDumpMode", ++ "-XX:-FlightRecorder", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ clearOrderFile(); ++ ++ String stdout; ++ OutputAnalyzer out = new OutputAnalyzer(pb.start()); ++ ++ stdout = out.getStdout(); ++ if (!stdout.contains("JBolt depends on JFR!")) { ++ throw new RuntimeException(stdout); ++ } ++ OrderFileShouldNotExist(); ++ } ++ ++ private static void testFatalError() throws Exception { ++ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + ORDER_FILE, ++ "-XX:+JBoltDumpMode", ++ "-XX:foo", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ clearOrderFile(); ++ ++ OutputAnalyzer out = new OutputAnalyzer(pb.start()); ++ ++ out.stderrShouldContain("Could not create the Java Virtual Machine"); ++ OrderFileShouldNotExist(); ++ } ++ ++ public static void main(String[] args) throws Exception { ++ testNormalUse(); ++ testUnabletoRun(); ++ testFatalError(); ++ } ++} +\ No newline at end of file +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltVMOptionsTest.java b/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltVMOptionsTest.java +new file mode 100644 +index 000000000..4b45a585b +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/JBoltVMOptionsTest.java +@@ -0,0 +1,291 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 JBolt VM options. ++ * @library /test/lib ++ * @requires vm.flagless ++ * ++ * @run driver compiler.codecache.jbolt.JBoltVMOptionsTest ++ */ ++ ++package compiler.codecache.jbolt; ++ ++import java.io.File; ++import jdk.test.lib.process.OutputAnalyzer; ++import jdk.test.lib.process.ProcessTools; ++import jdk.test.lib.Utils; ++ ++public class JBoltVMOptionsTest { ++ public static final String SRC_DIR = Utils.TEST_SRC; ++ public static final String TEMP_FILE = SRC_DIR + "/tmp.log"; ++ ++ public static void main(String[] args) throws Exception { ++ test1(); ++ test2(); ++ test3(); ++ test4(); ++ } ++ ++ private static void clearTmpFile() { ++ File tmp = new File(TEMP_FILE); ++ if (tmp.exists()) { ++ tmp.delete(); ++ } ++ } ++ ++ private static void test1() throws Exception { ++ ProcessBuilder pb1 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltDumpMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb2 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb3 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:+JBoltDumpMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o1.log", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb4 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:JBoltOrderFile=" + TEMP_FILE, ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ OutputAnalyzer out1 = new OutputAnalyzer(pb1.start()); ++ OutputAnalyzer out2 = new OutputAnalyzer(pb2.start()); ++ OutputAnalyzer out3 = new OutputAnalyzer(pb3.start()); ++ OutputAnalyzer out4 = new OutputAnalyzer(pb4.start()); ++ ++ String stdout; ++ ++ stdout = out1.getStdout(); ++ if (!stdout.contains("JBoltOrderFile is not set!")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out2.getStdout(); ++ if (!stdout.contains("JBoltOrderFile is not set!")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out3.getStdout(); ++ if (!stdout.contains("Do not set both JBoltDumpMode and JBoltLoadMode!")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out4.getStdout(); ++ if (!stdout.contains("JBoltOrderFile is ignored because it is in auto mode.")) { ++ throw new RuntimeException(stdout); ++ } ++ } ++ ++ private static void test2() throws Exception { ++ ProcessBuilder pb1 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+PrintFlagsFinal", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb2 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltDumpMode", ++ "-XX:JBoltOrderFile=" + TEMP_FILE, ++ "-XX:+PrintFlagsFinal", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb3 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o1.log", ++ "-XX:+PrintFlagsFinal", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ OutputAnalyzer out1 = new OutputAnalyzer(pb1.start()); ++ OutputAnalyzer out2 = new OutputAnalyzer(pb2.start()); ++ OutputAnalyzer out3 = new OutputAnalyzer(pb3.start()); ++ ++ String stdout; ++ ++ stdout = out1.getStdout().replaceAll(" +", ""); ++ if (!stdout.contains("JBoltDumpMode=false") || !stdout.contains("JBoltLoadMode=false")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out2.getStdout().replaceAll(" +", ""); ++ if (!stdout.contains("JBoltDumpMode=true") || !stdout.contains("JBoltLoadMode=false")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ clearTmpFile(); ++ ++ stdout = out3.getStdout().replaceAll(" +", ""); ++ if (!stdout.contains("JBoltDumpMode=false") || !stdout.contains("JBoltLoadMode=true")) { ++ throw new RuntimeException(stdout); ++ } ++ } ++ ++ private static void test3() throws Exception { ++ ProcessBuilder pbF0 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + TEMP_FILE, ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pbF1 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o1.log", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pbF2 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o2.log", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pbF3 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o3.log", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pbF4 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+UseJBolt", ++ "-XX:+JBoltLoadMode", ++ "-XX:JBoltOrderFile=" + SRC_DIR + "/o4.log", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ OutputAnalyzer outF0 = new OutputAnalyzer(pbF0.start()); ++ OutputAnalyzer outF1 = new OutputAnalyzer(pbF1.start()); ++ OutputAnalyzer outF2 = new OutputAnalyzer(pbF2.start()); ++ OutputAnalyzer outF3 = new OutputAnalyzer(pbF3.start()); ++ OutputAnalyzer outF4 = new OutputAnalyzer(pbF4.start()); ++ ++ String stdout; ++ ++ stdout = outF0.getStdout(); ++ if (!stdout.contains("JBoltOrderFile does not exist or cannot be accessed!")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = outF1.getStdout(); ++ if (!stdout.contains("Wrong format of JBolt order line! line=\"X 123 aa bb cc\".")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = outF2.getStdout(); ++ if (!stdout.contains("Wrong format of JBolt order line! line=\"M aa/bb/C dd ()V\".")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = outF3.getStdout(); ++ if (!stdout.contains("Duplicated method: {aa/bb/CC dd ()V}!")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = outF4.getStdout(); ++ if (stdout.contains("Error occurred during initialization of VM")) { ++ throw new RuntimeException(stdout); ++ } ++ outF4.shouldHaveExitValue(0); ++ ++ clearTmpFile(); ++ } ++ ++ private static void test4() throws Exception { ++ ProcessBuilder pb1 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+JBoltDumpMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb2 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:+JBoltLoadMode", ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ProcessBuilder pb3 = ProcessTools.createJavaProcessBuilder( ++ "-XX:+UnlockExperimentalVMOptions", ++ "-XX:JBoltOrderFile=" + TEMP_FILE, ++ "-Xlog:jbolt*=trace", ++ "--version" ++ ); ++ ++ OutputAnalyzer out1 = new OutputAnalyzer(pb1.start()); ++ OutputAnalyzer out2 = new OutputAnalyzer(pb2.start()); ++ OutputAnalyzer out3 = new OutputAnalyzer(pb3.start()); ++ ++ String stdout; ++ ++ stdout = out1.getStdout(); ++ if (!stdout.contains("Do not set VM option JBoltDumpMode without UseJBolt enabled.")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out2.getStdout(); ++ if (!stdout.contains("Do not set VM option JBoltLoadMode without UseJBolt enabled.")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ stdout = out3.getStdout(); ++ if (!stdout.contains("Do not set VM option JBoltOrderFile without UseJBolt enabled.")) { ++ throw new RuntimeException(stdout); ++ } ++ ++ clearTmpFile(); ++ } ++} +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/o1.log b/test/hotspot/jtreg/compiler/codecache/jbolt/o1.log +new file mode 100644 +index 000000000..f0ef01586 +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/o1.log +@@ -0,0 +1,2 @@ ++M 123 aa/bb/C dd ()V ++X 123 aa bb cc +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/o2.log b/test/hotspot/jtreg/compiler/codecache/jbolt/o2.log +new file mode 100644 +index 000000000..ef348a6ab +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/o2.log +@@ -0,0 +1,2 @@ ++M aa/bb/C dd ()V ++M 123 aa/bb/CC dd ()V +\ No newline at end of file +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/o3.log b/test/hotspot/jtreg/compiler/codecache/jbolt/o3.log +new file mode 100644 +index 000000000..fe6906b47 +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/o3.log +@@ -0,0 +1,4 @@ ++# this is a comment ++C ++M 123 aa/bb/CC dd ()V ++M 123 aa/bb/CC dd ()V +\ No newline at end of file +diff --git a/test/hotspot/jtreg/compiler/codecache/jbolt/o4.log b/test/hotspot/jtreg/compiler/codecache/jbolt/o4.log +new file mode 100644 +index 000000000..13e96dbab +--- /dev/null ++++ b/test/hotspot/jtreg/compiler/codecache/jbolt/o4.log +@@ -0,0 +1,12 @@ ++M 123 aa/bb/CC dd ()V ++# asdfadsfadfs ++C ++M 456 aa/bb/CC ddd ()V ++M 456 aa/bb/CCC dd ()V ++ ++C ++ ++ ++ ++ ++M 456 aa/bb/CCCCCC ddddddd ()V +diff --git a/test/hotspot/jtreg/runtime/cds/appcds/ClassLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/ClassLoaderTest.java +index 4fba6584f..6f4cc5a83 100644 +--- a/test/hotspot/jtreg/runtime/cds/appcds/ClassLoaderTest.java ++++ b/test/hotspot/jtreg/runtime/cds/appcds/ClassLoaderTest.java +@@ -57,7 +57,7 @@ public class ClassLoaderTest { + String bootClassPath = "-Xbootclasspath/a:" + appJar + + File.pathSeparator + whiteBoxJar; + +- TestCommon.dump(appJar, appClasses, bootClassPath); ++ TestCommon.dump(appJar, appClasses, bootClassPath).shouldHaveExitValue(0); + + TestCommon.run( + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", +diff --git a/test/lib/jdk/test/whitebox/code/BlobType.java b/test/lib/jdk/test/whitebox/code/BlobType.java +index 24ce9d96a..59039bbbe 100644 +--- a/test/lib/jdk/test/whitebox/code/BlobType.java ++++ b/test/lib/jdk/test/whitebox/code/BlobType.java +@@ -46,8 +46,24 @@ public enum BlobType { + || type == BlobType.MethodNonProfiled; + } + }, ++ // Execution hot non-profiled nmethods ++ MethodJBoltHot(2, "CodeHeap 'jbolt hot nmethods'", "JBoltCodeHeapSize") { ++ @Override ++ public boolean allowTypeWhenOverflow(BlobType type) { ++ return super.allowTypeWhenOverflow(type) ++ || type == BlobType.MethodNonProfiled; ++ } ++ }, ++ // Execution tmp non-profiled nmethods ++ MethodJBoltTmp(3, "CodeHeap 'jbolt tmp nmethods'", "JBoltCodeHeapSize") { ++ @Override ++ public boolean allowTypeWhenOverflow(BlobType type) { ++ return super.allowTypeWhenOverflow(type) ++ || type == BlobType.MethodNonProfiled; ++ } ++ }, + // Non-nmethods like Buffers, Adapters and Runtime Stubs +- NonNMethod(2, "CodeHeap 'non-nmethods'", "NonNMethodCodeHeapSize") { ++ NonNMethod(4, "CodeHeap 'non-nmethods'", "NonNMethodCodeHeapSize") { + @Override + public boolean allowTypeWhenOverflow(BlobType type) { + return super.allowTypeWhenOverflow(type) +@@ -56,7 +72,7 @@ public enum BlobType { + } + }, + // All types (No code cache segmentation) +- All(3, "CodeCache", "ReservedCodeCacheSize"); ++ All(5, "CodeCache", "ReservedCodeCacheSize"); + + public final int id; + public final String sizeOptionName; +@@ -99,6 +115,10 @@ public enum BlobType { + // there is no MethodProfiled in non tiered world or pure C1 + result.remove(MethodProfiled); + } ++ if (!whiteBox.getBooleanVMFlag("UseJBolt") || whiteBox.getBooleanVMFlag("JBoltDumpMode")) { ++ result.remove(MethodJBoltHot); ++ result.remove(MethodJBoltTmp); ++ } + return result; + } + +-- +2.22.0 + diff --git a/Add-specialized-hashmap-version-of-the-long-type.patch b/Add-specialized-hashmap-version-of-the-long-type.patch new file mode 100644 index 0000000..946be52 --- /dev/null +++ b/Add-specialized-hashmap-version-of-the-long-type.patch @@ -0,0 +1,4917 @@ +From abd1e549e0849d6688dcb42e93fa808c528c3070 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:37:57 +0800 +Subject: Add specialized hashmap version of the long type + +--- + .../cpu/aarch64/vm_version_aarch64.hpp | 5 + + src/hotspot/share/cds/filemap.cpp | 4 +- + src/hotspot/share/cds/heapShared.hpp | 3 +- + src/hotspot/share/cds/metaspaceShared.cpp | 7 +- + src/hotspot/share/classfile/classLoader.cpp | 51 + + src/hotspot/share/classfile/classLoader.hpp | 3 + + .../classfile/systemDictionaryShared.cpp | 3 + + src/hotspot/share/oops/symbol.hpp | 4 + + src/hotspot/share/runtime/arguments.cpp | 7 + + src/hotspot/share/runtime/globals.hpp | 6 + + src/hotspot/share/runtime/java.cpp | 6 + + src/hotspot/share/runtime/java.hpp | 2 + + src/hotspot/share/runtime/vm_version.cpp | 8 + + test/jdk/java/util/HashMap/HashMap.java | 3799 +++++++++++++++++ + test/jdk/java/util/HashMap/LinkedHashMap.java | 798 ++++ + 15 files changed, 4700 insertions(+), 6 deletions(-) + create mode 100644 test/jdk/java/util/HashMap/HashMap.java + create mode 100644 test/jdk/java/util/HashMap/LinkedHashMap.java + +diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +index 46c77e48b..eac0db870 100644 +--- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp ++++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +@@ -142,6 +142,11 @@ public: + static int cpu_variant() { return _variant; } + static int cpu_revision() { return _revision; } + ++ static bool is_hisi_enabled() { ++ return (_cpu == CPU_HISILICON && ++ (_model == 0xd01 || _model == 0xd02 || _model == 0xd22 || _model == 0xd45)); ++ } ++ + static bool model_is(int cpu_model) { + return _model == cpu_model || _model2 == cpu_model; + } +diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp +index 76e078d12..13a9f22a9 100644 +--- a/src/hotspot/share/cds/filemap.cpp ++++ b/src/hotspot/share/cds/filemap.cpp +@@ -1807,8 +1807,8 @@ MemRegion FileMapInfo::get_heap_regions_range_with_current_oop_encoding_mode() { + // open archive objects. + void FileMapInfo::map_heap_regions_impl() { + if (!HeapShared::is_heap_object_archiving_allowed()) { +- log_info(cds)("CDS heap data is being ignored. UseG1GC, " +- "UseCompressedOops and UseCompressedClassPointers are required."); ++ log_info(cds)("CDS heap data is being ignored. UseG1GC, UseCompressedOops, " ++ "UseCompressedClassPointers and !UsePrimHashMap are required."); + return; + } + +diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp +index 74de74d6c..f7a1a004f 100644 +--- a/src/hotspot/share/cds/heapShared.hpp ++++ b/src/hotspot/share/cds/heapShared.hpp +@@ -352,7 +352,8 @@ private: + static void run_full_gc_in_vm_thread() NOT_CDS_JAVA_HEAP_RETURN; + + static bool is_heap_object_archiving_allowed() { +- CDS_JAVA_HEAP_ONLY(return (UseG1GC && UseCompressedOops && UseCompressedClassPointers);) ++ CDS_JAVA_HEAP_ONLY(return (UseG1GC && UseCompressedOops && ++ UseCompressedClassPointers && !UsePrimHashMap);) + NOT_CDS_JAVA_HEAP(return false;) + } + +diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp +index f4c8d0f81..748166480 100644 +--- a/src/hotspot/share/cds/metaspaceShared.cpp ++++ b/src/hotspot/share/cds/metaspaceShared.cpp +@@ -785,10 +785,11 @@ void VM_PopulateDumpSharedSpace::dump_java_heap_objects(GrowableArray* k + if(!HeapShared::is_heap_object_archiving_allowed()) { + log_info(cds)( + "Archived java heap is not supported as UseG1GC, " +- "UseCompressedOops and UseCompressedClassPointers are required." +- "Current settings: UseG1GC=%s, UseCompressedOops=%s, UseCompressedClassPointers=%s.", ++ "UseCompressedOops, UseCompressedClassPointers and !UsePrimHashMap are required." ++ "Current settings: UseG1GC=%s, UseCompressedOops=%s, " ++ "UseCompressedClassPointers=%s, UsePrimHashMap=%s", + BOOL_TO_STR(UseG1GC), BOOL_TO_STR(UseCompressedOops), +- BOOL_TO_STR(UseCompressedClassPointers)); ++ BOOL_TO_STR(UseCompressedClassPointers), BOOL_TO_STR(UsePrimHashMap)); + return; + } + // Find all the interned strings that should be dumped. +diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp +index 6e9b9bcdf..6480cd6f6 100644 +--- a/src/hotspot/share/classfile/classLoader.cpp ++++ b/src/hotspot/share/classfile/classLoader.cpp +@@ -142,6 +142,9 @@ ClassPathEntry* ClassLoader::_jrt_entry = NULL; + + ClassPathEntry* volatile ClassLoader::_first_append_entry_list = NULL; + ClassPathEntry* volatile ClassLoader::_last_append_entry = NULL; ++ ++ClassPathEntry* ClassLoader::_prim_collection_entry = NULL; ++ + #if INCLUDE_CDS + ClassPathEntry* ClassLoader::_app_classpath_entries = NULL; + ClassPathEntry* ClassLoader::_last_app_classpath_entry = NULL; +@@ -628,6 +631,36 @@ bool ClassLoader::is_in_patch_mod_entries(Symbol* module_name) { + return false; + } + ++// Set up the _prim_collection_entry if UsePrimHashMap ++void ClassLoader::set_prim_collection_path(JavaThread *current) { ++ if (!UsePrimHashMap) { ++ return; ++ } ++ const char *prim_collection_jar = "primcollection.jar"; ++ char jvm_path[JVM_MAXPATHLEN]; ++ os::jvm_path(jvm_path, sizeof(jvm_path)); ++ const int trunc_times = 2; // set path/lib/server/libjvm.so to path/lib ++ for (int i = 0; i < trunc_times; ++i) { ++ char *end = strrchr(jvm_path, *os::file_separator()); ++ if (end != NULL) *end = '\0'; ++ } ++ ++ size_t jvm_path_len = strlen(jvm_path); ++ if (jvm_path_len < JVM_MAXPATHLEN - strlen(os::file_separator()) - strlen(prim_collection_jar)) { ++ jio_snprintf(jvm_path + jvm_path_len, ++ JVM_MAXPATHLEN - jvm_path_len, ++ "%s%s", os::file_separator(), prim_collection_jar); ++ } ++ char* error_msg = NULL; ++ jzfile* zip = open_zip_file(jvm_path, &error_msg, current); ++ if (zip != NULL && error_msg == NULL) { ++ _prim_collection_entry = new ClassPathZipEntry(zip, jvm_path, false, false); ++ log_info(class, load)("primcollection path: %s", jvm_path); ++ } else { ++ UsePrimHashMap = false; ++ } ++} ++ + // Set up the _jrt_entry if present and boot append path + void ClassLoader::setup_bootstrap_search_path_impl(JavaThread* current, const char *class_path) { + ResourceMark rm(current); +@@ -677,6 +710,8 @@ void ClassLoader::setup_bootstrap_search_path_impl(JavaThread* current, const ch + update_class_path_entry_list(current, path, false, true, false); + } + } ++ ++ set_prim_collection_path(current); + } + + // During an exploded modules build, each module defined to the boot loader +@@ -1183,6 +1218,22 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TR + stream = search_module_entries(THREAD, _patch_mod_entries, class_name, file_name); + } + } ++ // Load Attempt: primcollection.jar for PrimHashMapRelatedClass ++ if (UsePrimHashMap && (NULL == stream) && name->is_primhashmap_related_class()) { ++ static bool is_first_loading = true; ++ static bool is_first_loading_succeeded = false; ++ stream = _prim_collection_entry->open_stream(THREAD, file_name); ++ if (!is_first_loading) { ++ // exit when some loads succeed while some fail ++ if ((is_first_loading_succeeded && stream == nullptr) || ++ (!is_first_loading_succeeded && stream != nullptr)) { ++ vm_exit_during_prim_collection_loading(); ++ } ++ } else { ++ is_first_loading = false; ++ is_first_loading_succeeded = (stream != nullptr); ++ } ++ } + + // Load Attempt #2: [jimage | exploded build] + if (!search_append_only && (NULL == stream)) { +diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp +index bac23a9dd..7c1c68317 100644 +--- a/src/hotspot/share/classfile/classLoader.hpp ++++ b/src/hotspot/share/classfile/classLoader.hpp +@@ -216,6 +216,8 @@ class ClassLoader: AllStatic { + // Last entry in linked list of appended ClassPathEntry instances + static ClassPathEntry* volatile _last_append_entry; + ++ static ClassPathEntry* _prim_collection_entry; ++ + // Info used by CDS + CDS_ONLY(static ClassPathEntry* _app_classpath_entries;) + CDS_ONLY(static ClassPathEntry* _last_app_classpath_entry;) +@@ -243,6 +245,7 @@ class ClassLoader: AllStatic { + static void setup_bootstrap_search_path(JavaThread* current); + static void setup_bootstrap_search_path_impl(JavaThread* current, const char *class_path); + static void setup_patch_mod_entries(); ++ static void set_prim_collection_path(JavaThread *current); + static void create_javabase(); + + static void* dll_lookup(void* lib, const char* name, const char* path); +diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp +index c15e8f4df..8a6343fc3 100644 +--- a/src/hotspot/share/classfile/systemDictionaryShared.cpp ++++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp +@@ -2580,6 +2580,9 @@ SystemDictionaryShared::find_record(RunTimeSharedDictionary* static_dict, RunTim + } + + InstanceKlass* SystemDictionaryShared::find_builtin_class(Symbol* name) { ++ if (UsePrimHashMap && name->is_primhashmap_related_class()) { ++ return NULL; ++ } + const RunTimeSharedClassInfo* record = find_record(&_builtin_dictionary, &_dynamic_builtin_dictionary, name); + if (record != NULL) { + assert(!record->_klass->is_hidden(), "hidden class cannot be looked up by name"); +diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp +index 96562d08a..003422ca1 100644 +--- a/src/hotspot/share/oops/symbol.hpp ++++ b/src/hotspot/share/oops/symbol.hpp +@@ -194,6 +194,10 @@ class Symbol : public MetaspaceObj { + } + bool equals(const char* str) const { return equals(str, (int) strlen(str)); } + ++ bool is_primhashmap_related_class() const { ++ return starts_with("java/util/HashMap") || starts_with("java/util/LinkedHashMap"); ++ } ++ + // Tests if the symbol starts with the given prefix. + bool starts_with(const char* prefix, int len) const { + return contains_utf8_at(0, prefix, len); +diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp +index f24cabb11..357ad4aca 100644 +--- a/src/hotspot/share/runtime/arguments.cpp ++++ b/src/hotspot/share/runtime/arguments.cpp +@@ -2969,6 +2969,13 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m + LogConfiguration::configure_stdout(LogLevel::Info, true, LOG_TAGS(class, path)); + } + ++ if (DumpSharedSpaces && UsePrimHashMap) { ++ warning("UsePrimHashMap is confilict with -Xshare:dump. ignoring UsePrimHashMap."); ++ if (FLAG_SET_CMDLINE(UsePrimHashMap, false) != JVMFlag::SUCCESS) { ++ return JNI_EINVAL; ++ } ++ } ++ + fix_appclasspath(); + + return JNI_OK; +diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp +index 680e78c04..4f02cb31a 100644 +--- a/src/hotspot/share/runtime/globals.hpp ++++ b/src/hotspot/share/runtime/globals.hpp +@@ -2129,6 +2129,12 @@ const intx ObjectAlignmentInBytes = 8; + JFR_ONLY(product(ccstr, StartFlightRecording, NULL, \ + "Start flight recording with options")) \ + \ ++ product(bool, UsePrimHashMap, false, EXPERIMENTAL, \ ++ "The Prim HashMap is a specialized version for long key. " \ ++ "Long-Key HashMap can benefit from this in most scenarios." \ ++ "Note: The debugging of HashMap.java is inaccurate" \ ++ " when UsePrimHashMap is enabled.") \ ++ \ + product(bool, UseFastSerializer, false, EXPERIMENTAL, \ + "Cache-based serialization.It is extremely fast, but it" \ + "can only be effective in certain scenarios.") \ +diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp +index a77f30b8c..ccfb0806d 100644 +--- a/src/hotspot/share/runtime/java.cpp ++++ b/src/hotspot/share/runtime/java.cpp +@@ -690,6 +690,12 @@ void vm_exit_during_cds_dumping(const char* error, const char* message) { + vm_abort(false); + } + ++void vm_exit_during_prim_collection_loading() { ++ tty->print_cr("Error occurred during loading prim collection classes: must load all or none from primcollection.jar"); ++ // no need to dump core ++ vm_abort(false); ++} ++ + void vm_notify_during_shutdown(const char* error, const char* message) { + if (error != NULL) { + tty->print_cr("Error occurred during initialization of VM"); +diff --git a/src/hotspot/share/runtime/java.hpp b/src/hotspot/share/runtime/java.hpp +index ad179662f..e796fa84c 100644 +--- a/src/hotspot/share/runtime/java.hpp ++++ b/src/hotspot/share/runtime/java.hpp +@@ -59,6 +59,8 @@ extern void vm_shutdown_during_initialization(const char* error, const char* mes + + extern void vm_exit_during_cds_dumping(const char* error, const char* message = NULL); + ++extern void vm_exit_during_prim_collection_loading(); ++ + /** + * With the integration of the changes to handle the version string + * as defined by JEP-223, most of the code related to handle the version +diff --git a/src/hotspot/share/runtime/vm_version.cpp b/src/hotspot/share/runtime/vm_version.cpp +index 33a5c792c..1e6756aaa 100644 +--- a/src/hotspot/share/runtime/vm_version.cpp ++++ b/src/hotspot/share/runtime/vm_version.cpp +@@ -31,6 +31,14 @@ + void VM_Version_init() { + VM_Version::initialize(); + ++#ifdef AARCH64 ++ if (!VM_Version::is_hisi_enabled()) { ++ UsePrimHashMap = false; ++ } ++#else ++ UsePrimHashMap = false; ++#endif ++ + if (log_is_enabled(Info, os, cpu)) { + char buf[1024]; + ResourceMark rm; +diff --git a/test/jdk/java/util/HashMap/HashMap.java b/test/jdk/java/util/HashMap/HashMap.java +new file mode 100644 +index 000000000..8d7db3569 +--- /dev/null ++++ b/test/jdk/java/util/HashMap/HashMap.java +@@ -0,0 +1,3799 @@ ++/* ++ * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. ++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. ++ * ++ * This code is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License version 2 only, as ++ * published by the Free Software Foundation. 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 java.util; ++ ++import java.io.IOException; ++import java.io.InvalidObjectException; ++import java.io.ObjectInputStream; ++import java.io.Serializable; ++import java.lang.reflect.ParameterizedType; ++import java.lang.reflect.Type; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import jdk.internal.access.SharedSecrets; ++ ++/** ++ * Hash table based implementation of the {@code Map} interface. This ++ * implementation provides all of the optional map operations, and permits ++ * {@code null} values and the {@code null} key. (The {@code HashMap} ++ * class is roughly equivalent to {@code Hashtable}, except that it is ++ * unsynchronized and permits nulls.) This class makes no guarantees as to ++ * the order of the map; in particular, it does not guarantee that the order ++ * will remain constant over time. ++ * ++ *

This implementation provides constant-time performance for the basic ++ * operations ({@code get} and {@code put}), assuming the hash function ++ * disperses the elements properly among the buckets. Iteration over ++ * collection views requires time proportional to the "capacity" of the ++ * {@code HashMap} instance (the number of buckets) plus its size (the number ++ * of key-value mappings). Thus, it's very important not to set the initial ++ * capacity too high (or the load factor too low) if iteration performance is ++ * important. ++ * ++ *

An instance of {@code HashMap} has two parameters that affect its ++ * performance: initial capacity and load factor. The ++ * capacity is the number of buckets in the hash table, and the initial ++ * capacity is simply the capacity at the time the hash table is created. The ++ * load factor is a measure of how full the hash table is allowed to ++ * get before its capacity is automatically increased. When the number of ++ * entries in the hash table exceeds the product of the load factor and the ++ * current capacity, the hash table is rehashed (that is, internal data ++ * structures are rebuilt) so that the hash table has approximately twice the ++ * number of buckets. ++ * ++ *

As a general rule, the default load factor (.75) offers a good ++ * tradeoff between time and space costs. Higher values decrease the ++ * space overhead but increase the lookup cost (reflected in most of ++ * the operations of the {@code HashMap} class, including ++ * {@code get} and {@code put}). The expected number of entries in ++ * the map and its load factor should be taken into account when ++ * setting its initial capacity, so as to minimize the number of ++ * rehash operations. If the initial capacity is greater than the ++ * maximum number of entries divided by the load factor, no rehash ++ * operations will ever occur. ++ * ++ *

If many mappings are to be stored in a {@code HashMap} ++ * instance, creating it with a sufficiently large capacity will allow ++ * the mappings to be stored more efficiently than letting it perform ++ * automatic rehashing as needed to grow the table. Note that using ++ * many keys with the same {@code hashCode()} is a sure way to slow ++ * down performance of any hash table. To ameliorate impact, when keys ++ * are {@link Comparable}, this class may use comparison order among ++ * keys to help break ties. ++ * ++ *

Note that this implementation is not synchronized. ++ * If multiple threads access a hash map concurrently, and at least one of ++ * the threads modifies the map structurally, it must be ++ * synchronized externally. (A structural modification is any operation ++ * that adds or deletes one or more mappings; merely changing the value ++ * associated with a key that an instance already contains is not a ++ * structural modification.) This is typically accomplished by ++ * synchronizing on some object that naturally encapsulates the map. ++ * ++ * If no such object exists, the map should be "wrapped" using the ++ * {@link Collections#synchronizedMap Collections.synchronizedMap} ++ * method. This is best done at creation time, to prevent accidental ++ * unsynchronized access to the map:

++ *   Map m = Collections.synchronizedMap(new HashMap(...));
++ * ++ *

The iterators returned by all of this class's "collection view methods" ++ * are fail-fast: if the map is structurally modified at any time after ++ * the iterator is created, in any way except through the iterator's own ++ * {@code remove} method, the iterator will throw a ++ * {@link ConcurrentModificationException}. Thus, in the face of concurrent ++ * modification, the iterator fails quickly and cleanly, rather than risking ++ * arbitrary, non-deterministic behavior at an undetermined time in the ++ * future. ++ * ++ *

Note that the fail-fast behavior of an iterator cannot be guaranteed ++ * as it is, generally speaking, impossible to make any hard guarantees in the ++ * presence of unsynchronized concurrent modification. Fail-fast iterators ++ * throw {@code ConcurrentModificationException} on a best-effort basis. ++ * Therefore, it would be wrong to write a program that depended on this ++ * exception for its correctness: the fail-fast behavior of iterators ++ * should be used only to detect bugs. ++ * ++ *

This class is a member of the ++ * ++ * Java Collections Framework. ++ * ++ * @param the type of keys maintained by this map ++ * @param the type of mapped values ++ * ++ * @author Doug Lea ++ * @author Josh Bloch ++ * @author Arthur van Hoff ++ * @author Neal Gafter ++ * @see Object#hashCode() ++ * @see Collection ++ * @see Map ++ * @see TreeMap ++ * @see Hashtable ++ * @since 1.2 ++ */ ++public class HashMap extends AbstractMap ++ implements Map, Cloneable, Serializable { ++ ++ @java.io.Serial ++ private static final long serialVersionUID = 362498820763181265L; ++ ++ /* ++ * Implementation notes. ++ * ++ * This map usually acts as a binned (bucketed) hash table, but ++ * when bins get too large, they are transformed into bins of ++ * TreeNodes, each structured similarly to those in ++ * java.util.TreeMap. Most methods try to use normal bins, but ++ * relay to TreeNode methods when applicable (simply by checking ++ * instanceof a node). Bins of TreeNodes may be traversed and ++ * used like any others, but additionally support faster lookup ++ * when overpopulated. However, since the vast majority of bins in ++ * normal use are not overpopulated, checking for existence of ++ * tree bins may be delayed in the course of table methods. ++ * ++ * Tree bins (i.e., bins whose elements are all TreeNodes) are ++ * ordered primarily by hashCode, but in the case of ties, if two ++ * elements are of the same "class C implements Comparable", ++ * type then their compareTo method is used for ordering. (We ++ * conservatively check generic types via reflection to validate ++ * this -- see method comparableClassFor). The added complexity ++ * of tree bins is worthwhile in providing worst-case O(log n) ++ * operations when keys either have distinct hashes or are ++ * orderable, Thus, performance degrades gracefully under ++ * accidental or malicious usages in which hashCode() methods ++ * return values that are poorly distributed, as well as those in ++ * which many keys share a hashCode, so long as they are also ++ * Comparable. (If neither of these apply, we may waste about a ++ * factor of two in time and space compared to taking no ++ * precautions. But the only known cases stem from poor user ++ * programming practices that are already so slow that this makes ++ * little difference.) ++ * ++ * Because TreeNodes are about twice the size of regular nodes, we ++ * use them only when bins contain enough nodes to warrant use ++ * (see TREEIFY_THRESHOLD). And when they become too small (due to ++ * removal or resizing) they are converted back to plain bins. In ++ * usages with well-distributed user hashCodes, tree bins are ++ * rarely used. Ideally, under random hashCodes, the frequency of ++ * nodes in bins follows a Poisson distribution ++ * (http://en.wikipedia.org/wiki/Poisson_distribution) with a ++ * parameter of about 0.5 on average for the default resizing ++ * threshold of 0.75, although with a large variance because of ++ * resizing granularity. Ignoring variance, the expected ++ * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / ++ * factorial(k)). The first values are: ++ * ++ * 0: 0.60653066 ++ * 1: 0.30326533 ++ * 2: 0.07581633 ++ * 3: 0.01263606 ++ * 4: 0.00157952 ++ * 5: 0.00015795 ++ * 6: 0.00001316 ++ * 7: 0.00000094 ++ * 8: 0.00000006 ++ * more: less than 1 in ten million ++ * ++ * The root of a tree bin is normally its first node. However, ++ * sometimes (currently only upon Iterator.remove), the root might ++ * be elsewhere, but can be recovered following parent links ++ * (method TreeNode.root()). ++ * ++ * All applicable internal methods accept a hash code as an ++ * argument (as normally supplied from a public method), allowing ++ * them to call each other without recomputing user hashCodes. ++ * Most internal methods also accept a "tab" argument, that is ++ * normally the current table, but may be a new or old one when ++ * resizing or converting. ++ * ++ * When bin lists are treeified, split, or untreeified, we keep ++ * them in the same relative access/traversal order (i.e., field ++ * Node.next) to better preserve locality, and to slightly ++ * simplify handling of splits and traversals that invoke ++ * iterator.remove. When using comparators on insertion, to keep a ++ * total ordering (or as close as is required here) across ++ * rebalancings, we compare classes and identityHashCodes as ++ * tie-breakers. ++ * ++ * The use and transitions among plain vs tree modes is ++ * complicated by the existence of subclass LinkedHashMap. See ++ * below for hook methods defined to be invoked upon insertion, ++ * removal and access that allow LinkedHashMap internals to ++ * otherwise remain independent of these mechanics. (This also ++ * requires that a map instance be passed to some utility methods ++ * that may create new nodes.) ++ * ++ * The concurrent-programming-like SSA-based coding style helps ++ * avoid aliasing errors amid all of the twisty pointer operations. ++ */ ++ ++ /** ++ * The default initial capacity - MUST be a power of two. ++ */ ++ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 ++ ++ /** ++ * The maximum capacity, used if a higher value is implicitly specified ++ * by either of the constructors with arguments. ++ * MUST be a power of two <= 1<<30. ++ */ ++ static final int MAXIMUM_CAPACITY = 1 << 30; ++ ++ /** ++ * The load factor used when none specified in constructor. ++ */ ++ static final float DEFAULT_LOAD_FACTOR = 0.75f; ++ ++ /** ++ * The bin count threshold for using a tree rather than list for a ++ * bin. Bins are converted to trees when adding an element to a ++ * bin with at least this many nodes. The value must be greater ++ * than 2 and should be at least 8 to mesh with assumptions in ++ * tree removal about conversion back to plain bins upon ++ * shrinkage. ++ */ ++ static final int TREEIFY_THRESHOLD = 8; ++ ++ /** ++ * The bin count threshold for untreeifying a (split) bin during a ++ * resize operation. Should be less than TREEIFY_THRESHOLD, and at ++ * most 6 to mesh with shrinkage detection under removal. ++ */ ++ static final int UNTREEIFY_THRESHOLD = 6; ++ ++ /** ++ * The smallest table capacity for which bins may be treeified. ++ * (Otherwise the table is resized if too many nodes in a bin.) ++ * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts ++ * between resizing and treeification thresholds. ++ */ ++ static final int MIN_TREEIFY_CAPACITY = 64; ++ ++ /** ++ * The max load factor used for prim long hashmap. The performance of ++ * prim long hashmap will decrease sharply when the value exceeds 0.8f. ++ */ ++ static final float MAX_LOAD_FACTOR_FOR_PRIM_MAP = 0.8f; ++ ++ static final int NULL_KEY_INDEX_FOR_RPIM_MAP = -1; ++ static final int KEY_NO_EXIST_FOR_PRIM_MAP = -2; ++ ++ /** ++ * Basic hash bin node, used for most entries. (See below for ++ * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) ++ */ ++ static class Node implements Map.Entry { ++ final int hash; ++ final K key; ++ V value; ++ Node next; ++ ++ Node(int hash, K key, V value, Node next) { ++ this.hash = hash; ++ this.key = key; ++ this.value = value; ++ this.next = next; ++ } ++ ++ public final K getKey() { return key; } ++ public final V getValue() { return value; } ++ public final String toString() { return key + "=" + value; } ++ ++ public final int hashCode() { ++ return Objects.hashCode(key) ^ Objects.hashCode(value); ++ } ++ ++ public final V setValue(V newValue) { ++ V oldValue = value; ++ value = newValue; ++ return oldValue; ++ } ++ ++ public final boolean equals(Object o) { ++ if (o == this) ++ return true; ++ ++ return o instanceof Map.Entry e ++ && Objects.equals(key, e.getKey()) ++ && Objects.equals(value, e.getValue()); ++ } ++ } ++ ++ /* ---------------- Static utilities -------------- */ ++ ++ /** ++ * Computes key.hashCode() and spreads (XORs) higher bits of hash ++ * to lower. Because the table uses power-of-two masking, sets of ++ * hashes that vary only in bits above the current mask will ++ * always collide. (Among known examples are sets of Float keys ++ * holding consecutive whole numbers in small tables.) So we ++ * apply a transform that spreads the impact of higher bits ++ * downward. There is a tradeoff between speed, utility, and ++ * quality of bit-spreading. Because many common sets of hashes ++ * are already reasonably distributed (so don't benefit from ++ * spreading), and because we use trees to handle large sets of ++ * collisions in bins, we just XOR some shifted bits in the ++ * cheapest possible way to reduce systematic lossage, as well as ++ * to incorporate impact of the highest bits that would otherwise ++ * never be used in index calculations because of table bounds. ++ */ ++ static final int hash(Object key) { ++ int h; ++ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); ++ } ++ ++ /** ++ * Returns x's Class if it is of the form "class C implements ++ * Comparable", else null. ++ */ ++ static Class comparableClassFor(Object x) { ++ if (x instanceof Comparable) { ++ Class c; Type[] ts, as; ParameterizedType p; ++ if ((c = x.getClass()) == String.class) // bypass checks ++ return c; ++ if ((ts = c.getGenericInterfaces()) != null) { ++ for (Type t : ts) { ++ if ((t instanceof ParameterizedType) && ++ ((p = (ParameterizedType) t).getRawType() == ++ Comparable.class) && ++ (as = p.getActualTypeArguments()) != null && ++ as.length == 1 && as[0] == c) // type arg is c ++ return c; ++ } ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns k.compareTo(x) if x matches kc (k's screened comparable ++ * class), else 0. ++ */ ++ @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable ++ static int compareComparables(Class kc, Object k, Object x) { ++ return (x == null || x.getClass() != kc ? 0 : ++ ((Comparable)k).compareTo(x)); ++ } ++ ++ /** ++ * Returns a power of two size for the given target capacity. ++ */ ++ static final int tableSizeFor(int cap) { ++ int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); ++ return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; ++ } ++ ++ /* ---------------- Fields -------------- */ ++ ++ /** ++ * The table, initialized on first use, and resized as ++ * necessary. When allocated, length is always a power of two. ++ * (We also tolerate length zero in some operations to allow ++ * bootstrapping mechanics that are currently not needed.) ++ */ ++ transient Node[] table; ++ ++ /** ++ * Holds cached entrySet(). Note that AbstractMap fields are used ++ * for keySet() and values(). ++ */ ++ transient Set> entrySet; ++ ++ /** ++ * The number of key-value mappings contained in this map. ++ */ ++ transient int size; ++ ++ /** ++ * The number of times this HashMap has been structurally modified ++ * Structural modifications are those that change the number of mappings in ++ * the HashMap or otherwise modify its internal structure (e.g., ++ * rehash). This field is used to make iterators on Collection-views of ++ * the HashMap fail-fast. (See ConcurrentModificationException). ++ */ ++ transient int modCount; ++ ++ /** ++ * The next size value at which to resize (capacity * load factor). ++ * ++ * @serial ++ */ ++ // (The javadoc description is true upon serialization. ++ // Additionally, if the table array has not been allocated, this ++ // field holds the initial array capacity, or zero signifying ++ // DEFAULT_INITIAL_CAPACITY.) ++ int threshold; ++ ++ /** ++ * The load factor for the hash table. ++ * ++ * @serial ++ */ ++ final float loadFactor; ++ ++ /** ++ * The keys in prim long hashmap. ++ */ ++ transient long[] primMapKeys; ++ ++ /** ++ * The values in prim long hashmap. ++ */ ++ transient V[] primMapValues; ++ ++ /** ++ * Indicates whether the element is valid in prim long hashmap. ++ */ ++ transient boolean[] primMapValids; ++ ++ /** ++ * The value of null key in prim long hashmap. ++ */ ++ transient V primMapValOfNullKey; ++ ++ /** ++ * Indicates whether null key exist in prim long hashmap. ++ */ ++ transient boolean primMapNullKeyValid; ++ ++ /** ++ * Indicates whether the current state is in prim long map. ++ */ ++ transient boolean usingPrimHashMap; ++ ++ /* ---------------- Public operations -------------- */ ++ ++ /** ++ * Constructs an empty {@code HashMap} with the specified initial ++ * capacity and load factor. ++ * ++ * @param initialCapacity the initial capacity ++ * @param loadFactor the load factor ++ * @throws IllegalArgumentException if the initial capacity is negative ++ * or the load factor is nonpositive ++ */ ++ public HashMap(int initialCapacity, float loadFactor) { ++ if (initialCapacity < 0) ++ throw new IllegalArgumentException("Illegal initial capacity: " + ++ initialCapacity); ++ if (initialCapacity > MAXIMUM_CAPACITY) ++ initialCapacity = MAXIMUM_CAPACITY; ++ if (loadFactor <= 0 || Float.isNaN(loadFactor)) ++ throw new IllegalArgumentException("Illegal load factor: " + ++ loadFactor); ++ this.loadFactor = loadFactor; ++ this.threshold = tableSizeFor(initialCapacity); ++ if (this.loadFactor > MAX_LOAD_FACTOR_FOR_PRIM_MAP) { ++ disablePrimHashMap(); ++ } else { ++ initUsingPrimHashMap(); ++ } ++ } ++ ++ /** ++ * Constructs an empty {@code HashMap} with the specified initial ++ * capacity and the default load factor (0.75). ++ * ++ * @param initialCapacity the initial capacity. ++ * @throws IllegalArgumentException if the initial capacity is negative. ++ */ ++ public HashMap(int initialCapacity) { ++ this(initialCapacity, DEFAULT_LOAD_FACTOR); ++ } ++ ++ /** ++ * Constructs an empty {@code HashMap} with the default initial capacity ++ * (16) and the default load factor (0.75). ++ */ ++ public HashMap() { ++ initUsingPrimHashMap(); ++ this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted ++ } ++ ++ /** ++ * Constructs a new {@code HashMap} with the same mappings as the ++ * specified {@code Map}. The {@code HashMap} is created with ++ * default load factor (0.75) and an initial capacity sufficient to ++ * hold the mappings in the specified {@code Map}. ++ * ++ * @param m the map whose mappings are to be placed in this map ++ * @throws NullPointerException if the specified map is null ++ */ ++ public HashMap(Map m) { ++ initUsingPrimHashMap(); ++ this.loadFactor = DEFAULT_LOAD_FACTOR; ++ putMapEntries(m, false); ++ } ++ ++ /** ++ * Implements Map.putAll and Map constructor. ++ * ++ * @param m the map ++ * @param evict false when initially constructing this map, else ++ * true (relayed to method afterNodeInsertion). ++ */ ++ final void putMapEntries(Map m, boolean evict) { ++ int s = m.size(); ++ if (s > 0) { ++ if ((!usePrimHashMap() && table == null) || ++ (usePrimHashMap() && primMapValids == null)) { // pre-size ++ float ft = ((float)s / loadFactor) + 1.0F; ++ int t = ((ft < (float)MAXIMUM_CAPACITY) ? ++ (int)ft : MAXIMUM_CAPACITY); ++ if (t > threshold) ++ threshold = tableSizeFor(t); ++ } else { ++ if (!usePrimHashMap()) { ++ // Because of linked-list bucket constraints, we cannot ++ // expand all at once, but can reduce total resize ++ // effort by repeated doubling now vs later ++ while (s > threshold && table.length < MAXIMUM_CAPACITY) ++ resize(); ++ } else { ++ while (s > threshold && primMapValids.length < MAXIMUM_CAPACITY) ++ primHashMapResize(); ++ } ++ } ++ ++ for (Map.Entry e : m.entrySet()) { ++ K key = e.getKey(); ++ V value = e.getValue(); ++ if (!usePrimHashMap(key)) { ++ putVal(key, value, false, evict, true); ++ } else { ++ primHashMapPutVal((Long)key, value, false); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Returns the number of key-value mappings in this map. ++ * ++ * @return the number of key-value mappings in this map ++ */ ++ public int size() { ++ return size; ++ } ++ ++ /** ++ * Returns {@code true} if this map contains no key-value mappings. ++ * ++ * @return {@code true} if this map contains no key-value mappings ++ */ ++ public boolean isEmpty() { ++ return size == 0; ++ } ++ ++ /** ++ * Returns the value to which the specified key is mapped, ++ * or {@code null} if this map contains no mapping for the key. ++ * ++ *

More formally, if this map contains a mapping from a key ++ * {@code k} to a value {@code v} such that {@code (key==null ? k==null : ++ * key.equals(k))}, then this method returns {@code v}; otherwise ++ * it returns {@code null}. (There can be at most one such mapping.) ++ * ++ *

A return value of {@code null} does not necessarily ++ * indicate that the map contains no mapping for the key; it's also ++ * possible that the map explicitly maps the key to {@code null}. ++ * The {@link #containsKey containsKey} operation may be used to ++ * distinguish these two cases. ++ * ++ * @see #put(Object, Object) ++ */ ++ public V get(Object key) { ++ if (usePrimHashMap()) { ++ return primHashMapGet(key); ++ } ++ Node e; ++ return (e = getNode(key)) == null ? null : e.value; ++ } ++ ++ /** ++ * Implements Map.get and related methods. ++ * ++ * @param key the key ++ * @return the node, or null if none ++ */ ++ final Node getNode(Object key) { ++ Node[] tab; Node first, e; int n, hash; K k; ++ if ((tab = table) != null && (n = tab.length) > 0 && ++ (first = tab[(n - 1) & (hash = hash(key))]) != null) { ++ if (first.hash == hash && // always check first node ++ ((k = first.key) == key || (key != null && key.equals(k)))) ++ return first; ++ if ((e = first.next) != null) { ++ if (first instanceof TreeNode) ++ return ((TreeNode)first).getTreeNode(hash, key); ++ do { ++ if (e.hash == hash && ++ ((k = e.key) == key || (key != null && key.equals(k)))) ++ return e; ++ } while ((e = e.next) != null); ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Returns {@code true} if this map contains a mapping for the ++ * specified key. ++ * ++ * @param key The key whose presence in this map is to be tested ++ * @return {@code true} if this map contains a mapping for the specified ++ * key. ++ */ ++ public boolean containsKey(Object key) { ++ if (usePrimHashMap()) { ++ return (primHashGetIndexByKey(key) != KEY_NO_EXIST_FOR_PRIM_MAP); ++ } ++ return getNode(key) != null; ++ } ++ ++ /** ++ * Associates the specified value with the specified key in this map. ++ * If the map previously contained a mapping for the key, the old ++ * value is replaced. ++ * ++ * @param key key with which the specified value is to be associated ++ * @param value value to be associated with the specified key ++ * @return the previous value associated with {@code key}, or ++ * {@code null} if there was no mapping for {@code key}. ++ * (A {@code null} return can also indicate that the map ++ * previously associated {@code null} with {@code key}.) ++ */ ++ public V put(K key, V value) { ++ if (usePrimHashMap(key)) { ++ return primHashMapPutVal((Long)key, value, false); ++ } ++ return putVal(key, value, false, true, true); ++ } ++ ++ /** ++ * Implements Map.put and related methods. ++ * ++ * @param key the key ++ * @param value the value to put ++ * @param onlyIfAbsent if true, don't change existing value ++ * @param evict if false, the table is in creation mode. ++ * @param update if false(in rollback state), don't update size and modcount ++ * @return previous value, or null if none ++ */ ++ final V putVal(K key, V value, boolean onlyIfAbsent, ++ boolean evict, boolean update) { ++ int hash = hash(key); ++ Node[] tab; Node p; int n, i; ++ if ((tab = table) == null || (n = tab.length) == 0) ++ n = (tab = resize()).length; ++ if ((p = tab[i = (n - 1) & hash]) == null) ++ tab[i] = newNode(hash, key, value, null); ++ else { ++ Node e; K k; ++ if (p.hash == hash && ++ ((k = p.key) == key || (key != null && key.equals(k)))) ++ e = p; ++ else if (p instanceof TreeNode) ++ e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); ++ else { ++ for (int binCount = 0; ; ++binCount) { ++ if ((e = p.next) == null) { ++ p.next = newNode(hash, key, value, null); ++ if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st ++ treeifyBin(tab, hash); ++ break; ++ } ++ if (e.hash == hash && ++ ((k = e.key) == key || (key != null && key.equals(k)))) ++ break; ++ p = e; ++ } ++ } ++ if (e != null) { // existing mapping for key ++ V oldValue = e.value; ++ if (!onlyIfAbsent || oldValue == null) ++ e.value = value; ++ afterNodeAccess(e); ++ return oldValue; ++ } ++ } ++ if (update) { ++ ++modCount; ++ if (++size > threshold) ++ resize(); ++ } ++ afterNodeInsertion(evict); ++ return null; ++ } ++ ++ /** ++ * Calculate the new capacity based on the old capacity. ++ * If the old value is 0, the new capacity is set to the ++ * initial capacity saved in the threshold field. ++ * Otherwise, set the new capacity to double the old capacity. ++ * This method update threshold at the same time. ++ * ++ * @return the new capacity. 0 if oldCap reaches the max capacity. ++ */ ++ private int calNewCapAndUpdateThreshold(int oldCap) { ++ int oldThr = threshold; ++ int newCap, newThr = 0; ++ if (oldCap > 0) { ++ if (oldCap >= MAXIMUM_CAPACITY) { ++ threshold = Integer.MAX_VALUE; ++ return 0; ++ } ++ else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && ++ oldCap >= DEFAULT_INITIAL_CAPACITY) ++ newThr = oldThr << 1; // double threshold ++ } ++ else if (oldThr > 0) // initial capacity was placed in threshold ++ newCap = oldThr; ++ else { // zero initial threshold signifies using defaults ++ newCap = DEFAULT_INITIAL_CAPACITY; ++ newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); ++ } ++ if (newThr == 0) { ++ float ft = (float)newCap * loadFactor; ++ newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? ++ (int)ft : Integer.MAX_VALUE); ++ } ++ threshold = newThr; ++ return newCap; ++ } ++ ++ /** ++ * Initializes or doubles table size. If null, allocates in ++ * accord with initial capacity target held in field threshold. ++ * Otherwise, because we are using power-of-two expansion, the ++ * elements from each bin must either stay at same index, or move ++ * with a power of two offset in the new table. ++ * ++ * @return the table ++ */ ++ final Node[] resize() { ++ Node[] oldTab = table; ++ final int oldCap = (oldTab == null) ? 0 : oldTab.length; ++ int newCap = calNewCapAndUpdateThreshold(oldCap); ++ // 0 means oldCap reaches the MAXIMUM_CAPACITY ++ if (newCap == 0) { ++ return oldTab; ++ } ++ @SuppressWarnings({"rawtypes","unchecked"}) ++ Node[] newTab = (Node[])new Node[newCap]; ++ table = newTab; ++ if (oldTab != null) { ++ for (int j = 0; j < oldCap; ++j) { ++ Node e; ++ if ((e = oldTab[j]) != null) { ++ oldTab[j] = null; ++ if (e.next == null) ++ newTab[e.hash & (newCap - 1)] = e; ++ else if (e instanceof TreeNode) ++ ((TreeNode)e).split(this, newTab, j, oldCap); ++ else { // preserve order ++ Node loHead = null, loTail = null; ++ Node hiHead = null, hiTail = null; ++ Node next; ++ do { ++ next = e.next; ++ if ((e.hash & oldCap) == 0) { ++ if (loTail == null) ++ loHead = e; ++ else ++ loTail.next = e; ++ loTail = e; ++ } ++ else { ++ if (hiTail == null) ++ hiHead = e; ++ else ++ hiTail.next = e; ++ hiTail = e; ++ } ++ } while ((e = next) != null); ++ if (loTail != null) { ++ loTail.next = null; ++ newTab[j] = loHead; ++ } ++ if (hiTail != null) { ++ hiTail.next = null; ++ newTab[j + oldCap] = hiHead; ++ } ++ } ++ } ++ } ++ } ++ return newTab; ++ } ++ ++ /** ++ * Replaces all linked nodes in bin at index for given hash unless ++ * table is too small, in which case resizes instead. ++ */ ++ final void treeifyBin(Node[] tab, int hash) { ++ int n, index; Node e; ++ if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) ++ resize(); ++ else if ((e = tab[index = (n - 1) & hash]) != null) { ++ TreeNode hd = null, tl = null; ++ do { ++ TreeNode p = replacementTreeNode(e, null); ++ if (tl == null) ++ hd = p; ++ else { ++ p.prev = tl; ++ tl.next = p; ++ } ++ tl = p; ++ } while ((e = e.next) != null); ++ if ((tab[index] = hd) != null) ++ hd.treeify(tab); ++ } ++ } ++ ++ /** ++ * Copies all of the mappings from the specified map to this map. ++ * These mappings will replace any mappings that this map had for ++ * any of the keys currently in the specified map. ++ * ++ * @param m mappings to be stored in this map ++ * @throws NullPointerException if the specified map is null ++ */ ++ public void putAll(Map m) { ++ putMapEntries(m, true); ++ } ++ ++ /** ++ * Removes the mapping for the specified key from this map if present. ++ * ++ * @param key key whose mapping is to be removed from the map ++ * @return the previous value associated with {@code key}, or ++ * {@code null} if there was no mapping for {@code key}. ++ * (A {@code null} return can also indicate that the map ++ * previously associated {@code null} with {@code key}.) ++ */ ++ public V remove(Object key) { ++ if (usePrimHashMap(key)) { ++ return primHashMapRemoveByKey(key); ++ } ++ Node e; ++ return (e = removeNode(hash(key), key, null, false, true)) == null ? ++ null : e.value; ++ } ++ ++ /** ++ * Implements Map.remove and related methods. ++ * ++ * @param hash hash for key ++ * @param key the key ++ * @param value the value to match if matchValue, else ignored ++ * @param matchValue if true only remove if value is equal ++ * @param movable if false do not move other nodes while removing ++ * @return the node, or null if none ++ */ ++ final Node removeNode(int hash, Object key, Object value, ++ boolean matchValue, boolean movable) { ++ Node[] tab; Node p; int n, index; ++ if ((tab = table) != null && (n = tab.length) > 0 && ++ (p = tab[index = (n - 1) & hash]) != null) { ++ Node node = null, e; K k; V v; ++ if (p.hash == hash && ++ ((k = p.key) == key || (key != null && key.equals(k)))) ++ node = p; ++ else if ((e = p.next) != null) { ++ if (p instanceof TreeNode) ++ node = ((TreeNode)p).getTreeNode(hash, key); ++ else { ++ do { ++ if (e.hash == hash && ++ ((k = e.key) == key || ++ (key != null && key.equals(k)))) { ++ node = e; ++ break; ++ } ++ p = e; ++ } while ((e = e.next) != null); ++ } ++ } ++ if (node != null && (!matchValue || (v = node.value) == value || ++ (value != null && value.equals(v)))) { ++ if (node instanceof TreeNode) ++ ((TreeNode)node).removeTreeNode(this, tab, movable); ++ else if (node == p) ++ tab[index] = node.next; ++ else ++ p.next = node.next; ++ ++modCount; ++ --size; ++ afterNodeRemoval(node); ++ return node; ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * Removes all of the mappings from this map. ++ * The map will be empty after this call returns. ++ */ ++ public void clear() { ++ modCount++; ++ if (usePrimHashMap()) { ++ if (size > 0) { ++ size = 0; ++ if (primMapValids != null && primMapValids.length != 0) { ++ Arrays.fill(primMapValids, false); ++ Arrays.fill(primMapValues, null); ++ } ++ primMapNullKeyValid = false; ++ primMapValOfNullKey = null; ++ } ++ } else { ++ Node[] tab; ++ if ((tab = table) != null && size > 0) { ++ size = 0; ++ for (int i = 0; i < tab.length; ++i) ++ tab[i] = null; ++ } ++ } ++ } ++ ++ /** ++ * Returns {@code true} if this map maps one or more keys to the ++ * specified value. ++ * ++ * @param value value whose presence in this map is to be tested ++ * @return {@code true} if this map maps one or more keys to the ++ * specified value ++ */ ++ public boolean containsValue(Object value) { ++ if (usePrimHashMap()) { ++ return primHashMapContainsValue(value); ++ } ++ Node[] tab; V v; ++ if ((tab = table) != null && size > 0) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) { ++ if ((v = e.value) == value || ++ (value != null && value.equals(v))) ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * Returns a {@link Set} view of the keys contained in this map. ++ * The set is backed by the map, so changes to the map are ++ * reflected in the set, and vice-versa. If the map is modified ++ * while an iteration over the set is in progress (except through ++ * the iterator's own {@code remove} operation), the results of ++ * the iteration are undefined. The set supports element removal, ++ * which removes the corresponding mapping from the map, via the ++ * {@code Iterator.remove}, {@code Set.remove}, ++ * {@code removeAll}, {@code retainAll}, and {@code clear} ++ * operations. It does not support the {@code add} or {@code addAll} ++ * operations. ++ * ++ * @return a set view of the keys contained in this map ++ */ ++ public Set keySet() { ++ Set ks = keySet; ++ if (ks == null) { ++ ks = new KeySet(); ++ keySet = ks; ++ } ++ return ks; ++ } ++ ++ /** ++ * Prepares the array for {@link Collection#toArray(Object[])} implementation. ++ * If supplied array is smaller than this map size, a new array is allocated. ++ * If supplied array is bigger than this map size, a null is written at size index. ++ * ++ * @param a an original array passed to {@code toArray()} method ++ * @param type of array elements ++ * @return an array ready to be filled and returned from {@code toArray()} method. ++ */ ++ @SuppressWarnings("unchecked") ++ final T[] prepareArray(T[] a) { ++ int size = this.size; ++ if (a.length < size) { ++ return (T[]) java.lang.reflect.Array ++ .newInstance(a.getClass().getComponentType(), size); ++ } ++ if (a.length > size) { ++ a[size] = null; ++ } ++ return a; ++ } ++ ++ /** ++ * Fills an array with this map keys and returns it. This method assumes ++ * that input array is big enough to fit all the keys. Use ++ * {@link #prepareArray(Object[])} to ensure this. ++ * ++ * @param a an array to fill ++ * @param type of array elements ++ * @return supplied array ++ */ ++ T[] keysToArray(T[] a) { ++ Object[] r = a; ++ int idx = 0; ++ if (usePrimHashMap()) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ r[idx++] = null; ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ r[idx++] = keys[i]; ++ --remaining; ++ } ++ } ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) { ++ r[idx++] = e.key; ++ } ++ } ++ } ++ } ++ return a; ++ } ++ ++ /** ++ * Fills an array with this map values and returns it. This method assumes ++ * that input array is big enough to fit all the values. Use ++ * {@link #prepareArray(Object[])} to ensure this. ++ * ++ * @param a an array to fill ++ * @param type of array elements ++ * @return supplied array ++ */ ++ T[] valuesToArray(T[] a) { ++ Object[] r = a; ++ int idx = 0; ++ if (usePrimHashMap()) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ r[idx++] = primMapValOfNullKey; ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ V[] values = primMapValues; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ r[idx++] = values[i]; ++ --remaining; ++ } ++ } ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) { ++ r[idx++] = e.value; ++ } ++ } ++ } ++ } ++ return a; ++ } ++ ++ final class KeySet extends AbstractSet { ++ public final int size() { return size; } ++ public final void clear() { HashMap.this.clear(); } ++ public final Iterator iterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapKeyIterator(); ++ } ++ return new KeyIterator(); ++ } ++ public final boolean contains(Object o) { return containsKey(o); } ++ public final boolean remove(Object key) { ++ if (usePrimHashMap(key)) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return false; ++ } ++ primHashMapRemoveByIndex(index); ++ return true; ++ } ++ return removeNode(hash(key), key, null, false, true) != null; ++ } ++ public final Spliterator spliterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapKeySpliterator(0, -1, 0, 0, primMapNullKeyValid); ++ } ++ return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); ++ } ++ ++ public Object[] toArray() { ++ return keysToArray(new Object[size]); ++ } ++ ++ public T[] toArray(T[] a) { ++ return keysToArray(prepareArray(a)); ++ } ++ ++ public final void forEach(Consumer action) { ++ int mc = modCount; ++ if (action == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap()) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ action.accept(null); ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ action.accept(castKeyToGenericType(keys[i])); ++ --remaining; ++ } ++ } ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) ++ action.accept(e.key); ++ } ++ } ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ /** ++ * Returns a {@link Collection} view of the values contained in this map. ++ * The collection is backed by the map, so changes to the map are ++ * reflected in the collection, and vice-versa. If the map is ++ * modified while an iteration over the collection is in progress ++ * (except through the iterator's own {@code remove} operation), ++ * the results of the iteration are undefined. The collection ++ * supports element removal, which removes the corresponding ++ * mapping from the map, via the {@code Iterator.remove}, ++ * {@code Collection.remove}, {@code removeAll}, ++ * {@code retainAll} and {@code clear} operations. It does not ++ * support the {@code add} or {@code addAll} operations. ++ * ++ * @return a view of the values contained in this map ++ */ ++ public Collection values() { ++ Collection vs = values; ++ if (vs == null) { ++ vs = new Values(); ++ values = vs; ++ } ++ return vs; ++ } ++ ++ final class Values extends AbstractCollection { ++ public final int size() { return size; } ++ public final void clear() { HashMap.this.clear(); } ++ public final Iterator iterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapValueIterator(); ++ } ++ return new ValueIterator(); ++ } ++ public final boolean contains(Object o) { return containsValue(o); } ++ public final Spliterator spliterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapValueSpliterator(0, -1, 0, 0, primMapNullKeyValid); ++ } ++ return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0); ++ } ++ ++ public Object[] toArray() { ++ return valuesToArray(new Object[size]); ++ } ++ ++ public T[] toArray(T[] a) { ++ return valuesToArray(prepareArray(a)); ++ } ++ ++ public final void forEach(Consumer action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ if (usePrimHashMap()) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ action.accept(primMapValOfNullKey); ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ V[] values = primMapValues; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ action.accept(values[i]); ++ --remaining; ++ } ++ } ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) ++ action.accept(e.value); ++ } ++ } ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ /** ++ * Returns a {@link Set} view of the mappings contained in this map. ++ * The set is backed by the map, so changes to the map are ++ * reflected in the set, and vice-versa. If the map is modified ++ * while an iteration over the set is in progress (except through ++ * the iterator's own {@code remove} operation, or through the ++ * {@code setValue} operation on a map entry returned by the ++ * iterator) the results of the iteration are undefined. The set ++ * supports element removal, which removes the corresponding ++ * mapping from the map, via the {@code Iterator.remove}, ++ * {@code Set.remove}, {@code removeAll}, {@code retainAll} and ++ * {@code clear} operations. It does not support the ++ * {@code add} or {@code addAll} operations. ++ * ++ * @return a set view of the mappings contained in this map ++ */ ++ public Set> entrySet() { ++ Set> es; ++ return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; ++ } ++ ++ final class EntrySet extends AbstractSet> { ++ public final int size() { return size; } ++ public final void clear() { HashMap.this.clear(); } ++ public final Iterator> iterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapEntryIterator(); ++ } ++ return new EntryIterator(); ++ } ++ public final boolean contains(Object o) { ++ if (!(o instanceof Map.Entry e)) ++ return false; ++ Object key = e.getKey(); ++ if (usePrimHashMap()) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return false; ++ } ++ Object value = e.getValue(); ++ return (index == NULL_KEY_INDEX_FOR_RPIM_MAP) ? ++ Objects.equals(value, primMapValOfNullKey) : ++ Objects.equals(value, primMapValues[index]); ++ } else { ++ Node candidate = getNode(key); ++ return candidate != null && candidate.equals(e); ++ } ++ } ++ public final boolean remove(Object o) { ++ if (o instanceof Map.Entry e) { ++ Object key = e.getKey(); ++ Object value = e.getValue(); ++ return HashMap.this.remove(key, value); ++ } ++ return false; ++ } ++ public final Spliterator> spliterator() { ++ if (usePrimHashMap()) { ++ return new primHashMapEntrySpliterator(0, -1, 0, 0, primMapNullKeyValid); ++ } ++ return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); ++ } ++ public final void forEach(Consumer> action) { ++ if (action == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap()) { ++ Iterator> iter = iterator(); ++ int mc = modCount; ++ while (iter.hasNext()) { ++ Map.Entry e = iter.next(); ++ action.accept(e); ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ int mc = modCount; ++ for (Node e : tab) { ++ for (; e != null; e = e.next) ++ action.accept(e); ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ } ++ } ++ ++ // Overrides of JDK8 Map extension methods ++ ++ @Override ++ public V getOrDefault(Object key, V defaultValue) { ++ if (usePrimHashMap()) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return defaultValue; ++ } ++ return primHashMapGetValByIndex(index); ++ } ++ Node e; ++ return (e = getNode(key)) == null ? defaultValue : e.value; ++ } ++ ++ @Override ++ public V putIfAbsent(K key, V value) { ++ if (usePrimHashMap(key)) { ++ return primHashMapPutVal((Long)key, value, true); ++ } ++ return putVal(key, value, true, true, true); ++ } ++ ++ @Override ++ public boolean remove(Object key, Object value) { ++ if (usePrimHashMap(key)) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return false; ++ } ++ Object val = primHashMapGetValByIndex(index); ++ if (Objects.equals(value, val)) { ++ primHashMapRemoveByIndex(index); ++ return true; ++ } ++ return false; ++ } ++ return removeNode(hash(key), key, value, true, true) != null; ++ } ++ ++ @Override ++ public boolean replace(K key, V oldValue, V newValue) { ++ if (usePrimHashMap(key)) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return false; ++ } ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ if (Objects.equals(oldValue, primMapValOfNullKey)) { ++ primMapValOfNullKey = newValue; ++ return true; ++ } ++ } else { ++ if (Objects.equals(oldValue, primMapValues[index])) { ++ primMapValues[index] = newValue; ++ return true; ++ } ++ } ++ return false; ++ } ++ Node e; V v; ++ if ((e = getNode(key)) != null && ++ ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) { ++ e.value = newValue; ++ afterNodeAccess(e); ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public V replace(K key, V value) { ++ if (usePrimHashMap(key)) { ++ int index = primHashGetIndexByKey(key); ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return null; ++ } ++ V oldValue; ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ oldValue = primMapValOfNullKey; ++ primMapValOfNullKey = value; ++ } else { ++ oldValue = primMapValues[index]; ++ primMapValues[index] = value; ++ } ++ return oldValue; ++ } ++ Node e; ++ if ((e = getNode(key)) != null) { ++ V oldValue = e.value; ++ e.value = value; ++ afterNodeAccess(e); ++ return oldValue; ++ } ++ return null; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

This method will, on a best-effort basis, throw a ++ * {@link ConcurrentModificationException} if it is detected that the ++ * mapping function modifies this map during computation. ++ * ++ * @throws ConcurrentModificationException if it is detected that the ++ * mapping function modified this map ++ */ ++ @Override ++ public V computeIfAbsent(K key, ++ Function mappingFunction) { ++ if (mappingFunction == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap(key)) { ++ return primHashMapComputeIfAbsent(key, mappingFunction); ++ } ++ int hash = hash(key); ++ Node[] tab; Node first; int n, i; ++ int binCount = 0; ++ TreeNode t = null; ++ Node old = null; ++ if (size > threshold || (tab = table) == null || ++ (n = tab.length) == 0) ++ n = (tab = resize()).length; ++ if ((first = tab[i = (n - 1) & hash]) != null) { ++ if (first instanceof TreeNode) ++ old = (t = (TreeNode)first).getTreeNode(hash, key); ++ else { ++ Node e = first; K k; ++ do { ++ if (e.hash == hash && ++ ((k = e.key) == key || (key != null && key.equals(k)))) { ++ old = e; ++ break; ++ } ++ ++binCount; ++ } while ((e = e.next) != null); ++ } ++ V oldValue; ++ if (old != null && (oldValue = old.value) != null) { ++ afterNodeAccess(old); ++ return oldValue; ++ } ++ } ++ int mc = modCount; ++ V v = mappingFunction.apply(key); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (v == null) { ++ return null; ++ } else if (old != null) { ++ old.value = v; ++ afterNodeAccess(old); ++ return v; ++ } ++ else if (t != null) ++ t.putTreeVal(this, tab, hash, key, v); ++ else { ++ tab[i] = newNode(hash, key, v, first); ++ if (binCount >= TREEIFY_THRESHOLD - 1) ++ treeifyBin(tab, hash); ++ } ++ modCount = mc + 1; ++ ++size; ++ afterNodeInsertion(true); ++ return v; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

This method will, on a best-effort basis, throw a ++ * {@link ConcurrentModificationException} if it is detected that the ++ * remapping function modifies this map during computation. ++ * ++ * @throws ConcurrentModificationException if it is detected that the ++ * remapping function modified this map ++ */ ++ @Override ++ public V computeIfPresent(K key, ++ BiFunction remappingFunction) { ++ if (remappingFunction == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap(key)) { ++ return primHashMapComputeIfPresent(key, remappingFunction); ++ } ++ Node e; V oldValue; ++ if ((e = getNode(key)) != null && ++ (oldValue = e.value) != null) { ++ int mc = modCount; ++ V v = remappingFunction.apply(key, oldValue); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (v != null) { ++ e.value = v; ++ afterNodeAccess(e); ++ return v; ++ } ++ else { ++ int hash = hash(key); ++ removeNode(hash, key, null, false, true); ++ } ++ } ++ return null; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

This method will, on a best-effort basis, throw a ++ * {@link ConcurrentModificationException} if it is detected that the ++ * remapping function modifies this map during computation. ++ * ++ * @throws ConcurrentModificationException if it is detected that the ++ * remapping function modified this map ++ */ ++ @Override ++ public V compute(K key, ++ BiFunction remappingFunction) { ++ if (remappingFunction == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap(key)) { ++ return primHashMapCompute(key, remappingFunction); ++ } ++ int hash = hash(key); ++ Node[] tab; Node first; int n, i; ++ int binCount = 0; ++ TreeNode t = null; ++ Node old = null; ++ if (size > threshold || (tab = table) == null || ++ (n = tab.length) == 0) ++ n = (tab = resize()).length; ++ if ((first = tab[i = (n - 1) & hash]) != null) { ++ if (first instanceof TreeNode) ++ old = (t = (TreeNode)first).getTreeNode(hash, key); ++ else { ++ Node e = first; K k; ++ do { ++ if (e.hash == hash && ++ ((k = e.key) == key || (key != null && key.equals(k)))) { ++ old = e; ++ break; ++ } ++ ++binCount; ++ } while ((e = e.next) != null); ++ } ++ } ++ V oldValue = (old == null) ? null : old.value; ++ int mc = modCount; ++ V v = remappingFunction.apply(key, oldValue); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (old != null) { ++ if (v != null) { ++ old.value = v; ++ afterNodeAccess(old); ++ } ++ else ++ removeNode(hash, key, null, false, true); ++ } ++ else if (v != null) { ++ if (t != null) ++ t.putTreeVal(this, tab, hash, key, v); ++ else { ++ tab[i] = newNode(hash, key, v, first); ++ if (binCount >= TREEIFY_THRESHOLD - 1) ++ treeifyBin(tab, hash); ++ } ++ modCount = mc + 1; ++ ++size; ++ afterNodeInsertion(true); ++ } ++ return v; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

This method will, on a best-effort basis, throw a ++ * {@link ConcurrentModificationException} if it is detected that the ++ * remapping function modifies this map during computation. ++ * ++ * @throws ConcurrentModificationException if it is detected that the ++ * remapping function modified this map ++ */ ++ @Override ++ public V merge(K key, V value, ++ BiFunction remappingFunction) { ++ if (value == null || remappingFunction == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap(key)) { ++ return primHashMapMerge(key, value, remappingFunction); ++ } ++ int hash = hash(key); ++ Node[] tab; Node first; int n, i; ++ int binCount = 0; ++ TreeNode t = null; ++ Node old = null; ++ if (size > threshold || (tab = table) == null || ++ (n = tab.length) == 0) ++ n = (tab = resize()).length; ++ if ((first = tab[i = (n - 1) & hash]) != null) { ++ if (first instanceof TreeNode) ++ old = (t = (TreeNode)first).getTreeNode(hash, key); ++ else { ++ Node e = first; K k; ++ do { ++ if (e.hash == hash && ++ ((k = e.key) == key || (key != null && key.equals(k)))) { ++ old = e; ++ break; ++ } ++ ++binCount; ++ } while ((e = e.next) != null); ++ } ++ } ++ if (old != null) { ++ V v; ++ if (old.value != null) { ++ int mc = modCount; ++ v = remappingFunction.apply(old.value, value); ++ if (mc != modCount) { ++ throw new ConcurrentModificationException(); ++ } ++ } else { ++ v = value; ++ } ++ if (v != null) { ++ old.value = v; ++ afterNodeAccess(old); ++ } ++ else ++ removeNode(hash, key, null, false, true); ++ return v; ++ } else { ++ if (t != null) ++ t.putTreeVal(this, tab, hash, key, value); ++ else { ++ tab[i] = newNode(hash, key, value, first); ++ if (binCount >= TREEIFY_THRESHOLD - 1) ++ treeifyBin(tab, hash); ++ } ++ ++modCount; ++ ++size; ++ afterNodeInsertion(true); ++ return value; ++ } ++ } ++ ++ @Override ++ public void forEach(BiConsumer action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ if (usePrimHashMap()) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ action.accept(null, primMapValOfNullKey); ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ V[] values = primMapValues; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ action.accept(castKeyToGenericType(keys[i]), values[i]); ++ --remaining; ++ } ++ } ++ } ++ if (remaining != 0) { ++ throw new ConcurrentModificationException(); ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) ++ action.accept(e.key, e.value); ++ } ++ } ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ ++ @Override ++ public void replaceAll(BiFunction function) { ++ if (function == null) ++ throw new NullPointerException(); ++ if (usePrimHashMap()) { ++ int mc = modCount; ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ primMapValOfNullKey = function.apply(null, primMapValOfNullKey); ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ V[] values = primMapValues; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ values[i] = function.apply(castKeyToGenericType(keys[i]), values[i]); ++ --remaining; ++ } ++ } ++ } ++ if (remaining != 0) { ++ throw new ConcurrentModificationException(); ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ int mc = modCount; ++ for (Node e : tab) { ++ for (; e != null; e = e.next) { ++ e.value = function.apply(e.key, e.value); ++ } ++ } ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // Cloning and serialization ++ ++ /** ++ * Returns a shallow copy of this {@code HashMap} instance: the keys and ++ * values themselves are not cloned. ++ * ++ * @return a shallow copy of this map ++ */ ++ @SuppressWarnings("unchecked") ++ @Override ++ public Object clone() { ++ HashMap result; ++ try { ++ result = (HashMap)super.clone(); ++ } catch (CloneNotSupportedException e) { ++ // this shouldn't happen, since we are Cloneable ++ throw new InternalError(e); ++ } ++ result.reinitialize(); ++ result.putMapEntries(this, false); ++ return result; ++ } ++ ++ // These methods are also used when serializing HashSets ++ final float loadFactor() { return loadFactor; } ++ final int capacity() { ++ if (usePrimHashMap()) { ++ return (primMapValids != null) ? primMapValids.length : ++ (threshold > 0) ? threshold : ++ DEFAULT_INITIAL_CAPACITY; ++ } ++ return (table != null) ? table.length : ++ (threshold > 0) ? threshold : ++ DEFAULT_INITIAL_CAPACITY; ++ } ++ ++ /** ++ * Saves this map to a stream (that is, serializes it). ++ * ++ * @param s the stream ++ * @throws IOException if an I/O error occurs ++ * @serialData The capacity of the HashMap (the length of the ++ * bucket array) is emitted (int), followed by the ++ * size (an int, the number of key-value ++ * mappings), followed by the key (Object) and value (Object) ++ * for each key-value mapping. The key-value mappings are ++ * emitted in no particular order. ++ */ ++ @java.io.Serial ++ private void writeObject(java.io.ObjectOutputStream s) ++ throws IOException { ++ int buckets = capacity(); ++ // Write out the threshold, loadfactor, and any hidden stuff ++ s.defaultWriteObject(); ++ s.writeInt(buckets); ++ s.writeInt(size); ++ internalWriteEntries(s); ++ } ++ ++ /** ++ * Reconstitutes this map from a stream (that is, deserializes it). ++ * @param s the stream ++ * @throws ClassNotFoundException if the class of a serialized object ++ * could not be found ++ * @throws IOException if an I/O error occurs ++ */ ++ @java.io.Serial ++ private void readObject(ObjectInputStream s) ++ throws IOException, ClassNotFoundException { ++ ++ ObjectInputStream.GetField fields = s.readFields(); ++ ++ // Read loadFactor (ignore threshold) ++ float lf = fields.get("loadFactor", 0.75f); ++ if (lf <= 0 || Float.isNaN(lf)) ++ throw new InvalidObjectException("Illegal load factor: " + lf); ++ ++ lf = Math.min(Math.max(0.25f, lf), 4.0f); ++ HashMap.UnsafeHolder.putLoadFactor(this, lf); ++ ++ reinitialize(); ++ ++ s.readInt(); // Read and ignore number of buckets ++ int mappings = s.readInt(); // Read number of mappings (size) ++ if (mappings < 0) { ++ throw new InvalidObjectException("Illegal mappings count: " + mappings); ++ } else if (mappings == 0) { ++ // use defaults ++ } else if (mappings > 0) { ++ float fc = (float)mappings / lf + 1.0f; ++ int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? ++ DEFAULT_INITIAL_CAPACITY : ++ (fc >= MAXIMUM_CAPACITY) ? ++ MAXIMUM_CAPACITY : ++ tableSizeFor((int)fc)); ++ // null table will use threshold as initial size. ++ // set threshold to cap here to make sure that table.size is power of 2 ++ threshold = cap; ++ ++ // Check Map.Entry[].class since it's the nearest public type to ++ // what we're actually creating. ++ SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap); ++ ++ // Read the keys and values, and put the mappings in the HashMap ++ for (int i = 0; i < mappings; i++) { ++ @SuppressWarnings("unchecked") ++ K key = (K) s.readObject(); ++ @SuppressWarnings("unchecked") ++ V value = (V) s.readObject(); ++ if (usePrimHashMap(key)) { ++ primHashMapPutVal((Long)key, value,false); ++ } else { ++ putVal(key, value, false, false, true); ++ } ++ } ++ } ++ } ++ ++ // Support for resetting final field during deserializing ++ private static final class UnsafeHolder { ++ private UnsafeHolder() { throw new InternalError(); } ++ private static final jdk.internal.misc.Unsafe unsafe ++ = jdk.internal.misc.Unsafe.getUnsafe(); ++ private static final long LF_OFFSET ++ = unsafe.objectFieldOffset(HashMap.class, "loadFactor"); ++ static void putLoadFactor(HashMap map, float lf) { ++ unsafe.putFloat(map, LF_OFFSET, lf); ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // iterators ++ ++ abstract class HashIterator { ++ Node next; // next entry to return ++ Node current; // current entry ++ int expectedModCount; // for fast-fail ++ int index; // current slot ++ ++ HashIterator() { ++ expectedModCount = modCount; ++ Node[] t = table; ++ current = next = null; ++ index = 0; ++ if (t != null && size > 0) { // advance to first entry ++ do {} while (index < t.length && (next = t[index++]) == null); ++ } ++ } ++ ++ public final boolean hasNext() { ++ return next != null; ++ } ++ ++ final Node nextNode() { ++ Node[] t; ++ Node e = next; ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ if (e == null) ++ throw new NoSuchElementException(); ++ if ((next = (current = e).next) == null && (t = table) != null) { ++ do {} while (index < t.length && (next = t[index++]) == null); ++ } ++ return e; ++ } ++ ++ public final void remove() { ++ Node p = current; ++ if (p == null) ++ throw new IllegalStateException(); ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ current = null; ++ removeNode(p.hash, p.key, null, false, false); ++ expectedModCount = modCount; ++ } ++ } ++ ++ final class KeyIterator extends HashIterator ++ implements Iterator { ++ public final K next() { return nextNode().key; } ++ } ++ ++ final class ValueIterator extends HashIterator ++ implements Iterator { ++ public final V next() { return nextNode().value; } ++ } ++ ++ final class EntryIterator extends HashIterator ++ implements Iterator> { ++ public final Map.Entry next() { return nextNode(); } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // spliterators ++ ++ static class HashMapSpliterator { ++ final HashMap map; ++ Node current; // current node ++ int index; // current index, modified on advance/split ++ int fence; // one past last index ++ int est; // size estimate ++ int expectedModCount; // for comodification checks ++ ++ HashMapSpliterator(HashMap m, int origin, ++ int fence, int est, ++ int expectedModCount) { ++ // rollback to genericMap if this static class is created directly ++ // e.g. HashSet.spliterator ++ m.rollbackToGenericMap(); ++ this.map = m; ++ this.index = origin; ++ this.fence = fence; ++ this.est = est; ++ this.expectedModCount = expectedModCount; ++ } ++ ++ final int getFence() { // initialize fence and size on first use ++ int hi; ++ if ((hi = fence) < 0) { ++ HashMap m = map; ++ est = m.size; ++ expectedModCount = m.modCount; ++ Node[] tab = m.table; ++ hi = fence = (tab == null) ? 0 : tab.length; ++ } ++ return hi; ++ } ++ ++ public final long estimateSize() { ++ getFence(); // force init ++ return (long) est; ++ } ++ } ++ ++ static final class KeySpliterator ++ extends HashMapSpliterator ++ implements Spliterator { ++ KeySpliterator(HashMap m, int origin, int fence, int est, ++ int expectedModCount) { ++ super(m, origin, fence, est, expectedModCount); ++ } ++ ++ public KeySpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid || current != null) ? null : ++ new KeySpliterator<>(map, lo, index = mid, est >>>= 1, ++ expectedModCount); ++ } ++ ++ public void forEachRemaining(Consumer action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ HashMap m = map; ++ Node[] tab = m.table; ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = m.modCount; ++ hi = fence = (tab == null) ? 0 : tab.length; ++ } ++ else ++ mc = expectedModCount; ++ if (tab != null && tab.length >= hi && ++ (i = index) >= 0 && (i < (index = hi) || current != null)) { ++ Node p = current; ++ current = null; ++ do { ++ if (p == null) ++ p = tab[i++]; ++ else { ++ action.accept(p.key); ++ p = p.next; ++ } ++ } while (p != null || i < hi); ++ if (m.modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ public boolean tryAdvance(Consumer action) { ++ int hi; ++ if (action == null) ++ throw new NullPointerException(); ++ Node[] tab = map.table; ++ if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { ++ while (current != null || index < hi) { ++ if (current == null) ++ current = tab[index++]; ++ else { ++ K k = current.key; ++ current = current.next; ++ action.accept(k); ++ if (map.modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | ++ Spliterator.DISTINCT; ++ } ++ } ++ ++ static final class ValueSpliterator ++ extends HashMapSpliterator ++ implements Spliterator { ++ ValueSpliterator(HashMap m, int origin, int fence, int est, ++ int expectedModCount) { ++ super(m, origin, fence, est, expectedModCount); ++ } ++ ++ public ValueSpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid || current != null) ? null : ++ new ValueSpliterator<>(map, lo, index = mid, est >>>= 1, ++ expectedModCount); ++ } ++ ++ public void forEachRemaining(Consumer action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ HashMap m = map; ++ Node[] tab = m.table; ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = m.modCount; ++ hi = fence = (tab == null) ? 0 : tab.length; ++ } ++ else ++ mc = expectedModCount; ++ if (tab != null && tab.length >= hi && ++ (i = index) >= 0 && (i < (index = hi) || current != null)) { ++ Node p = current; ++ current = null; ++ do { ++ if (p == null) ++ p = tab[i++]; ++ else { ++ action.accept(p.value); ++ p = p.next; ++ } ++ } while (p != null || i < hi); ++ if (m.modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ public boolean tryAdvance(Consumer action) { ++ int hi; ++ if (action == null) ++ throw new NullPointerException(); ++ Node[] tab = map.table; ++ if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { ++ while (current != null || index < hi) { ++ if (current == null) ++ current = tab[index++]; ++ else { ++ V v = current.value; ++ current = current.next; ++ action.accept(v); ++ if (map.modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == map.size ? Spliterator.SIZED : 0); ++ } ++ } ++ ++ static final class EntrySpliterator ++ extends HashMapSpliterator ++ implements Spliterator> { ++ EntrySpliterator(HashMap m, int origin, int fence, int est, ++ int expectedModCount) { ++ super(m, origin, fence, est, expectedModCount); ++ } ++ ++ public EntrySpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid || current != null) ? null : ++ new EntrySpliterator<>(map, lo, index = mid, est >>>= 1, ++ expectedModCount); ++ } ++ ++ public void forEachRemaining(Consumer> action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ HashMap m = map; ++ Node[] tab = m.table; ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = m.modCount; ++ hi = fence = (tab == null) ? 0 : tab.length; ++ } ++ else ++ mc = expectedModCount; ++ if (tab != null && tab.length >= hi && ++ (i = index) >= 0 && (i < (index = hi) || current != null)) { ++ Node p = current; ++ current = null; ++ do { ++ if (p == null) ++ p = tab[i++]; ++ else { ++ action.accept(p); ++ p = p.next; ++ } ++ } while (p != null || i < hi); ++ if (m.modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ public boolean tryAdvance(Consumer> action) { ++ int hi; ++ if (action == null) ++ throw new NullPointerException(); ++ Node[] tab = map.table; ++ if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { ++ while (current != null || index < hi) { ++ if (current == null) ++ current = tab[index++]; ++ else { ++ Node e = current; ++ current = current.next; ++ action.accept(e); ++ if (map.modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | ++ Spliterator.DISTINCT; ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // LinkedHashMap support ++ ++ ++ /* ++ * The following package-protected methods are designed to be ++ * overridden by LinkedHashMap, but not by any other subclass. ++ * Nearly all other internal methods are also package-protected ++ * but are declared final, so can be used by LinkedHashMap, view ++ * classes, and HashSet. ++ */ ++ ++ // Create a regular (non-tree) node ++ Node newNode(int hash, K key, V value, Node next) { ++ return new Node<>(hash, key, value, next); ++ } ++ ++ // For conversion from TreeNodes to plain nodes ++ Node replacementNode(Node p, Node next) { ++ return new Node<>(p.hash, p.key, p.value, next); ++ } ++ ++ // Create a tree bin node ++ TreeNode newTreeNode(int hash, K key, V value, Node next) { ++ return new TreeNode<>(hash, key, value, next); ++ } ++ ++ // For treeifyBin ++ TreeNode replacementTreeNode(Node p, Node next) { ++ return new TreeNode<>(p.hash, p.key, p.value, next); ++ } ++ ++ /** ++ * Reset to initial default state. Called by clone and readObject. ++ */ ++ void reinitialize() { ++ table = null; ++ entrySet = null; ++ keySet = null; ++ values = null; ++ modCount = 0; ++ threshold = 0; ++ size = 0; ++ primMapValids = null; ++ primMapKeys = null; ++ primMapValues = null; ++ primMapNullKeyValid = false; ++ primMapValOfNullKey = null; ++ // loadFactor should be le than 0.8f to make sure there is at least one empty slot for prim raw array ++ if (loadFactor > MAX_LOAD_FACTOR_FOR_PRIM_MAP) { ++ disablePrimHashMap(); ++ } else { ++ initUsingPrimHashMap(); ++ } ++ } ++ ++ // Callbacks to allow LinkedHashMap post-actions ++ void afterNodeAccess(Node p) { } ++ void afterNodeInsertion(boolean evict) { } ++ void afterNodeRemoval(Node p) { } ++ ++ // Called only from writeObject, to ensure compatible ordering. ++ void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { ++ int remaining = size; ++ if (usePrimHashMap()) { ++ if (primMapNullKeyValid) { ++ s.writeObject(null); ++ s.writeObject(primMapValOfNullKey); ++ --remaining; ++ } ++ if (remaining > 0) { ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ V[] values = primMapValues; ++ int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ s.writeObject(castKeyToGenericType(keys[i])); ++ s.writeObject(values[i]); ++ --remaining; ++ } ++ } ++ } ++ } else { ++ Node[] tab; ++ if (size > 0 && (tab = table) != null) { ++ for (Node e : tab) { ++ for (; e != null; e = e.next) { ++ s.writeObject(e.key); ++ s.writeObject(e.value); ++ --remaining; ++ } ++ } ++ } ++ } ++ ++ if (remaining != 0) { ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // Tree bins ++ ++ /** ++ * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn ++ * extends Node) so can be used as extension of either regular or ++ * linked node. ++ */ ++ static final class TreeNode extends LinkedHashMap.Entry { ++ TreeNode parent; // red-black tree links ++ TreeNode left; ++ TreeNode right; ++ TreeNode prev; // needed to unlink next upon deletion ++ boolean red; ++ TreeNode(int hash, K key, V val, Node next) { ++ super(hash, key, val, next); ++ } ++ ++ /** ++ * Returns root of tree containing this node. ++ */ ++ final TreeNode root() { ++ for (TreeNode r = this, p;;) { ++ if ((p = r.parent) == null) ++ return r; ++ r = p; ++ } ++ } ++ ++ /** ++ * Ensures that the given root is the first node of its bin. ++ */ ++ static void moveRootToFront(Node[] tab, TreeNode root) { ++ int n; ++ if (root != null && tab != null && (n = tab.length) > 0) { ++ int index = (n - 1) & root.hash; ++ TreeNode first = (TreeNode)tab[index]; ++ if (root != first) { ++ Node rn; ++ tab[index] = root; ++ TreeNode rp = root.prev; ++ if ((rn = root.next) != null) ++ ((TreeNode)rn).prev = rp; ++ if (rp != null) ++ rp.next = rn; ++ if (first != null) ++ first.prev = root; ++ root.next = first; ++ root.prev = null; ++ } ++ assert checkInvariants(root); ++ } ++ } ++ ++ /** ++ * Finds the node starting at root p with the given hash and key. ++ * The kc argument caches comparableClassFor(key) upon first use ++ * comparing keys. ++ */ ++ final TreeNode find(int h, Object k, Class kc) { ++ TreeNode p = this; ++ do { ++ int ph, dir; K pk; ++ TreeNode pl = p.left, pr = p.right, q; ++ if ((ph = p.hash) > h) ++ p = pl; ++ else if (ph < h) ++ p = pr; ++ else if ((pk = p.key) == k || (k != null && k.equals(pk))) ++ return p; ++ else if (pl == null) ++ p = pr; ++ else if (pr == null) ++ p = pl; ++ else if ((kc != null || ++ (kc = comparableClassFor(k)) != null) && ++ (dir = compareComparables(kc, k, pk)) != 0) ++ p = (dir < 0) ? pl : pr; ++ else if ((q = pr.find(h, k, kc)) != null) ++ return q; ++ else ++ p = pl; ++ } while (p != null); ++ return null; ++ } ++ ++ /** ++ * Calls find for root node. ++ */ ++ final TreeNode getTreeNode(int h, Object k) { ++ return ((parent != null) ? root() : this).find(h, k, null); ++ } ++ ++ /** ++ * Tie-breaking utility for ordering insertions when equal ++ * hashCodes and non-comparable. We don't require a total ++ * order, just a consistent insertion rule to maintain ++ * equivalence across rebalancings. Tie-breaking further than ++ * necessary simplifies testing a bit. ++ */ ++ static int tieBreakOrder(Object a, Object b) { ++ int d; ++ if (a == null || b == null || ++ (d = a.getClass().getName(). ++ compareTo(b.getClass().getName())) == 0) ++ d = (System.identityHashCode(a) <= System.identityHashCode(b) ? ++ -1 : 1); ++ return d; ++ } ++ ++ /** ++ * Forms tree of the nodes linked from this node. ++ */ ++ final void treeify(Node[] tab) { ++ TreeNode root = null; ++ for (TreeNode x = this, next; x != null; x = next) { ++ next = (TreeNode)x.next; ++ x.left = x.right = null; ++ if (root == null) { ++ x.parent = null; ++ x.red = false; ++ root = x; ++ } ++ else { ++ K k = x.key; ++ int h = x.hash; ++ Class kc = null; ++ for (TreeNode p = root;;) { ++ int dir, ph; ++ K pk = p.key; ++ if ((ph = p.hash) > h) ++ dir = -1; ++ else if (ph < h) ++ dir = 1; ++ else if ((kc == null && ++ (kc = comparableClassFor(k)) == null) || ++ (dir = compareComparables(kc, k, pk)) == 0) ++ dir = tieBreakOrder(k, pk); ++ ++ TreeNode xp = p; ++ if ((p = (dir <= 0) ? p.left : p.right) == null) { ++ x.parent = xp; ++ if (dir <= 0) ++ xp.left = x; ++ else ++ xp.right = x; ++ root = balanceInsertion(root, x); ++ break; ++ } ++ } ++ } ++ } ++ moveRootToFront(tab, root); ++ } ++ ++ /** ++ * Returns a list of non-TreeNodes replacing those linked from ++ * this node. ++ */ ++ final Node untreeify(HashMap map) { ++ Node hd = null, tl = null; ++ for (Node q = this; q != null; q = q.next) { ++ Node p = map.replacementNode(q, null); ++ if (tl == null) ++ hd = p; ++ else ++ tl.next = p; ++ tl = p; ++ } ++ return hd; ++ } ++ ++ /** ++ * Tree version of putVal. ++ */ ++ final TreeNode putTreeVal(HashMap map, Node[] tab, ++ int h, K k, V v) { ++ Class kc = null; ++ boolean searched = false; ++ TreeNode root = (parent != null) ? root() : this; ++ for (TreeNode p = root;;) { ++ int dir, ph; K pk; ++ if ((ph = p.hash) > h) ++ dir = -1; ++ else if (ph < h) ++ dir = 1; ++ else if ((pk = p.key) == k || (k != null && k.equals(pk))) ++ return p; ++ else if ((kc == null && ++ (kc = comparableClassFor(k)) == null) || ++ (dir = compareComparables(kc, k, pk)) == 0) { ++ if (!searched) { ++ TreeNode q, ch; ++ searched = true; ++ if (((ch = p.left) != null && ++ (q = ch.find(h, k, kc)) != null) || ++ ((ch = p.right) != null && ++ (q = ch.find(h, k, kc)) != null)) ++ return q; ++ } ++ dir = tieBreakOrder(k, pk); ++ } ++ ++ TreeNode xp = p; ++ if ((p = (dir <= 0) ? p.left : p.right) == null) { ++ Node xpn = xp.next; ++ TreeNode x = map.newTreeNode(h, k, v, xpn); ++ if (dir <= 0) ++ xp.left = x; ++ else ++ xp.right = x; ++ xp.next = x; ++ x.parent = x.prev = xp; ++ if (xpn != null) ++ ((TreeNode)xpn).prev = x; ++ moveRootToFront(tab, balanceInsertion(root, x)); ++ return null; ++ } ++ } ++ } ++ ++ /** ++ * Removes the given node, that must be present before this call. ++ * This is messier than typical red-black deletion code because we ++ * cannot swap the contents of an interior node with a leaf ++ * successor that is pinned by "next" pointers that are accessible ++ * independently during traversal. So instead we swap the tree ++ * linkages. If the current tree appears to have too few nodes, ++ * the bin is converted back to a plain bin. (The test triggers ++ * somewhere between 2 and 6 nodes, depending on tree structure). ++ */ ++ final void removeTreeNode(HashMap map, Node[] tab, ++ boolean movable) { ++ int n; ++ if (tab == null || (n = tab.length) == 0) ++ return; ++ int index = (n - 1) & hash; ++ TreeNode first = (TreeNode)tab[index], root = first, rl; ++ TreeNode succ = (TreeNode)next, pred = prev; ++ if (pred == null) ++ tab[index] = first = succ; ++ else ++ pred.next = succ; ++ if (succ != null) ++ succ.prev = pred; ++ if (first == null) ++ return; ++ if (root.parent != null) ++ root = root.root(); ++ if (root == null ++ || (movable ++ && (root.right == null ++ || (rl = root.left) == null ++ || rl.left == null))) { ++ tab[index] = first.untreeify(map); // too small ++ return; ++ } ++ TreeNode p = this, pl = left, pr = right, replacement; ++ if (pl != null && pr != null) { ++ TreeNode s = pr, sl; ++ while ((sl = s.left) != null) // find successor ++ s = sl; ++ boolean c = s.red; s.red = p.red; p.red = c; // swap colors ++ TreeNode sr = s.right; ++ TreeNode pp = p.parent; ++ if (s == pr) { // p was s's direct parent ++ p.parent = s; ++ s.right = p; ++ } ++ else { ++ TreeNode sp = s.parent; ++ if ((p.parent = sp) != null) { ++ if (s == sp.left) ++ sp.left = p; ++ else ++ sp.right = p; ++ } ++ if ((s.right = pr) != null) ++ pr.parent = s; ++ } ++ p.left = null; ++ if ((p.right = sr) != null) ++ sr.parent = p; ++ if ((s.left = pl) != null) ++ pl.parent = s; ++ if ((s.parent = pp) == null) ++ root = s; ++ else if (p == pp.left) ++ pp.left = s; ++ else ++ pp.right = s; ++ if (sr != null) ++ replacement = sr; ++ else ++ replacement = p; ++ } ++ else if (pl != null) ++ replacement = pl; ++ else if (pr != null) ++ replacement = pr; ++ else ++ replacement = p; ++ if (replacement != p) { ++ TreeNode pp = replacement.parent = p.parent; ++ if (pp == null) ++ (root = replacement).red = false; ++ else if (p == pp.left) ++ pp.left = replacement; ++ else ++ pp.right = replacement; ++ p.left = p.right = p.parent = null; ++ } ++ ++ TreeNode r = p.red ? root : balanceDeletion(root, replacement); ++ ++ if (replacement == p) { // detach ++ TreeNode pp = p.parent; ++ p.parent = null; ++ if (pp != null) { ++ if (p == pp.left) ++ pp.left = null; ++ else if (p == pp.right) ++ pp.right = null; ++ } ++ } ++ if (movable) ++ moveRootToFront(tab, r); ++ } ++ ++ /** ++ * Splits nodes in a tree bin into lower and upper tree bins, ++ * or untreeifies if now too small. Called only from resize; ++ * see above discussion about split bits and indices. ++ * ++ * @param map the map ++ * @param tab the table for recording bin heads ++ * @param index the index of the table being split ++ * @param bit the bit of hash to split on ++ */ ++ final void split(HashMap map, Node[] tab, int index, int bit) { ++ TreeNode b = this; ++ // Relink into lo and hi lists, preserving order ++ TreeNode loHead = null, loTail = null; ++ TreeNode hiHead = null, hiTail = null; ++ int lc = 0, hc = 0; ++ for (TreeNode e = b, next; e != null; e = next) { ++ next = (TreeNode)e.next; ++ e.next = null; ++ if ((e.hash & bit) == 0) { ++ if ((e.prev = loTail) == null) ++ loHead = e; ++ else ++ loTail.next = e; ++ loTail = e; ++ ++lc; ++ } ++ else { ++ if ((e.prev = hiTail) == null) ++ hiHead = e; ++ else ++ hiTail.next = e; ++ hiTail = e; ++ ++hc; ++ } ++ } ++ ++ if (loHead != null) { ++ if (lc <= UNTREEIFY_THRESHOLD) ++ tab[index] = loHead.untreeify(map); ++ else { ++ tab[index] = loHead; ++ if (hiHead != null) // (else is already treeified) ++ loHead.treeify(tab); ++ } ++ } ++ if (hiHead != null) { ++ if (hc <= UNTREEIFY_THRESHOLD) ++ tab[index + bit] = hiHead.untreeify(map); ++ else { ++ tab[index + bit] = hiHead; ++ if (loHead != null) ++ hiHead.treeify(tab); ++ } ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // Red-black tree methods, all adapted from CLR ++ ++ static TreeNode rotateLeft(TreeNode root, ++ TreeNode p) { ++ TreeNode r, pp, rl; ++ if (p != null && (r = p.right) != null) { ++ if ((rl = p.right = r.left) != null) ++ rl.parent = p; ++ if ((pp = r.parent = p.parent) == null) ++ (root = r).red = false; ++ else if (pp.left == p) ++ pp.left = r; ++ else ++ pp.right = r; ++ r.left = p; ++ p.parent = r; ++ } ++ return root; ++ } ++ ++ static TreeNode rotateRight(TreeNode root, ++ TreeNode p) { ++ TreeNode l, pp, lr; ++ if (p != null && (l = p.left) != null) { ++ if ((lr = p.left = l.right) != null) ++ lr.parent = p; ++ if ((pp = l.parent = p.parent) == null) ++ (root = l).red = false; ++ else if (pp.right == p) ++ pp.right = l; ++ else ++ pp.left = l; ++ l.right = p; ++ p.parent = l; ++ } ++ return root; ++ } ++ ++ static TreeNode balanceInsertion(TreeNode root, ++ TreeNode x) { ++ x.red = true; ++ for (TreeNode xp, xpp, xppl, xppr;;) { ++ if ((xp = x.parent) == null) { ++ x.red = false; ++ return x; ++ } ++ else if (!xp.red || (xpp = xp.parent) == null) ++ return root; ++ if (xp == (xppl = xpp.left)) { ++ if ((xppr = xpp.right) != null && xppr.red) { ++ xppr.red = false; ++ xp.red = false; ++ xpp.red = true; ++ x = xpp; ++ } ++ else { ++ if (x == xp.right) { ++ root = rotateLeft(root, x = xp); ++ xpp = (xp = x.parent) == null ? null : xp.parent; ++ } ++ if (xp != null) { ++ xp.red = false; ++ if (xpp != null) { ++ xpp.red = true; ++ root = rotateRight(root, xpp); ++ } ++ } ++ } ++ } ++ else { ++ if (xppl != null && xppl.red) { ++ xppl.red = false; ++ xp.red = false; ++ xpp.red = true; ++ x = xpp; ++ } ++ else { ++ if (x == xp.left) { ++ root = rotateRight(root, x = xp); ++ xpp = (xp = x.parent) == null ? null : xp.parent; ++ } ++ if (xp != null) { ++ xp.red = false; ++ if (xpp != null) { ++ xpp.red = true; ++ root = rotateLeft(root, xpp); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ static TreeNode balanceDeletion(TreeNode root, ++ TreeNode x) { ++ for (TreeNode xp, xpl, xpr;;) { ++ if (x == null || x == root) ++ return root; ++ else if ((xp = x.parent) == null) { ++ x.red = false; ++ return x; ++ } ++ else if (x.red) { ++ x.red = false; ++ return root; ++ } ++ else if ((xpl = xp.left) == x) { ++ if ((xpr = xp.right) != null && xpr.red) { ++ xpr.red = false; ++ xp.red = true; ++ root = rotateLeft(root, xp); ++ xpr = (xp = x.parent) == null ? null : xp.right; ++ } ++ if (xpr == null) ++ x = xp; ++ else { ++ TreeNode sl = xpr.left, sr = xpr.right; ++ if ((sr == null || !sr.red) && ++ (sl == null || !sl.red)) { ++ xpr.red = true; ++ x = xp; ++ } ++ else { ++ if (sr == null || !sr.red) { ++ if (sl != null) ++ sl.red = false; ++ xpr.red = true; ++ root = rotateRight(root, xpr); ++ xpr = (xp = x.parent) == null ? ++ null : xp.right; ++ } ++ if (xpr != null) { ++ xpr.red = (xp == null) ? false : xp.red; ++ if ((sr = xpr.right) != null) ++ sr.red = false; ++ } ++ if (xp != null) { ++ xp.red = false; ++ root = rotateLeft(root, xp); ++ } ++ x = root; ++ } ++ } ++ } ++ else { // symmetric ++ if (xpl != null && xpl.red) { ++ xpl.red = false; ++ xp.red = true; ++ root = rotateRight(root, xp); ++ xpl = (xp = x.parent) == null ? null : xp.left; ++ } ++ if (xpl == null) ++ x = xp; ++ else { ++ TreeNode sl = xpl.left, sr = xpl.right; ++ if ((sl == null || !sl.red) && ++ (sr == null || !sr.red)) { ++ xpl.red = true; ++ x = xp; ++ } ++ else { ++ if (sl == null || !sl.red) { ++ if (sr != null) ++ sr.red = false; ++ xpl.red = true; ++ root = rotateLeft(root, xpl); ++ xpl = (xp = x.parent) == null ? ++ null : xp.left; ++ } ++ if (xpl != null) { ++ xpl.red = (xp == null) ? false : xp.red; ++ if ((sl = xpl.left) != null) ++ sl.red = false; ++ } ++ if (xp != null) { ++ xp.red = false; ++ root = rotateRight(root, xp); ++ } ++ x = root; ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Recursive invariant check ++ */ ++ static boolean checkInvariants(TreeNode t) { ++ TreeNode tp = t.parent, tl = t.left, tr = t.right, ++ tb = t.prev, tn = (TreeNode)t.next; ++ if (tb != null && tb.next != t) ++ return false; ++ if (tn != null && tn.prev != t) ++ return false; ++ if (tp != null && t != tp.left && t != tp.right) ++ return false; ++ if (tl != null && (tl.parent != t || tl.hash > t.hash)) ++ return false; ++ if (tr != null && (tr.parent != t || tr.hash < t.hash)) ++ return false; ++ if (t.red && tl != null && tl.red && tr != null && tr.red) ++ return false; ++ if (tl != null && !checkInvariants(tl)) ++ return false; ++ if (tr != null && !checkInvariants(tr)) ++ return false; ++ return true; ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // Primitive long HashMap support ++ ++ @SuppressWarnings("unchecked") ++ private K castKeyToGenericType(long key) { ++ return (K)(Long)(key); ++ } ++ ++ private void initUsingPrimHashMap() { ++ usingPrimHashMap = true; ++ primMapNullKeyValid = false; ++ primMapValOfNullKey = null; ++ } ++ ++ /** ++ * set usingPrimHashMap to false to disable primitive long hashmap ++ */ ++ final protected void disablePrimHashMap() { ++ usingPrimHashMap = false; ++ } ++ ++ private boolean usePrimHashMap() { ++ return usingPrimHashMap; ++ } ++ ++ private boolean usePrimHashMap(Object key) { ++ if (!usingPrimHashMap) { ++ return false; ++ } ++ if (key != null && !(key instanceof Long)) { ++ rollbackToGenericMap(); ++ return false; ++ } ++ return true; ++ } ++ ++ /** ++ * This method copy prim hash map's key-values to Node[] table ++ * and then set usingPrimHashMap to false ++ */ ++ private void rollbackToGenericMap() { ++ if (!usePrimHashMap()) { ++ return ; ++ } ++ // add lock to prevent multiple threads from executing the following rollback code: ++ synchronized (this) { ++ if (!usePrimHashMap()) { ++ return ; ++ } ++ if (size > 0) { ++ // null table will use threshold as initial length. ++ // set threshold according size here to make sure that table.size is power of 2 ++ threshold = tableSizeFor(size); ++ // take a snapshot to detect concurrent modification ++ int expectedSize = size; ++ int expectedCount = modCount; ++ int remaining = size; ++ // put existing key-value to GenericMap ++ if (primMapNullKeyValid) { ++ putVal(null, primMapValOfNullKey, false, true, false); ++ --remaining; ++ } ++ final boolean[] valids = primMapValids; ++ if (valids != null) { ++ final long[] keys = primMapKeys; ++ final V[] values = primMapValues; ++ int Cap = valids.length; ++ for (int i = 0; remaining > 0 && i < Cap; ++i) { ++ if (valids[i]) { ++ putVal(castKeyToGenericType(keys[i]), values[i], false, true, false); ++ --remaining; ++ } ++ } ++ } ++ ++ if (remaining != 0 || expectedSize != size || expectedCount != modCount) { ++ throw new ConcurrentModificationException(); ++ } ++ // Don't set arrays to null. Keep long map's data to avoid concurrent visit NPE ++ } ++ disablePrimHashMap(); ++ } ++ } ++ ++ /** ++ * Computes hash to get index. ++ */ ++ static private int primHashMapCalculateIndex(Object key, final int mask) { ++ return hash(key) & mask; ++ } ++ ++ private void primHashMapResize() { ++ final boolean[] oldValids = primMapValids; ++ int oldCap = (oldValids == null) ? 0 : oldValids.length; ++ int newCap = calNewCapAndUpdateThreshold(oldCap); ++ // 0 means oldCap reaches the MAXIMUM_CAPACITY ++ if (newCap == 0) { ++ throw new IllegalStateException("can't resize due to primitive long map reaches the max capacity"); ++ } ++ ++ final boolean[] newValids = new boolean[newCap]; ++ final long[] newKeys = new long[newCap]; ++ @SuppressWarnings({"rawtypes","unchecked"}) ++ final V[] newValues = (V[])new Object[newCap]; ++ ++ final int mask = newCap - 1; ++ if (oldValids != null) { ++ final long[] oldKeys = primMapKeys; ++ final V[] oldValues = primMapValues; ++ int remaining = primMapNullKeyValid ? size - 1 : size; ++ for (int i = 0; remaining > 0 && i < oldCap; ++i) { ++ if (oldValids[i]) { ++ long key = oldKeys[i]; ++ V value = oldValues[i]; ++ int index = primHashMapCalculateIndex((Long)key, mask); ++ while (newValids[index]) { ++ index = (++index) & mask; ++ } ++ newValids[index] = true; ++ newKeys[index] = key; ++ newValues[index] = value; ++ --remaining; ++ } ++ } ++ } ++ primMapValids = newValids; ++ primMapKeys = newKeys; ++ primMapValues = newValues; ++ } ++ ++ /** ++ * Implements Map.put and related methods. ++ * ++ * @param key the key ++ * @param value the value to put ++ * @param onlyIfAbsent if true, don't change existing value ++ * @return previous value, or null if none ++ */ ++ private V primHashMapPutVal(Long key, V value, boolean onlyIfAbsent) { ++ if (key == null) { ++ if (primMapNullKeyValid) { // existing mapping for key ++ V oldValue = primMapValOfNullKey; ++ if (!onlyIfAbsent || oldValue == null) ++ primMapValOfNullKey = value; ++ return oldValue; ++ } ++ primMapNullKeyValid = true; ++ primMapValOfNullKey = value; ++ ++modCount; ++ ++size; ++ return null; ++ } ++ if (primMapValids == null || primMapValids.length == 0) { ++ primHashMapResize(); ++ } ++ long primKey = key; ++ final boolean[] valids = primMapValids; ++ final long[] keys = primMapKeys; ++ final V[] values = primMapValues; ++ int remainingLength = valids.length; ++ final int mask = remainingLength - 1; ++ int index = primHashMapCalculateIndex(key, mask); ++ // find empty slots to insert ++ while (valids[index] && remainingLength > 0) { ++ if (keys[index] == primKey) { ++ break; ++ } ++ index = (++index) & mask; ++ --remainingLength; ++ } ++ if (valids[index]) { // existing mapping for key ++ V oldValue = values[index]; ++ if (!onlyIfAbsent || oldValue == null) { ++ values[index] = value; ++ } ++ return oldValue; ++ } ++ keys[index] = primKey; ++ values[index] = value; ++ valids[index] = true; ++ ++modCount; ++ if (++size > threshold) ++ primHashMapResize(); ++ return null; ++ } ++ ++ /** ++ * find the key's index in prim hashmap ++ * ++ * @param key the key ++ * @return NULL_KEY_INDEX_FOR_RPIM_MAP if key is null and null key valid, ++ * related key-index if found, ++ * or KEY_NO_EXIST_FOR_PRIM_MAP if not found ++ */ ++ private int primHashGetIndexByKey(Object key) { ++ if (key == null) { ++ return primMapNullKeyValid ? NULL_KEY_INDEX_FOR_RPIM_MAP : KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ if (!(key instanceof Long)) { ++ return KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ final boolean[] valids = primMapValids; ++ if (valids == null || valids.length == 0) { ++ return KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ ++ final long[] keys = primMapKeys; ++ int remainingLength = valids.length; ++ final int mask = remainingLength - 1; ++ long primKey = (Long)key; ++ int index = primHashMapCalculateIndex(key, mask); ++ while (valids[index] && remainingLength > 0) { ++ if (keys[index] == primKey) { ++ return index; ++ } ++ index = (++index) & mask; ++ --remainingLength; ++ } ++ return KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ ++ private V primHashMapGetValByIndex(int index) { ++ if (index == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return null; ++ } ++ return (index == NULL_KEY_INDEX_FOR_RPIM_MAP) ? primMapValOfNullKey : primMapValues[index]; ++ } ++ ++ private V primHashMapGet(Object key) { ++ int index = primHashGetIndexByKey(key); ++ return primHashMapGetValByIndex(index); ++ } ++ ++ private boolean primHashMapContainsValue(Object value) { ++ int remaining = size; ++ if (primMapNullKeyValid) { ++ if (Objects.equals(value, primMapValOfNullKey)) { ++ return true; ++ } ++ --remaining; ++ } ++ final boolean[] valids = primMapValids; ++ if (valids == null || valids.length == 0) { ++ return false; ++ } ++ final V[] values = primMapValues; ++ final int length = valids.length; ++ for (int i = 0; remaining > 0 && i < length; ++i) { ++ if (valids[i]) { ++ if (Objects.equals(value, values[i])) { ++ return true; ++ } ++ --remaining; ++ } ++ } ++ return false; ++ } ++ ++ /** ++ * remove an element from prim hashmap by index. ++ * ++ * @param index the index of element to be removed ++ * @return the value of key, or null if none ++ */ ++ private V primHashMapRemoveByIndex(int index) { ++ int removeIdx = index; ++ if (removeIdx == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ return null; ++ } ++ V oldValue; ++ if (removeIdx == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ oldValue = primMapValOfNullKey; ++ primMapNullKeyValid = false; ++ primMapValOfNullKey = null; ++ } else { ++ oldValue = primMapValues[removeIdx]; ++ final boolean[] valids = primMapValids; ++ final V[] values = primMapValues; ++ final long[] keys = primMapKeys; ++ int mask = valids.length - 1; ++ // Moves the slot, whose expected idx and its actual index overwrite the removed slot, to the removed slot. ++ // Do it recursively until reaching an empty slot(there is always an empty slot since load factor <= 0.8f) ++ int actualIdx = (removeIdx + 1) & mask; ++ while (valids[actualIdx]) { ++ int expectedIdx = primHashMapCalculateIndex((Long)(keys[actualIdx]), mask); ++ // move actual to remove, then set actual as new remove ++ // | expectedIdx--->removeIdx--->actualIdx | or ++ // |--->actualIdx expectedIdx--->removeIdx--->| or ++ // |--->removeIdx--->actualIdx expectedIdx--->| ++ if ((expectedIdx <= removeIdx && removeIdx < actualIdx) || ++ (expectedIdx > actualIdx && (expectedIdx <= removeIdx || removeIdx < actualIdx))) { ++ keys[removeIdx] = keys[actualIdx]; ++ values[removeIdx] = values[actualIdx]; ++ removeIdx = actualIdx; ++ } ++ actualIdx = (++actualIdx) & mask; ++ } ++ valids[removeIdx] = false; ++ values[removeIdx] = null; ++ } ++ ++modCount; ++ --size; ++ return oldValue; ++ } ++ ++ /** ++ * remove an element from prim hashmap by key. ++ * ++ * @param key the key ++ * @return the value of key, or null if none ++ */ ++ private V primHashMapRemoveByKey(Object key) { ++ int index = primHashGetIndexByKey(key); ++ return primHashMapRemoveByIndex(index); ++ } ++ ++ private V primHashMapComputeIfAbsent(K key, ++ Function mappingFunction) { ++ int index = primHashGetIndexByKey(key); ++ V oldValue = primHashMapGetValByIndex(index); ++ if (oldValue != null) { ++ return oldValue; ++ } ++ int mc = modCount; ++ V v = mappingFunction.apply(key); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (v == null) { ++ return null; ++ } else if (index != KEY_NO_EXIST_FOR_PRIM_MAP) { // key exist and oldValue is null ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ primMapValOfNullKey = v; ++ } else { ++ primMapValues[index] = v; ++ } ++ return v; ++ } else { // key not exist ++ primHashMapPutVal((Long)key, v, false); ++ return v; ++ } ++ } ++ ++ private V primHashMapComputeIfPresent(K key, ++ BiFunction remappingFunction) { ++ int index = primHashGetIndexByKey(key); ++ V oldValue = primHashMapGetValByIndex(index); ++ if (oldValue == null) { ++ return null; ++ } ++ ++ int mc = modCount; ++ V v = remappingFunction.apply(key, oldValue); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (v == null) { ++ primHashMapRemoveByIndex(index); ++ return null; ++ } ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ primMapValOfNullKey = v; ++ } else { ++ primMapValues[index] = v; ++ } ++ return v; ++ } ++ ++ private V primHashMapCompute(K key, ++ BiFunction remappingFunction) { ++ int index = primHashGetIndexByKey(key); ++ V oldValue = primHashMapGetValByIndex(index); ++ int mc = modCount; ++ V v = remappingFunction.apply(key, oldValue); ++ if (mc != modCount) { throw new ConcurrentModificationException(); } ++ if (index != KEY_NO_EXIST_FOR_PRIM_MAP) { ++ if (v != null) { ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ primMapValOfNullKey = v; ++ } else { ++ primMapValues[index] = v; ++ } ++ } else { ++ primHashMapRemoveByIndex(index); ++ } ++ } else if (v != null) { ++ primHashMapPutVal((Long)key, v, false); ++ } ++ return v; ++ } ++ ++ private V primHashMapMerge(K key, V value, ++ BiFunction remappingFunction) { ++ int index = primHashGetIndexByKey(key); ++ V oldValue = primHashMapGetValByIndex(index); ++ if (index != KEY_NO_EXIST_FOR_PRIM_MAP) { ++ V v; ++ if (oldValue != null) { ++ int mc = modCount; ++ v = remappingFunction.apply(oldValue, value); ++ if (mc != modCount) { ++ throw new ConcurrentModificationException(); ++ } ++ } else { ++ v = value; ++ } ++ if (v != null) { ++ if (index == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ primMapValOfNullKey = v; ++ } else { ++ primMapValues[index] = v; ++ } ++ } else { ++ primHashMapRemoveByIndex(index); ++ } ++ return v; ++ } else { ++ primHashMapPutVal((Long)key, value, false); ++ return value; ++ } ++ } ++ ++ /** ++ * prim hashmap node, used for key-value iterator. ++ */ ++ class primHashMapNode implements Map.Entry { ++ final K key; ++ V value; ++ final int nodeIdx; ++ ++ primHashMapNode(int nodeIdx) { ++ if (nodeIdx == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ this.key = null; ++ this.value = HashMap.this.primMapValOfNullKey; ++ } else { ++ this.key = HashMap.this.castKeyToGenericType(HashMap.this.primMapKeys[nodeIdx]); ++ this.value = HashMap.this.primMapValues[nodeIdx]; ++ } ++ this.nodeIdx = nodeIdx; ++ } ++ ++ public final K getKey() { return key; } ++ public final V getValue() { return value; } ++ public final String toString() { return key + "=" + value; } ++ ++ public final boolean equals(Object o) { ++ if (o == this) ++ return true; ++ ++ return o instanceof Map.Entry e ++ && Objects.equals(key, e.getKey()) ++ && Objects.equals(value, e.getValue()); ++ } ++ ++ public final int hashCode() { ++ return Objects.hashCode(key) ^ Objects.hashCode(value); ++ } ++ ++ public final V setValue(V newValue) { ++ V oldValue = value; ++ value = newValue; ++ if (nodeIdx == NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ HashMap.this.primMapValOfNullKey = newValue; ++ } else { ++ HashMap.this.primMapValues[nodeIdx] = newValue; ++ } ++ return oldValue; ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // prim hashmap iterators ++ ++ abstract class primHashMapIterator { ++ int nextIndex; ++ int curIndex; // current slot ++ int expectedModCount; // for fast-fail ++ int remaining; ++ int startIndex; ++ int stopIndex; ++ int mask; ++ ++ primHashMapIterator() { ++ expectedModCount = modCount; ++ remaining = size; ++ curIndex = nextIndex = KEY_NO_EXIST_FOR_PRIM_MAP; ++ // init startIndex/stopIndex if there are elements in primMapValues ++ if (remaining > (primMapNullKeyValid ? 1 : 0)) { ++ final boolean[] valids = primMapValids; ++ mask = valids.length - 1; ++ // Use reverse traversal to prevent a node from being traversed after it is deleted. ++ // Set stopIndex to the index of the first empty slot (starting from 0), and then set ++ // startIndex to the index of the first valid slot starting from the reverse of stopIndex. ++ stopIndex = 0; ++ while (valids[stopIndex]) { ++ stopIndex = (++stopIndex) & mask; ++ } ++ startIndex = stopIndex; ++ while (!valids[startIndex]) { ++ startIndex = (--startIndex) & mask; ++ } ++ } else { ++ startIndex = stopIndex = KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ ++ if (remaining > 0) { // advance to first entry ++ if (primMapNullKeyValid) { // always set first node to null key if exist ++ nextIndex = NULL_KEY_INDEX_FOR_RPIM_MAP; ++ } else { ++ nextIndex = startIndex; ++ } ++ } ++ } ++ ++ public final boolean hasNext() { ++ return remaining > 0; ++ } ++ ++ final void findNext() { ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ if (nextIndex == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ throw new NoSuchElementException(); ++ } ++ curIndex = nextIndex; ++ --remaining; ++ if (remaining > 0) { ++ // startIndex has been calculated if curIndex is null key index ++ if (curIndex != NULL_KEY_INDEX_FOR_RPIM_MAP) { ++ final boolean[] valids = primMapValids; ++ while (startIndex != stopIndex) { ++ startIndex = (--startIndex) & mask; ++ if (valids[startIndex]) { ++ break; ++ } ++ } ++ } ++ nextIndex = startIndex; ++ } else { ++ nextIndex = KEY_NO_EXIST_FOR_PRIM_MAP; ++ } ++ } ++ ++ public final void remove() { ++ if (curIndex == KEY_NO_EXIST_FOR_PRIM_MAP) { ++ throw new IllegalStateException(); ++ } ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ primHashMapRemoveByIndex(curIndex); ++ curIndex = KEY_NO_EXIST_FOR_PRIM_MAP; ++ expectedModCount = modCount; ++ } ++ } ++ ++ final class primHashMapKeyIterator extends primHashMapIterator ++ implements Iterator { ++ public final K next() { ++ findNext(); ++ return (curIndex == NULL_KEY_INDEX_FOR_RPIM_MAP) ? null : castKeyToGenericType(primMapKeys[curIndex]); ++ } ++ } ++ ++ final class primHashMapValueIterator extends primHashMapIterator ++ implements Iterator { ++ public final V next() { ++ findNext(); ++ return (curIndex == NULL_KEY_INDEX_FOR_RPIM_MAP) ? primMapValOfNullKey : primMapValues[curIndex]; ++ } ++ } ++ ++ final class primHashMapEntryIterator extends primHashMapIterator ++ implements Iterator> { ++ public final Map.Entry next() { ++ findNext(); ++ return new primHashMapNode(curIndex); ++ } ++ } ++ ++ /* ------------------------------------------------------------ */ ++ // prim hashmap spliterators ++ ++ abstract class primHashMapSpliterator implements Spliterator { ++ int index; // current index, modified on advance/split ++ int fence; // one past last index ++ int est; // size estimate ++ int expectedModCount; // for comodification checks ++ ++ // indicate whether this spliterator need to process null key or not ++ // always set to false if this spliterator came form trySplit() ++ boolean needToProcessNullKey; ++ ++ primHashMapSpliterator(int origin, int fence, int est, ++ int expectedModCount, boolean needToProcessNullKey) { ++ this.index = origin; ++ this.fence = fence; ++ this.est = est; ++ this.expectedModCount = expectedModCount; ++ this.needToProcessNullKey = needToProcessNullKey; ++ } ++ ++ final int getFence() { // initialize fence and size on first use ++ int hi; ++ if ((hi = fence) < 0) { ++ est = size; ++ expectedModCount = modCount; ++ boolean[] valids = primMapValids; ++ hi = fence = (valids == null) ? 0 : valids.length; ++ } ++ return hi; ++ } ++ ++ public final long estimateSize() { ++ getFence(); // force init ++ return (long) est; ++ } ++ } ++ ++ final class primHashMapKeySpliterator ++ extends primHashMapSpliterator { ++ primHashMapKeySpliterator(int origin, int fence, int est, ++ int expectedModCount, boolean needToProcessNullKey) { ++ super(origin, fence, est, expectedModCount, needToProcessNullKey); ++ } ++ ++ public primHashMapKeySpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid) ? null : ++ new primHashMapKeySpliterator(lo, index = mid, est >>>= 1, ++ expectedModCount, false); ++ } ++ ++ public void forEachRemaining(Consumer action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = modCount; ++ hi = fence = (valids == null) ? 0 : valids.length; ++ } ++ else ++ mc = expectedModCount; ++ if (valids != null && valids.length >= hi && ++ (i = index) >= 0 && i < (index = hi)) { ++ do { ++ if (valids[i]) { ++ action.accept(castKeyToGenericType(keys[i])); ++ } ++ ++i; ++ } while (i < hi); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(null); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ needToProcessNullKey = false; ++ } ++ } ++ ++ public boolean tryAdvance(Consumer action) { ++ int hi = getFence(); // force init ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ long[] keys = primMapKeys; ++ if (valids != null && valids.length >= hi && index >= 0) { ++ while (index < hi) { ++ if (!valids[index]) { ++ ++index; ++ } else { ++ action.accept(castKeyToGenericType(keys[index])); ++ ++index; ++ if (modCount != expectedModCount) { ++ throw new ConcurrentModificationException(); ++ } ++ return true; ++ } ++ } ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(null); ++ if (modCount != expectedModCount) { ++ throw new ConcurrentModificationException(); ++ } ++ needToProcessNullKey = false; ++ return true; ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == size ? Spliterator.SIZED : 0) | ++ Spliterator.DISTINCT; ++ } ++ } ++ ++ final class primHashMapValueSpliterator ++ extends primHashMapSpliterator { ++ primHashMapValueSpliterator(int origin, int fence, int est, ++ int expectedModCount, boolean needToProcessNullKey) { ++ super(origin, fence, est, expectedModCount, needToProcessNullKey); ++ } ++ ++ public primHashMapValueSpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid) ? null : ++ new primHashMapValueSpliterator(lo, index = mid, est >>>= 1, ++ expectedModCount, false); ++ } ++ ++ public void forEachRemaining(Consumer action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ V[] values = primMapValues; ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = modCount; ++ hi = fence = (valids == null) ? 0 : valids.length; ++ } ++ else ++ mc = expectedModCount; ++ if (valids != null && valids.length >= hi && ++ (i = index) >= 0 && i < (index = hi)) { ++ do { ++ if (valids[i]) { ++ action.accept(values[i]); ++ } ++ ++i; ++ } while (i < hi); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(primMapValOfNullKey); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ needToProcessNullKey = false; ++ } ++ } ++ ++ public boolean tryAdvance(Consumer action) { ++ int hi = getFence(); // force init ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ V[] values = primMapValues; ++ if (valids != null && valids.length >= hi && index >= 0) { ++ while (index < hi) { ++ if (!valids[index]) { ++ ++index; ++ } else { ++ action.accept(values[index]); ++ ++index; ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ return true; ++ } ++ } ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(primMapValOfNullKey); ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ needToProcessNullKey = false; ++ return true; ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == size ? Spliterator.SIZED : 0); ++ } ++ } ++ ++ final class primHashMapEntrySpliterator ++ extends primHashMapSpliterator> { ++ primHashMapEntrySpliterator(int origin, int fence, int est, ++ int expectedModCount, boolean needToProcessNullKey) { ++ super(origin, fence, est, expectedModCount, needToProcessNullKey); ++ } ++ ++ public primHashMapEntrySpliterator trySplit() { ++ int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; ++ return (lo >= mid) ? null : ++ new primHashMapEntrySpliterator(lo, index = mid, est >>>= 1, ++ expectedModCount, false); ++ } ++ ++ public void forEachRemaining(Consumer> action) { ++ int i, hi, mc; ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ ++ if ((hi = fence) < 0) { ++ mc = expectedModCount = modCount; ++ hi = fence = (valids == null) ? 0 : valids.length; ++ } ++ else ++ mc = expectedModCount; ++ if (valids != null && valids.length >= hi && ++ (i = index) >= 0 && i < (index = hi)) { ++ do { ++ if (valids[i]) { ++ action.accept(new primHashMapNode(i)); ++ } ++ ++i; ++ } while ( i < hi); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(new primHashMapNode(NULL_KEY_INDEX_FOR_RPIM_MAP)); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ needToProcessNullKey = false; ++ } ++ } ++ ++ public boolean tryAdvance(Consumer> action) { ++ int hi = getFence(); // force init ++ if (action == null) ++ throw new NullPointerException(); ++ boolean[] valids = primMapValids; ++ if (valids != null && valids.length >= hi && index >= 0) { ++ while (index < hi) { ++ if (!valids[index]) { ++ ++index; ++ } ++ else { ++ action.accept(new primHashMapNode(index)); ++ ++index; ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ return true; ++ } ++ } ++ } ++ if (needToProcessNullKey && primMapNullKeyValid) { ++ action.accept(new primHashMapNode(NULL_KEY_INDEX_FOR_RPIM_MAP)); ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ needToProcessNullKey = false; ++ return true; ++ } ++ return false; ++ } ++ ++ public int characteristics() { ++ return (fence < 0 || est == size ? Spliterator.SIZED : 0) | ++ Spliterator.DISTINCT; ++ } ++ } ++ ++} +diff --git a/test/jdk/java/util/HashMap/LinkedHashMap.java b/test/jdk/java/util/HashMap/LinkedHashMap.java +new file mode 100644 +index 000000000..92c1a795a +--- /dev/null ++++ b/test/jdk/java/util/HashMap/LinkedHashMap.java +@@ -0,0 +1,798 @@ ++/* ++ * Copyright (c) 1997, 2020, 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 java.util; ++ ++import java.util.function.Consumer; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.io.IOException; ++ ++/** ++ *

Hash table and linked list implementation of the {@code Map} interface, ++ * with predictable iteration order. This implementation differs from ++ * {@code HashMap} in that it maintains a doubly-linked list running through ++ * all of its entries. This linked list defines the iteration ordering, ++ * which is normally the order in which keys were inserted into the map ++ * (insertion-order). Note that insertion order is not affected ++ * if a key is re-inserted into the map. (A key {@code k} is ++ * reinserted into a map {@code m} if {@code m.put(k, v)} is invoked when ++ * {@code m.containsKey(k)} would return {@code true} immediately prior to ++ * the invocation.) ++ * ++ *

This implementation spares its clients from the unspecified, generally ++ * chaotic ordering provided by {@link HashMap} (and {@link Hashtable}), ++ * without incurring the increased cost associated with {@link TreeMap}. It ++ * can be used to produce a copy of a map that has the same order as the ++ * original, regardless of the original map's implementation: ++ *

{@code
++ *     void foo(Map m) {
++ *         Map copy = new LinkedHashMap<>(m);
++ *         ...
++ *     }
++ * }
++ * This technique is particularly useful if a module takes a map on input, ++ * copies it, and later returns results whose order is determined by that of ++ * the copy. (Clients generally appreciate having things returned in the same ++ * order they were presented.) ++ * ++ *

A special {@link #LinkedHashMap(int,float,boolean) constructor} is ++ * provided to create a linked hash map whose order of iteration is the order ++ * in which its entries were last accessed, from least-recently accessed to ++ * most-recently (access-order). This kind of map is well-suited to ++ * building LRU caches. Invoking the {@code put}, {@code putIfAbsent}, ++ * {@code get}, {@code getOrDefault}, {@code compute}, {@code computeIfAbsent}, ++ * {@code computeIfPresent}, or {@code merge} methods results ++ * in an access to the corresponding entry (assuming it exists after the ++ * invocation completes). The {@code replace} methods only result in an access ++ * of the entry if the value is replaced. The {@code putAll} method generates one ++ * entry access for each mapping in the specified map, in the order that ++ * key-value mappings are provided by the specified map's entry set iterator. ++ * No other methods generate entry accesses. In particular, operations ++ * on collection-views do not affect the order of iteration of the ++ * backing map. ++ * ++ *

The {@link #removeEldestEntry(Map.Entry)} method may be overridden to ++ * impose a policy for removing stale mappings automatically when new mappings ++ * are added to the map. ++ * ++ *

This class provides all of the optional {@code Map} operations, and ++ * permits null elements. Like {@code HashMap}, it provides constant-time ++ * performance for the basic operations ({@code add}, {@code contains} and ++ * {@code remove}), assuming the hash function disperses elements ++ * properly among the buckets. Performance is likely to be just slightly ++ * below that of {@code HashMap}, due to the added expense of maintaining the ++ * linked list, with one exception: Iteration over the collection-views ++ * of a {@code LinkedHashMap} requires time proportional to the size ++ * of the map, regardless of its capacity. Iteration over a {@code HashMap} ++ * is likely to be more expensive, requiring time proportional to its ++ * capacity. ++ * ++ *

A linked hash map has two parameters that affect its performance: ++ * initial capacity and load factor. They are defined precisely ++ * as for {@code HashMap}. Note, however, that the penalty for choosing an ++ * excessively high value for initial capacity is less severe for this class ++ * than for {@code HashMap}, as iteration times for this class are unaffected ++ * by capacity. ++ * ++ *

Note that this implementation is not synchronized. ++ * If multiple threads access a linked hash map concurrently, and at least ++ * one of the threads modifies the map structurally, it must be ++ * synchronized externally. This is typically accomplished by ++ * synchronizing on some object that naturally encapsulates the map. ++ * ++ * If no such object exists, the map should be "wrapped" using the ++ * {@link Collections#synchronizedMap Collections.synchronizedMap} ++ * method. This is best done at creation time, to prevent accidental ++ * unsynchronized access to the map:

++ *   Map m = Collections.synchronizedMap(new LinkedHashMap(...));
++ * ++ * A structural modification is any operation that adds or deletes one or more ++ * mappings or, in the case of access-ordered linked hash maps, affects ++ * iteration order. In insertion-ordered linked hash maps, merely changing ++ * the value associated with a key that is already contained in the map is not ++ * a structural modification. In access-ordered linked hash maps, ++ * merely querying the map with {@code get} is a structural modification. ++ * ) ++ * ++ *

The iterators returned by the {@code iterator} method of the collections ++ * returned by all of this class's collection view methods are ++ * fail-fast: if the map is structurally modified at any time after ++ * the iterator is created, in any way except through the iterator's own ++ * {@code remove} method, the iterator will throw a {@link ++ * ConcurrentModificationException}. Thus, in the face of concurrent ++ * modification, the iterator fails quickly and cleanly, rather than risking ++ * arbitrary, non-deterministic behavior at an undetermined time in the future. ++ * ++ *

Note that the fail-fast behavior of an iterator cannot be guaranteed ++ * as it is, generally speaking, impossible to make any hard guarantees in the ++ * presence of unsynchronized concurrent modification. Fail-fast iterators ++ * throw {@code ConcurrentModificationException} on a best-effort basis. ++ * Therefore, it would be wrong to write a program that depended on this ++ * exception for its correctness: the fail-fast behavior of iterators ++ * should be used only to detect bugs. ++ * ++ *

The spliterators returned by the spliterator method of the collections ++ * returned by all of this class's collection view methods are ++ * late-binding, ++ * fail-fast, and additionally report {@link Spliterator#ORDERED}. ++ * ++ *

This class is a member of the ++ * ++ * Java Collections Framework. ++ * ++ * @implNote ++ * The spliterators returned by the spliterator method of the collections ++ * returned by all of this class's collection view methods are created from ++ * the iterators of the corresponding collections. ++ * ++ * @param the type of keys maintained by this map ++ * @param the type of mapped values ++ * ++ * @author Josh Bloch ++ * @see Object#hashCode() ++ * @see Collection ++ * @see Map ++ * @see HashMap ++ * @see TreeMap ++ * @see Hashtable ++ * @since 1.4 ++ */ ++public class LinkedHashMap ++ extends HashMap ++ implements Map ++{ ++ ++ /* ++ * Implementation note. A previous version of this class was ++ * internally structured a little differently. Because superclass ++ * HashMap now uses trees for some of its nodes, class ++ * LinkedHashMap.Entry is now treated as intermediary node class ++ * that can also be converted to tree form. The name of this ++ * class, LinkedHashMap.Entry, is confusing in several ways in its ++ * current context, but cannot be changed. Otherwise, even though ++ * it is not exported outside this package, some existing source ++ * code is known to have relied on a symbol resolution corner case ++ * rule in calls to removeEldestEntry that suppressed compilation ++ * errors due to ambiguous usages. So, we keep the name to ++ * preserve unmodified compilability. ++ * ++ * The changes in node classes also require using two fields ++ * (head, tail) rather than a pointer to a header node to maintain ++ * the doubly-linked before/after list. This class also ++ * previously used a different style of callback methods upon ++ * access, insertion, and removal. ++ */ ++ ++ /** ++ * HashMap.Node subclass for normal LinkedHashMap entries. ++ */ ++ static class Entry extends HashMap.Node { ++ Entry before, after; ++ Entry(int hash, K key, V value, Node next) { ++ super(hash, key, value, next); ++ } ++ } ++ ++ @java.io.Serial ++ private static final long serialVersionUID = 3801124242820219131L; ++ ++ /** ++ * The head (eldest) of the doubly linked list. ++ */ ++ transient LinkedHashMap.Entry head; ++ ++ /** ++ * The tail (youngest) of the doubly linked list. ++ */ ++ transient LinkedHashMap.Entry tail; ++ ++ /** ++ * The iteration ordering method for this linked hash map: {@code true} ++ * for access-order, {@code false} for insertion-order. ++ * ++ * @serial ++ */ ++ final boolean accessOrder; ++ ++ // internal utilities ++ ++ // link at the end of list ++ private void linkNodeLast(LinkedHashMap.Entry p) { ++ LinkedHashMap.Entry last = tail; ++ tail = p; ++ if (last == null) ++ head = p; ++ else { ++ p.before = last; ++ last.after = p; ++ } ++ } ++ ++ // apply src's links to dst ++ private void transferLinks(LinkedHashMap.Entry src, ++ LinkedHashMap.Entry dst) { ++ LinkedHashMap.Entry b = dst.before = src.before; ++ LinkedHashMap.Entry a = dst.after = src.after; ++ if (b == null) ++ head = dst; ++ else ++ b.after = dst; ++ if (a == null) ++ tail = dst; ++ else ++ a.before = dst; ++ } ++ ++ // overrides of HashMap hook methods ++ ++ void reinitialize() { ++ super.reinitialize(); ++ disablePrimHashMap(); ++ head = tail = null; ++ } ++ ++ Node newNode(int hash, K key, V value, Node e) { ++ LinkedHashMap.Entry p = ++ new LinkedHashMap.Entry<>(hash, key, value, e); ++ linkNodeLast(p); ++ return p; ++ } ++ ++ Node replacementNode(Node p, Node next) { ++ LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; ++ LinkedHashMap.Entry t = ++ new LinkedHashMap.Entry<>(q.hash, q.key, q.value, next); ++ transferLinks(q, t); ++ return t; ++ } ++ ++ TreeNode newTreeNode(int hash, K key, V value, Node next) { ++ TreeNode p = new TreeNode<>(hash, key, value, next); ++ linkNodeLast(p); ++ return p; ++ } ++ ++ TreeNode replacementTreeNode(Node p, Node next) { ++ LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; ++ TreeNode t = new TreeNode<>(q.hash, q.key, q.value, next); ++ transferLinks(q, t); ++ return t; ++ } ++ ++ void afterNodeRemoval(Node e) { // unlink ++ LinkedHashMap.Entry p = ++ (LinkedHashMap.Entry)e, b = p.before, a = p.after; ++ p.before = p.after = null; ++ if (b == null) ++ head = a; ++ else ++ b.after = a; ++ if (a == null) ++ tail = b; ++ else ++ a.before = b; ++ } ++ ++ void afterNodeInsertion(boolean evict) { // possibly remove eldest ++ LinkedHashMap.Entry first; ++ if (evict && (first = head) != null && removeEldestEntry(first)) { ++ K key = first.key; ++ removeNode(hash(key), key, null, false, true); ++ } ++ } ++ ++ void afterNodeAccess(Node e) { // move node to last ++ LinkedHashMap.Entry last; ++ if (accessOrder && (last = tail) != e) { ++ LinkedHashMap.Entry p = ++ (LinkedHashMap.Entry)e, b = p.before, a = p.after; ++ p.after = null; ++ if (b == null) ++ head = a; ++ else ++ b.after = a; ++ if (a != null) ++ a.before = b; ++ else ++ last = b; ++ if (last == null) ++ head = p; ++ else { ++ p.before = last; ++ last.after = p; ++ } ++ tail = p; ++ ++modCount; ++ } ++ } ++ ++ void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) { ++ s.writeObject(e.key); ++ s.writeObject(e.value); ++ } ++ } ++ ++ /** ++ * Constructs an empty insertion-ordered {@code LinkedHashMap} instance ++ * with the specified initial capacity and load factor. ++ * ++ * @param initialCapacity the initial capacity ++ * @param loadFactor the load factor ++ * @throws IllegalArgumentException if the initial capacity is negative ++ * or the load factor is nonpositive ++ */ ++ public LinkedHashMap(int initialCapacity, float loadFactor) { ++ super(initialCapacity, loadFactor); ++ disablePrimHashMap(); ++ accessOrder = false; ++ } ++ ++ /** ++ * Constructs an empty insertion-ordered {@code LinkedHashMap} instance ++ * with the specified initial capacity and a default load factor (0.75). ++ * ++ * @param initialCapacity the initial capacity ++ * @throws IllegalArgumentException if the initial capacity is negative ++ */ ++ public LinkedHashMap(int initialCapacity) { ++ super(initialCapacity); ++ disablePrimHashMap(); ++ accessOrder = false; ++ } ++ ++ /** ++ * Constructs an empty insertion-ordered {@code LinkedHashMap} instance ++ * with the default initial capacity (16) and load factor (0.75). ++ */ ++ public LinkedHashMap() { ++ super(); ++ disablePrimHashMap(); ++ accessOrder = false; ++ } ++ ++ /** ++ * Constructs an insertion-ordered {@code LinkedHashMap} instance with ++ * the same mappings as the specified map. The {@code LinkedHashMap} ++ * instance is created with a default load factor (0.75) and an initial ++ * capacity sufficient to hold the mappings in the specified map. ++ * ++ * @param m the map whose mappings are to be placed in this map ++ * @throws NullPointerException if the specified map is null ++ */ ++ public LinkedHashMap(Map m) { ++ super(); ++ disablePrimHashMap(); ++ accessOrder = false; ++ putMapEntries(m, false); ++ } ++ ++ /** ++ * Constructs an empty {@code LinkedHashMap} instance with the ++ * specified initial capacity, load factor and ordering mode. ++ * ++ * @param initialCapacity the initial capacity ++ * @param loadFactor the load factor ++ * @param accessOrder the ordering mode - {@code true} for ++ * access-order, {@code false} for insertion-order ++ * @throws IllegalArgumentException if the initial capacity is negative ++ * or the load factor is nonpositive ++ */ ++ public LinkedHashMap(int initialCapacity, ++ float loadFactor, ++ boolean accessOrder) { ++ super(initialCapacity, loadFactor); ++ disablePrimHashMap(); ++ this.accessOrder = accessOrder; ++ } ++ ++ ++ /** ++ * Returns {@code true} if this map maps one or more keys to the ++ * specified value. ++ * ++ * @param value value whose presence in this map is to be tested ++ * @return {@code true} if this map maps one or more keys to the ++ * specified value ++ */ ++ public boolean containsValue(Object value) { ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) { ++ V v = e.value; ++ if (v == value || (value != null && value.equals(v))) ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Returns the value to which the specified key is mapped, ++ * or {@code null} if this map contains no mapping for the key. ++ * ++ *

More formally, if this map contains a mapping from a key ++ * {@code k} to a value {@code v} such that {@code (key==null ? k==null : ++ * key.equals(k))}, then this method returns {@code v}; otherwise ++ * it returns {@code null}. (There can be at most one such mapping.) ++ * ++ *

A return value of {@code null} does not necessarily ++ * indicate that the map contains no mapping for the key; it's also ++ * possible that the map explicitly maps the key to {@code null}. ++ * The {@link #containsKey containsKey} operation may be used to ++ * distinguish these two cases. ++ */ ++ public V get(Object key) { ++ Node e; ++ if ((e = getNode(key)) == null) ++ return null; ++ if (accessOrder) ++ afterNodeAccess(e); ++ return e.value; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public V getOrDefault(Object key, V defaultValue) { ++ Node e; ++ if ((e = getNode(key)) == null) ++ return defaultValue; ++ if (accessOrder) ++ afterNodeAccess(e); ++ return e.value; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ public void clear() { ++ super.clear(); ++ head = tail = null; ++ } ++ ++ /** ++ * Returns {@code true} if this map should remove its eldest entry. ++ * This method is invoked by {@code put} and {@code putAll} after ++ * inserting a new entry into the map. It provides the implementor ++ * with the opportunity to remove the eldest entry each time a new one ++ * is added. This is useful if the map represents a cache: it allows ++ * the map to reduce memory consumption by deleting stale entries. ++ * ++ *

Sample use: this override will allow the map to grow up to 100 ++ * entries and then delete the eldest entry each time a new entry is ++ * added, maintaining a steady state of 100 entries. ++ *

++     *     private static final int MAX_ENTRIES = 100;
++     *
++     *     protected boolean removeEldestEntry(Map.Entry eldest) {
++     *        return size() > MAX_ENTRIES;
++     *     }
++     * 
++ * ++ *

This method typically does not modify the map in any way, ++ * instead allowing the map to modify itself as directed by its ++ * return value. It is permitted for this method to modify ++ * the map directly, but if it does so, it must return ++ * {@code false} (indicating that the map should not attempt any ++ * further modification). The effects of returning {@code true} ++ * after modifying the map from within this method are unspecified. ++ * ++ *

This implementation merely returns {@code false} (so that this ++ * map acts like a normal map - the eldest element is never removed). ++ * ++ * @param eldest The least recently inserted entry in the map, or if ++ * this is an access-ordered map, the least recently accessed ++ * entry. This is the entry that will be removed it this ++ * method returns {@code true}. If the map was empty prior ++ * to the {@code put} or {@code putAll} invocation resulting ++ * in this invocation, this will be the entry that was just ++ * inserted; in other words, if the map contains a single ++ * entry, the eldest entry is also the newest. ++ * @return {@code true} if the eldest entry should be removed ++ * from the map; {@code false} if it should be retained. ++ */ ++ protected boolean removeEldestEntry(Map.Entry eldest) { ++ return false; ++ } ++ ++ /** ++ * Returns a {@link Set} view of the keys contained in this map. ++ * The set is backed by the map, so changes to the map are ++ * reflected in the set, and vice-versa. If the map is modified ++ * while an iteration over the set is in progress (except through ++ * the iterator's own {@code remove} operation), the results of ++ * the iteration are undefined. The set supports element removal, ++ * which removes the corresponding mapping from the map, via the ++ * {@code Iterator.remove}, {@code Set.remove}, ++ * {@code removeAll}, {@code retainAll}, and {@code clear} ++ * operations. It does not support the {@code add} or {@code addAll} ++ * operations. ++ * Its {@link Spliterator} typically provides faster sequential ++ * performance but much poorer parallel performance than that of ++ * {@code HashMap}. ++ * ++ * @return a set view of the keys contained in this map ++ */ ++ public Set keySet() { ++ Set ks = keySet; ++ if (ks == null) { ++ ks = new LinkedKeySet(); ++ keySet = ks; ++ } ++ return ks; ++ } ++ ++ @Override ++ final T[] keysToArray(T[] a) { ++ Object[] r = a; ++ int idx = 0; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) { ++ r[idx++] = e.key; ++ } ++ return a; ++ } ++ ++ @Override ++ final T[] valuesToArray(T[] a) { ++ Object[] r = a; ++ int idx = 0; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) { ++ r[idx++] = e.value; ++ } ++ return a; ++ } ++ ++ final class LinkedKeySet extends AbstractSet { ++ public final int size() { return size; } ++ public final void clear() { LinkedHashMap.this.clear(); } ++ public final Iterator iterator() { ++ return new LinkedKeyIterator(); ++ } ++ public final boolean contains(Object o) { return containsKey(o); } ++ public final boolean remove(Object key) { ++ return removeNode(hash(key), key, null, false, true) != null; ++ } ++ public final Spliterator spliterator() { ++ return Spliterators.spliterator(this, Spliterator.SIZED | ++ Spliterator.ORDERED | ++ Spliterator.DISTINCT); ++ } ++ ++ public Object[] toArray() { ++ return keysToArray(new Object[size]); ++ } ++ ++ public T[] toArray(T[] a) { ++ return keysToArray(prepareArray(a)); ++ } ++ ++ public final void forEach(Consumer action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) ++ action.accept(e.key); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ /** ++ * Returns a {@link Collection} view of the values contained in this map. ++ * The collection is backed by the map, so changes to the map are ++ * reflected in the collection, and vice-versa. If the map is ++ * modified while an iteration over the collection is in progress ++ * (except through the iterator's own {@code remove} operation), ++ * the results of the iteration are undefined. The collection ++ * supports element removal, which removes the corresponding ++ * mapping from the map, via the {@code Iterator.remove}, ++ * {@code Collection.remove}, {@code removeAll}, ++ * {@code retainAll} and {@code clear} operations. It does not ++ * support the {@code add} or {@code addAll} operations. ++ * Its {@link Spliterator} typically provides faster sequential ++ * performance but much poorer parallel performance than that of ++ * {@code HashMap}. ++ * ++ * @return a view of the values contained in this map ++ */ ++ public Collection values() { ++ Collection vs = values; ++ if (vs == null) { ++ vs = new LinkedValues(); ++ values = vs; ++ } ++ return vs; ++ } ++ ++ final class LinkedValues extends AbstractCollection { ++ public final int size() { return size; } ++ public final void clear() { LinkedHashMap.this.clear(); } ++ public final Iterator iterator() { ++ return new LinkedValueIterator(); ++ } ++ public final boolean contains(Object o) { return containsValue(o); } ++ public final Spliterator spliterator() { ++ return Spliterators.spliterator(this, Spliterator.SIZED | ++ Spliterator.ORDERED); ++ } ++ ++ public Object[] toArray() { ++ return valuesToArray(new Object[size]); ++ } ++ ++ public T[] toArray(T[] a) { ++ return valuesToArray(prepareArray(a)); ++ } ++ ++ public final void forEach(Consumer action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) ++ action.accept(e.value); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ /** ++ * Returns a {@link Set} view of the mappings contained in this map. ++ * The set is backed by the map, so changes to the map are ++ * reflected in the set, and vice-versa. If the map is modified ++ * while an iteration over the set is in progress (except through ++ * the iterator's own {@code remove} operation, or through the ++ * {@code setValue} operation on a map entry returned by the ++ * iterator) the results of the iteration are undefined. The set ++ * supports element removal, which removes the corresponding ++ * mapping from the map, via the {@code Iterator.remove}, ++ * {@code Set.remove}, {@code removeAll}, {@code retainAll} and ++ * {@code clear} operations. It does not support the ++ * {@code add} or {@code addAll} operations. ++ * Its {@link Spliterator} typically provides faster sequential ++ * performance but much poorer parallel performance than that of ++ * {@code HashMap}. ++ * ++ * @return a set view of the mappings contained in this map ++ */ ++ public Set> entrySet() { ++ Set> es; ++ return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; ++ } ++ ++ final class LinkedEntrySet extends AbstractSet> { ++ public final int size() { return size; } ++ public final void clear() { LinkedHashMap.this.clear(); } ++ public final Iterator> iterator() { ++ return new LinkedEntryIterator(); ++ } ++ public final boolean contains(Object o) { ++ if (!(o instanceof Map.Entry e)) ++ return false; ++ Object key = e.getKey(); ++ Node candidate = getNode(key); ++ return candidate != null && candidate.equals(e); ++ } ++ public final boolean remove(Object o) { ++ if (o instanceof Map.Entry e) { ++ Object key = e.getKey(); ++ Object value = e.getValue(); ++ return removeNode(hash(key), key, value, true, true) != null; ++ } ++ return false; ++ } ++ public final Spliterator> spliterator() { ++ return Spliterators.spliterator(this, Spliterator.SIZED | ++ Spliterator.ORDERED | ++ Spliterator.DISTINCT); ++ } ++ public final void forEach(Consumer> action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) ++ action.accept(e); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ } ++ ++ // Map overrides ++ ++ public void forEach(BiConsumer action) { ++ if (action == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) ++ action.accept(e.key, e.value); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ ++ public void replaceAll(BiFunction function) { ++ if (function == null) ++ throw new NullPointerException(); ++ int mc = modCount; ++ for (LinkedHashMap.Entry e = head; e != null; e = e.after) ++ e.value = function.apply(e.key, e.value); ++ if (modCount != mc) ++ throw new ConcurrentModificationException(); ++ } ++ ++ // Iterators ++ ++ abstract class LinkedHashIterator { ++ LinkedHashMap.Entry next; ++ LinkedHashMap.Entry current; ++ int expectedModCount; ++ ++ LinkedHashIterator() { ++ next = head; ++ expectedModCount = modCount; ++ current = null; ++ } ++ ++ public final boolean hasNext() { ++ return next != null; ++ } ++ ++ final LinkedHashMap.Entry nextNode() { ++ LinkedHashMap.Entry e = next; ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ if (e == null) ++ throw new NoSuchElementException(); ++ current = e; ++ next = e.after; ++ return e; ++ } ++ ++ public final void remove() { ++ Node p = current; ++ if (p == null) ++ throw new IllegalStateException(); ++ if (modCount != expectedModCount) ++ throw new ConcurrentModificationException(); ++ current = null; ++ removeNode(p.hash, p.key, null, false, false); ++ expectedModCount = modCount; ++ } ++ } ++ ++ final class LinkedKeyIterator extends LinkedHashIterator ++ implements Iterator { ++ public final K next() { return nextNode().getKey(); } ++ } ++ ++ final class LinkedValueIterator extends LinkedHashIterator ++ implements Iterator { ++ public final V next() { return nextNode().value; } ++ } ++ ++ final class LinkedEntryIterator extends LinkedHashIterator ++ implements Iterator> { ++ public final Map.Entry next() { return nextNode(); } ++ } ++ ++ ++} +-- +2.22.0 + diff --git a/Enable-TLS-to-communciation-between-JBooster-Server-.patch b/Enable-TLS-to-communciation-between-JBooster-Server-.patch new file mode 100644 index 0000000..b60e3a0 --- /dev/null +++ b/Enable-TLS-to-communciation-between-JBooster-Server-.patch @@ -0,0 +1,2652 @@ +From afc216706bb555316211691268c01d7c977997c4 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:36:29 +0800 +Subject: Enable TLS to communciation between JBooster Server and Client JVM + +--- + .../linux/jbooster/net/clientStream_linux.cpp | 54 ++++ + .../net/serverListeningThread_linux.cpp | 64 ++++- + src/hotspot/share/cds/filemap.cpp | 2 +- + src/hotspot/share/include/jvm.h | 5 +- + .../jbooster/client/clientDataManager.cpp | 79 +++--- + .../jbooster/client/clientDataManager.hpp | 16 +- + .../jbooster/client/clientMessageHandler.cpp | 6 +- + .../share/jbooster/jBoosterManager.cpp | 1 + + .../share/jbooster/jBoosterSymbols.hpp | 1 + + .../share/jbooster/jClientArguments.cpp | 36 +-- + .../share/jbooster/jClientArguments.hpp | 32 ++- + src/hotspot/share/jbooster/jClientVMFlags.cpp | 19 +- + src/hotspot/share/jbooster/jClientVMFlags.hpp | 11 +- + .../share/jbooster/jbooster_globals.hpp | 3 + + .../share/jbooster/net/clientStream.cpp | 48 +++- + .../share/jbooster/net/clientStream.hpp | 6 + + .../jbooster/net/communicationStream.cpp | 37 ++- + .../jbooster/net/communicationStream.hpp | 12 +- + src/hotspot/share/jbooster/net/errorCode.hpp | 1 + + .../jbooster/net/serverListeningThread.cpp | 55 ++++- + .../jbooster/net/serverListeningThread.hpp | 13 +- + .../share/jbooster/net/serverStream.cpp | 27 +- + .../share/jbooster/net/serverStream.hpp | 6 +- + src/hotspot/share/jbooster/net/sslUtils.cpp | 231 ++++++++++++++++++ + src/hotspot/share/jbooster/net/sslUtils.hpp | 148 +++++++++++ + .../jbooster/server/serverDataManager.cpp | 42 ++-- + .../jbooster/server/serverDataManager.hpp | 30 ++- + .../jbooster/server/serverDataManagerLog.cpp | 17 +- + .../jbooster/server/serverMessageHandler.cpp | 30 ++- + src/hotspot/share/prims/jvm.cpp | 16 +- + src/hotspot/share/runtime/java.cpp | 2 +- + .../share/classes/jdk/jbooster/JBooster.java | 11 +- + .../share/classes/jdk/jbooster/Options.java | 22 ++ + .../share/native/libjbooster/JBooster.c | 8 +- + test/jdk/tools/jbooster/JBoosterSSLTest.java | 152 ++++++++++++ + .../jbooster/JBoosterSharedCacheTest.java | 150 ++++++++++++ + test/jdk/tools/jbooster/JBoosterTestBase.java | 6 +- + test/jdk/tools/jbooster/server-cert.pem | 22 ++ + test/jdk/tools/jbooster/server-key.pem | 27 ++ + test/jdk/tools/jbooster/unrelated-cert.pem | 22 ++ + 40 files changed, 1239 insertions(+), 231 deletions(-) + create mode 100644 src/hotspot/share/jbooster/net/sslUtils.cpp + create mode 100644 src/hotspot/share/jbooster/net/sslUtils.hpp + create mode 100644 test/jdk/tools/jbooster/JBoosterSSLTest.java + create mode 100644 test/jdk/tools/jbooster/JBoosterSharedCacheTest.java + create mode 100644 test/jdk/tools/jbooster/server-cert.pem + create mode 100644 test/jdk/tools/jbooster/server-key.pem + create mode 100644 test/jdk/tools/jbooster/unrelated-cert.pem + +diff --git a/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp b/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp +index 6425ef0f2..d0b43506d 100644 +--- a/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp ++++ b/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp +@@ -28,7 +28,9 @@ + + #include "jbooster/net/clientStream.hpp" + #include "jbooster/net/errorCode.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "logging/log.hpp" ++#include "runtime/java.hpp" + #include "runtime/os.hpp" + + #define LOG_INNER(socket_fd, address, port, err_code, format, args...) \ +@@ -142,3 +144,55 @@ int ClientStream::try_to_connect_once(int* res_fd, const char* address, const ch + *res_fd = -1; + return res_err; + } ++ ++int ClientStream::try_to_ssl_connect(SSL** res_ssl, int conn_fd) { ++ errno = 0; ++ ++ SSL* ssl = nullptr; ++ do { ++ SSL* ssl = SSLUtils::ssl_new(_client_ssl_ctx); ++ if (ssl == nullptr) { ++ log_error(jbooster, rpc)("Failed to get SSL."); ++ break; ++ } ++ ++ int ret = 0; ++ if (ret = SSLUtils::ssl_set_fd(ssl, conn_fd) != 1) { ++ SSLUtils::handle_ssl_error(ssl, ret, "Failed to set SSL file descriptor"); ++ break; ++ } ++ ++ if (ret = SSLUtils::ssl_connect(ssl) != 1) { ++ SSLUtils::handle_ssl_error(ssl, ret, "Failed to build SSL connection"); ++ break; ++ } ++ ++ if (!verify_cert(ssl)) { ++ break; ++ } ++ ++ // success ++ assert(errno == 0, "why errno=%s", JBErr::err_name(errno)); ++ *res_ssl = ssl; ++ return 0; ++ } while (false); ++ ++ // fail ++ int res_err = JBErr::BAD_SSL; ++ SSLUtils::shutdown_and_free_ssl(ssl); ++ return res_err; ++} ++ ++bool ClientStream::verify_cert(SSL* ssl) { ++ X509* cert = SSLUtils::ssl_get_peer_certificate(ssl); ++ if ((cert == nullptr)) { ++ log_error(jbooster, rpc)("Server cert unspecified."); ++ return false; ++ } ++ int res = SSLUtils::ssl_get_verify_result(ssl); ++ if (res != X509_V_OK) { ++ log_error(jbooster, rpc)("Failed to verify server cert."); ++ return false; ++ } ++ return true; ++} +diff --git a/src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp b/src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp +index bc3eae5b0..073052e72 100644 +--- a/src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp ++++ b/src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp +@@ -28,8 +28,10 @@ + + #include "jbooster/net/errorCode.hpp" + #include "jbooster/net/serverListeningThread.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "logging/log.hpp" + #include "runtime/interfaceSupport.inline.hpp" ++#include "runtime/java.hpp" + + static int init_server_socket_opts(int socket_fd) { + // enable reuse of address +@@ -105,6 +107,56 @@ static int bind_address(const char* address, uint16_t port) { + return socket_fd; + } + ++static SSL* try_to_ssl_connect(int conn_fd, SSL_CTX* ssl_ctx) { ++ SSL* ssl = SSLUtils::ssl_new(ssl_ctx); ++ if (ssl == nullptr) { ++ log_error(jbooster, rpc)("Failed to get SSL."); ++ os::close(conn_fd); ++ return nullptr; ++ } ++ ++ int ret = 0; ++ const char* error_description = nullptr; ++ if (ret = SSLUtils::ssl_set_fd(ssl, conn_fd) != 1) { ++ error_description = "Failed to set SSL file descriptor"; ++ } else if (ret = SSLUtils::ssl_accept(ssl) != 1) { ++ error_description = "Failed to accept SSL connection"; ++ } ++ ++ if (error_description != nullptr) { ++ SSLUtils::handle_ssl_error(ssl, ret, error_description); ++ SSLUtils::shutdown_and_free_ssl(ssl); ++ os::close(conn_fd); ++ return nullptr; ++ } ++ ++ log_info(jbooster, rpc)("Succeeded to build SSL connection."); ++ return ssl; ++} ++ ++bool ServerListeningThread::prepare_and_handle_new_connection(int server_fd, sockaddr_in* acc_addr, socklen_t* acc_addrlen, TRAPS) { ++ int conn_fd = accept(server_fd, (sockaddr*)acc_addr, acc_addrlen); ++ if (conn_fd < 0) { ++ if (errno != EAGAIN && errno != EWOULDBLOCK) { ++ log_error(jbooster, rpc)("Failed to accept: %s.", strerror(errno)); ++ } ++ return false; ++ } ++ if (init_accepted_socket_opts(conn_fd, _timeout_ms) < 0) { ++ return false; ++ } ++ ++ SSL* ssl = nullptr; ++ if (_server_ssl_ctx != nullptr) { ++ ssl = try_to_ssl_connect(conn_fd, _server_ssl_ctx); ++ if (ssl == nullptr) { ++ return false; ++ } ++ } ++ handle_new_connection(conn_fd, ssl, THREAD); ++ return true; ++} ++ + /** + * Keep listening for client requests. + */ +@@ -138,19 +190,15 @@ int ServerListeningThread::run_listener(TRAPS) { + } + + while (!get_exit_flag()) { +- int conn_fd = accept(server_fd, (sockaddr*)&acc_addr, &acc_addrlen); +- if (conn_fd < 0) { +- if (errno != EAGAIN && errno != EWOULDBLOCK) { +- log_error(jbooster, rpc)("Failed to accept: %s.", strerror(errno)); +- } ++ if (!prepare_and_handle_new_connection(server_fd, &acc_addr, &acc_addrlen, THREAD)) { + break; + } +- if (init_accepted_socket_opts(conn_fd, _timeout_ms) < 0) break; +- +- handle_new_connection(conn_fd, THREAD); + } + } + ++ if (_server_ssl_ctx != nullptr) { ++ SSLUtils::ssl_ctx_free(_server_ssl_ctx); ++ } + close(server_fd); + log_debug(jbooster, rpc)("The JBooster server listener thread stopped."); + return 0; +diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp +index 1fe0adbe5..76e078d12 100644 +--- a/src/hotspot/share/cds/filemap.cpp ++++ b/src/hotspot/share/cds/filemap.cpp +@@ -1216,7 +1216,7 @@ void FileMapInfo::open_for_write(const char* path) { + + int fd; + #if INCLUDE_JBOOSTER +- if (UseJBooster && ClientDataManager::get().is_cds_allowed()) { ++ if (UseJBooster && ClientDataManager::get().boost_level().is_cds_allowed()) { + // The _full_path points to the tmp file in the JBooster environment. + // The tmp file should have been created before (see dump_cds() in + // clientMessageHandler.cpp). So do not remove it or try to create it. +diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h +index 488ad25ce..7c917a240 100644 +--- a/src/hotspot/share/include/jvm.h ++++ b/src/hotspot/share/include/jvm.h +@@ -1105,13 +1105,14 @@ JVM_GetEnclosingMethodInfo(JNIEnv* env, jclass ofClass); + * Init the JBooster server in Hotspot. + */ + JNIEXPORT void JNICALL +-JVM_JBoosterInitVM(JNIEnv *env, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path); ++JVM_JBoosterInitVM(JNIEnv *env, jint server_port, jint connection_timeout, jint cleanup_timeout, ++ jstring cache_path, jstring ssl_key, jstring ssl_cert); + + /** + * Handle a TCP connection. + */ + JNIEXPORT void JNICALL +-JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd); ++JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd, jlong connection_ssl); + + /** + * Print data in ServerDataManager. +diff --git a/src/hotspot/share/jbooster/client/clientDataManager.cpp b/src/hotspot/share/jbooster/client/clientDataManager.cpp +index 0504dc656..3fbe143a8 100644 +--- a/src/hotspot/share/jbooster/client/clientDataManager.cpp ++++ b/src/hotspot/share/jbooster/client/clientDataManager.cpp +@@ -27,6 +27,7 @@ + #include "jbooster/client/clientDataManager.hpp" + #include "jbooster/client/clientStartupSignal.hpp" + #include "jbooster/net/clientStream.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "logging/log.hpp" + #include "runtime/arguments.hpp" + #include "runtime/globals_extension.hpp" +@@ -43,15 +44,9 @@ ClientDataManager::ClientDataManager() { + _program_str_id = nullptr; + _cache_dir_path = nullptr; + +- _allow_aot = false; +- _allow_cds = false; +- _allow_clr = false; +- _allow_pgo = false; +- + _using_aot = false; + _using_cds = false; + _using_clr = false; +- _using_pgo = false; + + _cache_clr_path = nullptr; + _cache_cds_path = nullptr; +@@ -81,25 +76,32 @@ void ClientDataManager::init_client_vm_options() { + + if (!FLAG_IS_DEFAULT(UseBoostPackages)) { + if (strcmp(UseBoostPackages, "all") == 0) { +- _allow_clr = _allow_cds = _allow_aot = _allow_pgo = true; ++ _boost_level.set_allow_clr(true); ++ _boost_level.set_allow_cds(true); ++ _boost_level.set_allow_aot(true); ++ _boost_level.set_enable_aot_pgo(true); + } else { +- _allow_clr = is_option_on("UseBoostPackages", UseBoostPackages, "clr"); +- _allow_cds = is_option_on("UseBoostPackages", UseBoostPackages, "cds"); +- _allow_aot = is_option_on("UseBoostPackages", UseBoostPackages, "aot"); +- _allow_pgo = is_option_on("UseBoostPackages", UseBoostPackages, "pgo"); +- if (_allow_pgo) _allow_aot = true; ++ _boost_level.set_allow_clr(is_option_on("UseBoostPackages", UseBoostPackages, "clr")); ++ _boost_level.set_allow_cds(is_option_on("UseBoostPackages", UseBoostPackages, "cds")); ++ _boost_level.set_allow_aot(is_option_on("UseBoostPackages", UseBoostPackages, "aot")); ++ _boost_level.set_enable_aot_pgo(is_option_on("UseBoostPackages", UseBoostPackages, "pgo")); ++ if (_boost_level.is_aot_pgo_enabled()) _boost_level.set_allow_aot(true); + } + } else { + switch (BoostStopAtLevel) { +- case 4: _allow_pgo = true; +- case 3: _allow_aot = true; +- case 2: _allow_cds = true; +- case 1: _allow_clr = true; ++ case 4: _boost_level.set_enable_aot_pgo(true); ++ case 3: _boost_level.set_allow_aot(true); ++ case 2: _boost_level.set_allow_cds(true); ++ case 1: _boost_level.set_allow_clr(true); + case 0: break; + default: break; + } + } + ++ if (FLAG_IS_DEFAULT(UseAggressiveCDS) || UseAggressiveCDS) { ++ _boost_level.set_enable_cds_agg(true); ++ } ++ + if (JBoosterStartupSignal != nullptr) { + ClientStartupSignal::init_phase1(); + } +@@ -113,17 +115,23 @@ void ClientDataManager::init_const() { + _program_args->is_jar(), + _program_args->hash()); + _cache_clr_path = JBoosterManager::calc_cache_path(_cache_dir_path, _program_str_id, "clr.log"); +- _cache_cds_path = JBoosterManager::calc_cache_path(_cache_dir_path, _program_str_id, "cds.jsa"); +- const char* aot_path_suffix = _allow_pgo ? "aot-pgo.so" : "aot.so"; ++ const char* cds_path_suffix = _boost_level.is_cds_agg_enabled() ? "cds-agg.jsa" : "cds-dy.jsa"; ++ _cache_cds_path = JBoosterManager::calc_cache_path(_cache_dir_path, _program_str_id, cds_path_suffix); ++ const char* aot_path_suffix = _boost_level.is_aot_pgo_enabled() ? "aot-pgo.so" : "aot-static.so"; + _cache_aot_path = JBoosterManager::calc_cache_path(_cache_dir_path, _program_str_id, aot_path_suffix); + } + + void ClientDataManager::init_client_duty() { ++ if (JBoosterServerSSLRootCerts) { ++ if (!SSLUtils::init_ssl_lib()) { ++ vm_exit_during_initialization("Failed to load all functions from OpenSSL Dynamic Library."); ++ } ++ ClientStream::client_init_ssl_ctx(JBoosterServerSSLRootCerts); ++ } + // Connect to jbooster before initializing CDS, before loading java_lang_classes + // and before starting the compiler threads. + ClientStream client_stream(JBoosterAddress, JBoosterPort, JBoosterTimeout, nullptr); + int rpc_err = client_stream.connect_and_init_session(&_using_clr, &_using_cds, &_using_aot); +- if (_using_aot && _allow_pgo) _using_pgo = true; + set_server_available(rpc_err == 0); + } + +@@ -132,11 +140,10 @@ void ClientDataManager::init_client_duty_under_local_mode() { + _using_clr = FileUtils::is_file(_cache_clr_path); + _using_cds = FileUtils::is_file(_cache_cds_path); + _using_aot = FileUtils::is_file(_cache_aot_path); +- if (_using_aot && _allow_pgo) _using_pgo = true; + } + + jint ClientDataManager::init_clr_options() { +- if (!is_clr_allowed()) return JNI_OK; ++ if (!_boost_level.is_clr_allowed()) return JNI_OK; + + if (FLAG_SET_CMDLINE(UseClassLoaderResourceCache, true) != JVMFlag::SUCCESS) { + return JNI_EINVAL; +@@ -156,10 +163,14 @@ jint ClientDataManager::init_clr_options() { + } + + jint ClientDataManager::init_cds_options() { +- if (!is_cds_allowed()) return JNI_OK; ++ if (!_boost_level.is_cds_allowed()) return JNI_OK; ++ ++ if (FLAG_IS_CMDLINE(SharedArchiveFile) || FLAG_IS_CMDLINE(ArchiveClassesAtExit) || FLAG_IS_CMDLINE(SharedArchiveConfigFile)) { ++ vm_exit_during_initialization("Do not set CDS path manually whe using JBooster."); ++ } + +- if (FLAG_IS_CMDLINE(SharedArchiveFile) || FLAG_IS_CMDLINE(ArchiveClassesAtExit)) { +- vm_exit_during_initialization("Do not set CDS manually whe using JBooster."); ++ if (FLAG_IS_CMDLINE(DynamicDumpSharedSpaces) || FLAG_IS_CMDLINE(DumpSharedSpaces) || FLAG_IS_CMDLINE(UseSharedSpaces)) { ++ vm_exit_during_initialization("Do not set dump/load CDS manually whe using JBooster."); + } + + if (is_cds_being_used()) { +@@ -197,7 +208,7 @@ jint ClientDataManager::init_cds_options() { + } + + jint ClientDataManager::init_aot_options() { +- if (!is_aot_allowed()) return JNI_OK; ++ if (!_boost_level.is_aot_allowed()) return JNI_OK; + if (FLAG_SET_CMDLINE(UseAOT, true) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } +@@ -210,7 +221,7 @@ jint ClientDataManager::init_aot_options() { + } + + jint ClientDataManager::init_pgo_options() { +- if (!is_pgo_allowed()) return JNI_OK; ++ if (!_boost_level.is_aot_pgo_enabled()) return JNI_OK; + if (FLAG_SET_CMDLINE(TypeProfileWidth, 8) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } +@@ -220,13 +231,11 @@ jint ClientDataManager::init_pgo_options() { + void ClientDataManager::print_init_state() { + log_info(jbooster)("Using boost packages:\n" + " - CLR: allowed_to_use=%s,\tis_being_used=%s\n" +- " - CDS: allowed_to_use=%s,\tis_being_used=%s\n" +- " - AOT: allowed_to_use=%s,\tis_being_used=%s\n" +- " - PGO: allowed_to_use=%s,\tis_being_used=%s", +- BOOL_TO_STR(is_clr_allowed()), BOOL_TO_STR(is_clr_being_used()), +- BOOL_TO_STR(is_cds_allowed()), BOOL_TO_STR(is_cds_being_used()), +- BOOL_TO_STR(is_aot_allowed()), BOOL_TO_STR(is_aot_being_used()), +- BOOL_TO_STR(is_pgo_allowed()), BOOL_TO_STR(is_pgo_being_used())); ++ " - CDS: allowed_to_use=%s,\tis_being_used=%s,\tis_aggressive_cds_enabled=%s\n" ++ " - AOT: allowed_to_use=%s,\tis_being_used=%s,\tis_pgo_aot_enabled=%s\n", ++ BOOL_TO_STR(_boost_level.is_clr_allowed()), BOOL_TO_STR(is_clr_being_used()), ++ BOOL_TO_STR(_boost_level.is_cds_allowed()), BOOL_TO_STR(is_cds_being_used()), BOOL_TO_STR(_boost_level.is_cds_agg_enabled()), ++ BOOL_TO_STR(_boost_level.is_aot_allowed()), BOOL_TO_STR(is_aot_being_used()), BOOL_TO_STR(_boost_level.is_aot_pgo_enabled())); + } + + static void check_jbooster_port() { +@@ -328,7 +337,7 @@ void ClientDataManager::init_phase2(TRAPS) { + } + ClientDaemonThread::start_thread(CHECK); + +- if (_singleton->is_clr_allowed()) { ++ if (_singleton->_boost_level.is_clr_allowed()) { + Klass* klass = SystemDictionary::resolve_or_fail(vmSymbols::java_net_ClassLoaderResourceCache(), true, CHECK); + InstanceKlass::cast(klass)->initialize(CHECK); + } +@@ -339,7 +348,7 @@ jint ClientDataManager::escape() { + return JNI_EINVAL; + } + +- if(!JBoosterLocalMode){ ++ if (!JBoosterLocalMode) { + log_error(jbooster)("Rolled back to the original path (UseJBooster=false), since the server is unavailable."); + } + return JNI_OK; +diff --git a/src/hotspot/share/jbooster/client/clientDataManager.hpp b/src/hotspot/share/jbooster/client/clientDataManager.hpp +index 07be0dc2a..da4a8cacb 100644 +--- a/src/hotspot/share/jbooster/client/clientDataManager.hpp ++++ b/src/hotspot/share/jbooster/client/clientDataManager.hpp +@@ -39,15 +39,11 @@ class ClientDataManager: public CHeapObj { + const char* _program_str_id; + const char* _cache_dir_path; + +- bool _allow_clr; +- bool _allow_cds; +- bool _allow_aot; +- bool _allow_pgo; ++ JClientBoostLevel _boost_level; + + bool _using_clr; + bool _using_cds; + bool _using_aot; +- bool _using_pgo; + + const char* _cache_clr_path; + const char* _cache_cds_path; +@@ -102,21 +98,17 @@ public: + // $HOME/.jbooster/client + const char* cache_dir_path() { return _cache_dir_path; } + +- bool is_clr_allowed() { return _allow_clr; } +- bool is_cds_allowed() { return _allow_cds; } +- bool is_aot_allowed() { return _allow_aot; } +- bool is_pgo_allowed() { return _allow_pgo; } ++ JClientBoostLevel& boost_level() { return _boost_level; } + + bool is_clr_being_used() { return _using_clr; } + bool is_cds_being_used() { return _using_cds; } + bool is_aot_being_used() { return _using_aot; } +- bool is_pgo_being_used() { return _using_pgo; } + + // /client/cache--clr.log + const char* cache_clr_path() { return _cache_clr_path; } +- // /client/cache--cds.jsa ++ // /client/cache--cds-[dy|agg].jsa + const char* cache_cds_path() { return _cache_cds_path; } +- // /client/cache--aot[-pgo].so ++ // /client/cache--aot-[static|pgo].so + const char* cache_aot_path() { return _cache_aot_path; } + + uint32_t session_id() { return _session_id; } +diff --git a/src/hotspot/share/jbooster/client/clientMessageHandler.cpp b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp +index 0ebe350a0..9119b897a 100644 +--- a/src/hotspot/share/jbooster/client/clientMessageHandler.cpp ++++ b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp +@@ -87,7 +87,7 @@ void ClientMessageHandler::trigger_cache_generation_tasks(TriggerTaskPhase phase + ClientMessageHandler msg_handler(client_stream); + + bool cache_file_task = false; +- if (ClientDataManager::get().is_clr_allowed() || ClientDataManager::get().is_cds_allowed()) { ++ if (ClientDataManager::get().boost_level().is_clr_allowed() || ClientDataManager::get().boost_level().is_cds_allowed()) { + cache_file_task = (phase == ON_SHUTDOWN); + } + if (cache_file_task) { +@@ -95,7 +95,7 @@ void ClientMessageHandler::trigger_cache_generation_tasks(TriggerTaskPhase phase + } + + bool lazy_aot_task = false; +- if (ClientDataManager::get().is_aot_allowed()) { ++ if (ClientDataManager::get().boost_level().is_aot_allowed()) { + lazy_aot_task = ((phase == ON_STARTUP) || ((phase == ON_SHUTDOWN) && !ClientDataManager::get().is_startup_end())); + } + if (lazy_aot_task) { +@@ -170,7 +170,7 @@ static void dump_cds() { + } + + int ClientMessageHandler::handle_CacheAggressiveCDS() { +- if (DynamicDumpSharedSpaces && ClientDataManager::get().is_cds_allowed()) { ++ if (DynamicDumpSharedSpaces && ClientDataManager::get().boost_level().is_cds_allowed()) { + dump_cds(); + } + FileWrapper file(ClientDataManager::get().cache_cds_path(), +diff --git a/src/hotspot/share/jbooster/jBoosterManager.cpp b/src/hotspot/share/jbooster/jBoosterManager.cpp +index 64af5e894..2a8995f4a 100644 +--- a/src/hotspot/share/jbooster/jBoosterManager.cpp ++++ b/src/hotspot/share/jbooster/jBoosterManager.cpp +@@ -178,4 +178,5 @@ void JBoosterManager::check_arguments() { + check_argument(FLAG_MEMBER_ENUM(JBoosterClientStrictMatch)); + check_argument(FLAG_MEMBER_ENUM(PrintAllClassInfo)); + check_argument(FLAG_MEMBER_ENUM(CheckClassFileTimeStamp)); ++ check_argument(FLAG_MEMBER_ENUM(JBoosterServerSSLRootCerts)); + } +\ No newline at end of file +diff --git a/src/hotspot/share/jbooster/jBoosterSymbols.hpp b/src/hotspot/share/jbooster/jBoosterSymbols.hpp +index e45bf85aa..4d7f64318 100644 +--- a/src/hotspot/share/jbooster/jBoosterSymbols.hpp ++++ b/src/hotspot/share/jbooster/jBoosterSymbols.hpp +@@ -26,6 +26,7 @@ + + #define JBOOSTER_TEMPLATES(template) \ + template(receiveConnection_name, "receiveConnection") \ ++ template(receiveConnection_signature, "(IJ)Z") \ + template(codesource_signature, "Ljava/security/CodeSource;") \ + template(getProtectionDomainByURLString_name, "getProtectionDomainByURLString") \ + template(getProtectionDomainByURLString_signature, "(Ljava/lang/String;)Ljava/security/ProtectionDomain;") \ +diff --git a/src/hotspot/share/jbooster/jClientArguments.cpp b/src/hotspot/share/jbooster/jClientArguments.cpp +index 1ae91674f..37093d031 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.cpp ++++ b/src/hotspot/share/jbooster/jClientArguments.cpp +@@ -209,10 +209,6 @@ void JClientArguments::init_for_client() { + } else { + _java_commands = StringUtils::copy_to_heap("", mtJBooster); + } +- _jbooster_allow_clr = ClientDataManager::get().is_clr_allowed(); +- _jbooster_allow_cds = ClientDataManager::get().is_cds_allowed(); +- _jbooster_allow_aot = ClientDataManager::get().is_aot_allowed(); +- _jbooster_allow_pgo = ClientDataManager::get().is_pgo_allowed(); + _related_flags = new JClientVMFlags(true); + + _hash = calc_hash(); +@@ -232,11 +228,7 @@ uint32_t JClientArguments::calc_hash() { + result = calc_new_hash(result, _classpath_timestamp_hash); + result = calc_new_hash(result, _agent_name_hash); + result = calc_new_hash(result, StringUtils::hash_code(_java_commands)); +- result = calc_new_hash(result, primitive_hash(_jbooster_allow_clr)); +- result = calc_new_hash(result, primitive_hash(_jbooster_allow_cds)); +- result = calc_new_hash(result, primitive_hash(_jbooster_allow_aot)); +- result = calc_new_hash(result, primitive_hash(_jbooster_allow_pgo)); +- result = calc_new_hash(result, _related_flags->hash(_jbooster_allow_clr, _jbooster_allow_cds, _jbooster_allow_aot)); ++ result = calc_new_hash(result, _related_flags->hash()); + + return result; + } +@@ -255,14 +247,7 @@ bool JClientArguments::equals(const JClientArguments* that) const { + if (this->_classpath_timestamp_hash != that->_classpath_timestamp_hash) return false; + if (this->_agent_name_hash != that->_agent_name_hash) return false; + if (StringUtils::compare(this->_java_commands, that->_java_commands) != 0) return false; +- if (this->_jbooster_allow_clr != that->_jbooster_allow_clr) return false; +- if (this->_jbooster_allow_cds != that->_jbooster_allow_cds) return false; +- if (this->_jbooster_allow_aot != that->_jbooster_allow_aot) return false; +- if (this->_jbooster_allow_pgo != that->_jbooster_allow_pgo) return false; +- if (!this->_related_flags->equals(that->_related_flags, +- _jbooster_allow_clr, +- _jbooster_allow_cds, +- _jbooster_allow_aot)) return false; ++ if (!this->_related_flags->equals(that->_related_flags)) return false; + return true; + } + +@@ -279,14 +264,8 @@ void JClientArguments::print_args(outputStream* st) const { + st->print_cr(" classpath_timestamp_hash: %x", _classpath_timestamp_hash); + st->print_cr(" agent_name_hash: %x", _agent_name_hash); + st->print_cr(" java_commands: \"%s\"", _java_commands); +- st->print_cr(" allow_clr: %s", BOOL_TO_STR(_jbooster_allow_clr)); +- st->print_cr(" allow_cds: %s", BOOL_TO_STR(_jbooster_allow_cds)); +- st->print_cr(" allow_aot: %s", BOOL_TO_STR(_jbooster_allow_aot)); +- st->print_cr(" allow_pgo: %s", BOOL_TO_STR(_jbooster_allow_pgo)); + st->print_cr(" vm_flags:"); +- st->print_cr(" hash: %u", _related_flags->hash(_jbooster_allow_clr, +- _jbooster_allow_cds, +- _jbooster_allow_aot)); ++ st->print_cr(" hash: %u", _related_flags->hash()); + _related_flags->print_flags(st); + } + +@@ -308,10 +287,6 @@ int JClientArguments::serialize(MessageBuffer& buf) const { + JB_RETURN(buf.serialize_no_meta(_classpath_timestamp_hash)); + JB_RETURN(buf.serialize_no_meta(_agent_name_hash)); + JB_RETURN(buf.serialize_with_meta(&_java_commands)); +- JB_RETURN(buf.serialize_no_meta(_jbooster_allow_clr)); +- JB_RETURN(buf.serialize_no_meta(_jbooster_allow_cds)); +- JB_RETURN(buf.serialize_no_meta(_jbooster_allow_aot)); +- JB_RETURN(buf.serialize_no_meta(_jbooster_allow_pgo)); + JB_RETURN(buf.serialize_with_meta(_related_flags)); + + JB_RETURN(buf.serialize_no_meta(_hash)); +@@ -351,11 +326,6 @@ int JClientArguments::deserialize(MessageBuffer& buf) { + JB_RETURN(buf.deserialize_with_meta(&sw_java_commands)); + _java_commands = sw_java_commands.export_string(); + +- JB_RETURN(buf.deserialize_ref_no_meta(_jbooster_allow_clr)); +- JB_RETURN(buf.deserialize_ref_no_meta(_jbooster_allow_cds)); +- JB_RETURN(buf.deserialize_ref_no_meta(_jbooster_allow_aot)); +- JB_RETURN(buf.deserialize_ref_no_meta(_jbooster_allow_pgo)); +- + _related_flags = new JClientVMFlags(false); + JB_RETURN(buf.deserialize_with_meta(_related_flags)); + +diff --git a/src/hotspot/share/jbooster/jClientArguments.hpp b/src/hotspot/share/jbooster/jClientArguments.hpp +index 7a0db1738..be057a07d 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.hpp ++++ b/src/hotspot/share/jbooster/jClientArguments.hpp +@@ -27,6 +27,30 @@ + #include "jbooster/jClientVMFlags.hpp" + #include "jbooster/net/serialization.hpp" + ++class JClientBoostLevel { ++ bool _allow_clr; ++ bool _allow_cds; ++ bool _allow_aot; ++ bool _enable_aot_pgo; ++ bool _enable_cds_agg; ++ ++public: ++ JClientBoostLevel(): _allow_clr(false), _allow_cds(false), _allow_aot(false), _enable_aot_pgo(false), _enable_cds_agg(false) {} ++ JClientBoostLevel(const JClientBoostLevel& level) = default; ++ ++ bool is_clr_allowed() const { return _allow_clr; } ++ bool is_cds_allowed() const { return _allow_cds; } ++ bool is_aot_allowed() const { return _allow_aot; } ++ bool is_aot_pgo_enabled() const {return _enable_aot_pgo; } ++ bool is_cds_agg_enabled() const {return _enable_cds_agg; } ++ ++ void set_allow_clr(bool allow_clr) { _allow_clr = allow_clr; } ++ void set_allow_cds(bool allow_cds) { _allow_cds = allow_cds; } ++ void set_allow_aot(bool allow_aot) { _allow_aot = allow_aot; } ++ void set_enable_aot_pgo(bool enable_aot_pgo) { _enable_aot_pgo = enable_aot_pgo; } ++ void set_enable_cds_agg(bool enable_cds_agg) { _enable_cds_agg = enable_cds_agg; } ++}; ++ + /** + * Arguments that identify a program. + */ +@@ -64,10 +88,6 @@ private: + uint32_t _classpath_timestamp_hash; + uint32_t _agent_name_hash; + const char* _java_commands; +- bool _jbooster_allow_clr; +- bool _jbooster_allow_cds; +- bool _jbooster_allow_aot; +- bool _jbooster_allow_pgo; + JClientVMFlags* _related_flags; + // ========================= end ========================= + +@@ -93,10 +113,6 @@ public: + uint32_t classpath_timestamp_hash() const { return _classpath_timestamp_hash; } + uint32_t agent_name_hash() const { return _agent_name_hash; } + const char* java_commands() const { return _java_commands; } +- bool jbooster_allow_clr() const { return _jbooster_allow_clr; } +- bool jbooster_allow_cds() const { return _jbooster_allow_cds; } +- bool jbooster_allow_aot() const { return _jbooster_allow_aot; } +- bool jbooster_allow_pgo() const { return _jbooster_allow_pgo; } + JClientVMFlags* related_flags() const { return _related_flags; } + + bool equals(const JClientArguments* that) const; +diff --git a/src/hotspot/share/jbooster/jClientVMFlags.cpp b/src/hotspot/share/jbooster/jClientVMFlags.cpp +index 6e46aeb48..570c1b64c 100644 +--- a/src/hotspot/share/jbooster/jClientVMFlags.cpp ++++ b/src/hotspot/share/jbooster/jClientVMFlags.cpp +@@ -125,28 +125,17 @@ JClientVMFlags::~JClientVMFlags() { + #undef FREE_FLAG + } + +-bool JClientVMFlags::equals(JClientVMFlags* that, bool allow_clr, bool allow_cds, bool allow_aot) { ++bool JClientVMFlags::equals(JClientVMFlags* that) { + #define CMP_FLAG(type, flag) if (!FlagTypeHandler::equals(this->v_##flag, that->v_##flag)) return false; +- if (allow_cds) { +- JCLIENT_CDS_VM_FLAGS(CMP_FLAG) +- } +- if (allow_aot) { +- JCLIENT_AOT_VM_FLAGS(CMP_FLAG) +- } ++ JCLIENT_VM_FLAGS(CMP_FLAG) + #undef CMP_FLAG +- + return true; + } + +-uint32_t JClientVMFlags::hash(bool allow_clr, bool allow_cds, bool allow_aot) { ++uint32_t JClientVMFlags::hash() { + uint32_t result = 1; + #define CALC_FLAG_HASH(type, flag) result = 31 * result + FlagTypeHandler::hash(v_##flag); +- if (allow_cds) { +- JCLIENT_CDS_VM_FLAGS(CALC_FLAG_HASH) +- } +- if (allow_aot) { +- JCLIENT_AOT_VM_FLAGS(CALC_FLAG_HASH) +- } ++ JCLIENT_VM_FLAGS(CALC_FLAG_HASH) + #undef CALC_FLAG_HASH + return result; + } +diff --git a/src/hotspot/share/jbooster/jClientVMFlags.hpp b/src/hotspot/share/jbooster/jClientVMFlags.hpp +index 71da58934..9d203d041 100644 +--- a/src/hotspot/share/jbooster/jClientVMFlags.hpp ++++ b/src/hotspot/share/jbooster/jClientVMFlags.hpp +@@ -29,14 +29,7 @@ + #include "runtime/globals_extension.hpp" + + #define JCLIENT_CDS_VM_FLAGS(f) \ +- f(bool, DynamicDumpSharedSpaces ) \ +- f(bool, DumpSharedSpaces ) \ +- f(bool, UseSharedSpaces ) \ +- f(ccstr, SharedArchiveFile ) \ +- f(ccstr, SharedArchiveConfigFile ) \ +- f(ccstr, ArchiveClassesAtExit ) \ + f(size_t, MaxMetaspaceSize ) \ +- f(bool, UseAggressiveCDS ) \ + f(size_t, SharedBaseAddress ) \ + f(bool, UseCompressedOops ) \ + f(bool, UseCompressedClassPointers ) \ +@@ -87,8 +80,8 @@ public: + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + +- bool equals(JClientVMFlags* that, bool allow_clr, bool allow_cds, bool allow_aot); +- uint32_t hash(bool allow_clr, bool allow_cds, bool allow_aot); ++ bool equals(JClientVMFlags* that); ++ uint32_t hash(); + + void print_flags(outputStream* st); + }; +diff --git a/src/hotspot/share/jbooster/jbooster_globals.hpp b/src/hotspot/share/jbooster/jbooster_globals.hpp +index 3eb74abf8..74968af75 100644 +--- a/src/hotspot/share/jbooster/jbooster_globals.hpp ++++ b/src/hotspot/share/jbooster/jbooster_globals.hpp +@@ -116,6 +116,9 @@ + \ + product(bool, ClassLoaderResourceCacheVerboseMode, false, DIAGNOSTIC, \ + "Dump/load more data for verification and debugging.") \ ++ \ ++ product(ccstr, JBoosterServerSSLRootCerts, NULL, EXPERIMENTAL, \ ++ "The file path to save server SSL root certificate.") \ + + + // end of JBOOSTER_FLAGS +diff --git a/src/hotspot/share/jbooster/net/clientStream.cpp b/src/hotspot/share/jbooster/net/clientStream.cpp +index d72b796d5..5b66e083f 100644 +--- a/src/hotspot/share/jbooster/net/clientStream.cpp ++++ b/src/hotspot/share/jbooster/net/clientStream.cpp +@@ -25,10 +25,13 @@ + #include "jbooster/net/clientStream.hpp" + #include "jbooster/net/rpcCompatibility.hpp" + #include "jbooster/net/serializationWrappers.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "jbooster/utilities/fileUtils.hpp" + #include "runtime/java.hpp" + #include "runtime/thread.hpp" + ++SSL_CTX* ClientStream::_client_ssl_ctx = nullptr; ++ + ClientStream::ClientStream(const char* address, const char* port, uint32_t timeout_ms): + CommunicationStream(Thread::current_or_null()), + _server_address(address), +@@ -49,6 +52,29 @@ ClientStream::~ClientStream() { + } + } + ++void ClientStream::client_init_ssl_ctx(const char* root_certs) { ++ guarantee(_client_ssl_ctx == nullptr, "sanity"); ++ ++ SSLUtils::openssl_init_ssl(); ++ _client_ssl_ctx = SSLUtils::ssl_ctx_new(SSLUtils::sslv23_client_method()); ++ if (_client_ssl_ctx == nullptr) { ++ vm_exit_during_initialization("Failed to create SSL context."); ++ } ++ ++ SSLUtils::ssl_ctx_set_options(_client_ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2); ++ ++ const int security_level = 2; ++ SSLUtils::ssl_ctx_set_security_level(_client_ssl_ctx, security_level); ++ ++ SSLUtils::ssl_ctx_set_verify(_client_ssl_ctx, SSL_VERIFY_PEER, NULL); ++ if (SSLUtils::ssl_ctx_load_verify_locations(_client_ssl_ctx, root_certs, NULL) != 1) { ++ if (log_is_enabled(Error, jbooster, rpc)) { ++ SSLUtils::err_print_errors_fp(stderr); ++ } ++ vm_exit_during_initialization("Failed to load root cert."); ++ } ++} ++ + int ClientStream::connect_to_server() { + close_stream(); + const int retries = 3; +@@ -58,10 +84,16 @@ int ClientStream::connect_to_server() { + int conn_fd; + JB_THROW(try_to_connect_once(&conn_fd, _server_address, _server_port, _timeout_ms)); + assert(conn_fd >= 0 && errno == 0, "sanity"); +- init_stream(conn_fd); ++ ++ SSL* ssl = nullptr; ++ if (_client_ssl_ctx != nullptr) { ++ JB_THROW(try_to_ssl_connect(&ssl, conn_fd)); ++ assert(ssl != nullptr && errno == 0, "sanity"); ++ } ++ init_stream(conn_fd, ssl); + return 0; + } JB_TRY_BREAKABLE_END +- JB_CATCH(ECONNREFUSED, EBADF) { ++ JB_CATCH(ECONNREFUSED, EBADF, JBErr::BAD_SSL) { + last_err = JB_ERR; + } JB_CATCH_REST() { + last_err = JB_ERR; +@@ -69,7 +101,7 @@ int ClientStream::connect_to_server() { + } JB_CATCH_END; + } + +- if (last_err == ECONNREFUSED || last_err == EBADF) { ++ if (last_err == ECONNREFUSED || last_err == EBADF || last_err == JBErr::BAD_SSL) { + constexpr const char* fmt = "Failed to connect to the JBooster server! Retried %d times."; + if (JBoosterCrashIfNoServer) { + fatal(fmt, retries); +@@ -119,7 +151,9 @@ int ClientStream::sync_session_meta__client(bool* has_remote_clr, bool* has_remo + RpcCompatibility comp; + uint64_t client_random_id = cdm.random_id(); + JClientArguments* program_args = cdm.program_args(); +- JB_RETURN(send_request(MessageType::ClientSessionMeta, &comp, &client_random_id, program_args)); ++ JClientBoostLevel boost_level = cdm.boost_level(); ++ ++ JB_RETURN(send_request(MessageType::ClientSessionMeta, &comp, &client_random_id, program_args, &boost_level)); + + uint64_t server_random_id; + uint32_t session_id, program_id; +@@ -176,21 +210,21 @@ int ClientStream::connect_and_init_session(bool* use_clr, bool* use_cds, bool* u + JB_THROW(sync_session_meta__client(&has_remote_clr, &has_remote_cds, &has_remote_aot)); + + JB_THROW(request_cache_file(use_clr, +- cdm.is_clr_allowed(), ++ cdm.boost_level().is_clr_allowed(), + FileUtils::is_file(cdm.cache_clr_path()), + has_remote_clr, + cdm.cache_clr_path(), + MessageType::GetClassLoaderResourceCache)); + + JB_THROW(request_cache_file(use_cds, +- cdm.is_cds_allowed(), ++ cdm.boost_level().is_cds_allowed(), + FileUtils::is_file(cdm.cache_cds_path()), + has_remote_cds, + cdm.cache_cds_path(), + MessageType::GetAggressiveCDSCache)); + + JB_THROW(request_cache_file(use_aot, +- cdm.is_aot_allowed(), ++ cdm.boost_level().is_aot_allowed(), + FileUtils::is_file(cdm.cache_aot_path()), + has_remote_aot, + cdm.cache_aot_path(), +diff --git a/src/hotspot/share/jbooster/net/clientStream.hpp b/src/hotspot/share/jbooster/net/clientStream.hpp +index 1f185644f..3c4d0fc8c 100644 +--- a/src/hotspot/share/jbooster/net/clientStream.hpp ++++ b/src/hotspot/share/jbooster/net/clientStream.hpp +@@ -28,6 +28,8 @@ + + class ClientStream: public CommunicationStream { + private: ++ static SSL_CTX* _client_ssl_ctx; ++ + const char* const _server_address; + const char* const _server_port; + const uint32_t _timeout_ms; +@@ -36,6 +38,8 @@ private: + + private: + static int try_to_connect_once(int* res_fd, const char* address, const char* port, uint32_t timeout_ms); ++ static int try_to_ssl_connect(SSL** res_ssl, int conn_fd); ++ static bool verify_cert(SSL* ssl); + + int request_cache_file(bool* use_it, + bool allowed_to_use, +@@ -55,6 +59,8 @@ public: + ClientStream(const char* address, const char* port, uint32_t timeout_ms, Thread* thread); + ~ClientStream(); + ++ static void client_init_ssl_ctx(const char* root_certs); ++ + void set_inform_before_close(bool should) { _inform_before_close = should; } + + int connect_and_init_session(bool* use_clr, bool* use_cds, bool* use_aot); +diff --git a/src/hotspot/share/jbooster/net/communicationStream.cpp b/src/hotspot/share/jbooster/net/communicationStream.cpp +index f0898170c..cdb4b8fa7 100644 +--- a/src/hotspot/share/jbooster/net/communicationStream.cpp ++++ b/src/hotspot/share/jbooster/net/communicationStream.cpp +@@ -22,6 +22,7 @@ + */ + + #include "jbooster/net/communicationStream.inline.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "runtime/os.inline.hpp" + #ifdef ASSERT + #include "runtime/thread.inline.hpp" +@@ -54,19 +55,27 @@ void CommunicationStream::handle_net_err(int comm_size, bool is_recv) { + return; + } + ++int CommunicationStream::read_from_fd_or_ssl(char* buf, size_t size) { ++ if (_ssl == nullptr) { ++ return os::recv(_conn_fd, buf, size, 0); ++ } else { ++ return SSLUtils::ssl_read(_ssl, buf, size); ++ } ++} ++ + uint32_t CommunicationStream::read_once_from_stream(char* buf, uint32_t size) { +- int read_size = os::recv(_conn_fd, buf, (size_t) size, 0); +- if (read_size <= 0) { +- handle_net_err(read_size, true); +- return 0; +- } +- return (uint32_t) read_size; ++ int read_size = read_from_fd_or_ssl(buf, size); ++ if (read_size <= 0) { ++ handle_net_err(read_size, true); ++ return 0; ++ } ++ return (uint32_t) read_size; + } + + uint32_t CommunicationStream::read_all_from_stream(char* buf, uint32_t size) { + uint32_t total_read_size = 0; + while (total_read_size < size) { +- int read_size = os::recv(_conn_fd, buf + total_read_size, (size_t) (size - total_read_size), 0); ++ int read_size = read_from_fd_or_ssl(buf + total_read_size, (size_t) (size - total_read_size)); + if (read_size <= 0) { + handle_net_err(read_size, true); + break; +@@ -76,8 +85,16 @@ uint32_t CommunicationStream::read_all_from_stream(char* buf, uint32_t size) { + return total_read_size; + } + ++int CommunicationStream::write_to_fd_or_ssl(char* buf, size_t size) { ++ if (_ssl == nullptr) { ++ return os::send(_conn_fd, buf, size, 0); ++ } else { ++ return SSLUtils::ssl_write(_ssl, buf, size); ++ } ++} ++ + uint32_t CommunicationStream::write_once_to_stream(char* buf, uint32_t size) { +- int written_size = os::send(_conn_fd, buf, size, 0); ++ int written_size = write_to_fd_or_ssl(buf, size); + if (written_size <= 0) { + handle_net_err(written_size, false); + return 0; +@@ -88,7 +105,7 @@ uint32_t CommunicationStream::write_once_to_stream(char* buf, uint32_t size) { + uint32_t CommunicationStream::write_all_to_stream(char* buf, uint32_t size) { + uint32_t total_written_size = 0; + while (total_written_size < size) { +- int written_size = os::send(_conn_fd, buf + total_written_size, (size_t) (size - total_written_size), 0); ++ int written_size = write_to_fd_or_ssl(buf + total_written_size, (size_t) (size - total_written_size)); + if (written_size <= 0) { + handle_net_err(written_size, false); + break; +@@ -99,6 +116,8 @@ uint32_t CommunicationStream::write_all_to_stream(char* buf, uint32_t size) { + } + + void CommunicationStream::close_stream() { ++ SSLUtils::shutdown_and_free_ssl(_ssl); ++ + if (_conn_fd >= 0) { + log_trace(jbooster, rpc)("Connection closed. stream_id=%u.", stream_id()); + os::close(_conn_fd); +diff --git a/src/hotspot/share/jbooster/net/communicationStream.hpp b/src/hotspot/share/jbooster/net/communicationStream.hpp +index 7b2cfd9db..926221986 100644 +--- a/src/hotspot/share/jbooster/net/communicationStream.hpp ++++ b/src/hotspot/share/jbooster/net/communicationStream.hpp +@@ -30,6 +30,8 @@ + #include "utilities/globalDefinitions.hpp" + + class Thread; ++typedef struct ssl_st SSL; ++typedef struct ssl_ctx_st SSL_CTX; + + /** + * Base class of ServerStream and ClientStream. +@@ -39,6 +41,7 @@ class Thread; + class CommunicationStream: public CHeapObj { + private: + int _conn_fd; // generated by the OS ++ SSL* _ssl; + uint32_t _stream_id; // generated by the server + int _errno; + +@@ -56,6 +59,8 @@ private: + int get_errno() { return _errno; } + int get_and_clear_errno() { int eno = _errno; _errno = 0; return eno; } + int return_errno_or_flag(int flag) { return get_errno() ? get_and_clear_errno() : flag; } ++ int read_from_fd_or_ssl(char* buf, size_t size); ++ int write_to_fd_or_ssl(char* buf, size_t size); + + uint32_t read_once_from_stream(char* buf, uint32_t size); + uint32_t read_all_from_stream(char* buf, uint32_t size); +@@ -71,6 +76,7 @@ private: + protected: + CommunicationStream(Thread* thread): + _conn_fd(-1), ++ _ssl(nullptr), + _stream_id(0), + _errno(0), + _msg_recv(SerializationMode::DESERIALIZE, this), +@@ -80,7 +86,11 @@ protected: + + virtual ~CommunicationStream() { close_stream(); } + +- void init_stream(int conn_fd) { _conn_fd = conn_fd; } ++ void init_stream(int conn_fd, SSL* ssl) { ++ _conn_fd = conn_fd; ++ _ssl = ssl; ++ } ++ + void close_stream(); + + int recv_message(); +diff --git a/src/hotspot/share/jbooster/net/errorCode.hpp b/src/hotspot/share/jbooster/net/errorCode.hpp +index 8dddd31f0..625ef6951 100644 +--- a/src/hotspot/share/jbooster/net/errorCode.hpp ++++ b/src/hotspot/share/jbooster/net/errorCode.hpp +@@ -29,6 +29,7 @@ + #define JB_ERROR_CODES(f) \ + f(CONN_CLOSED, "Connection has been closed" ) \ + f(CONN_CLOSED_BY_PEER, "Connection is closed by the other end" ) \ ++ f(BAD_SSL, "Unexpected SSL error during initialization" ) \ + f(BAD_MSG_SIZE, "Unexpected size of the received message" ) \ + f(BAD_MSG_TYPE, "Unexpected message type of the received message" ) \ + f(BAD_MSG_DATA, "Unexpected payload data of the received message" ) \ +diff --git a/src/hotspot/share/jbooster/net/serverListeningThread.cpp b/src/hotspot/share/jbooster/net/serverListeningThread.cpp +index 0f7b5dc8d..f01ab7bf1 100644 +--- a/src/hotspot/share/jbooster/net/serverListeningThread.cpp ++++ b/src/hotspot/share/jbooster/net/serverListeningThread.cpp +@@ -26,6 +26,7 @@ + #include "classfile/vmSymbols.hpp" + #include "jbooster/net/serverListeningThread.hpp" + #include "jbooster/net/serverStream.hpp" ++#include "jbooster/net/sslUtils.hpp" + #include "jbooster/server/serverDataManager.hpp" + #include "jbooster/server/serverMessageHandler.hpp" + #include "logging/log.hpp" +@@ -38,6 +39,7 @@ + #include "runtime/thread.inline.hpp" + + ServerListeningThread* ServerListeningThread::_singleton = nullptr; ++SSL_CTX* ServerListeningThread::_server_ssl_ctx = nullptr; + + /** + * This function is called in the main thread. +@@ -45,7 +47,21 @@ ServerListeningThread* ServerListeningThread::_singleton = nullptr; + ServerListeningThread* ServerListeningThread::start_thread(const char* address, + uint16_t port, + uint32_t timeout_ms, ++ const char* ssl_key, ++ const char* ssl_cert, + TRAPS) { ++ if ((ssl_key == nullptr && ssl_cert != nullptr) || (ssl_key != nullptr && ssl_cert == nullptr)) { ++ log_error(jbooster, rpc)("You must include --ssl-cert and --ssl-key together."); ++ vm_exit(1); ++ } ++ if (ssl_key != nullptr && ssl_cert != nullptr) { ++ if (!SSLUtils::init_ssl_lib()) { ++ log_error(jbooster, rpc)("Failed to load all functions from OpenSSL Dynamic Library."); ++ vm_exit(1); ++ } ++ server_init_ssl_ctx(ssl_key, ssl_cert); ++ } ++ + JavaThread* new_thread = new JavaThread(&server_listener_thread_entry); + guarantee(new_thread != nullptr && new_thread->osthread() != nullptr, "sanity"); + guarantee(_singleton == nullptr, "sanity"); +@@ -74,6 +90,35 @@ ServerListeningThread* ServerListeningThread::start_thread(const char* address, + return _singleton; + } + ++void ServerListeningThread::server_init_ssl_ctx(const char* ssl_key, const char* ssl_cert) { ++ SSLUtils::openssl_init_ssl(); ++ ++ _server_ssl_ctx = SSLUtils::ssl_ctx_new(SSLUtils::sslv23_server_method()); ++ if (_server_ssl_ctx == nullptr) { ++ log_error(jbooster, rpc)("Failed to create SSL context"); ++ vm_exit(1); ++ } ++ ++ SSLUtils::ssl_ctx_set_options(_server_ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2); ++ ++ const int security_level = 2; ++ SSLUtils::ssl_ctx_set_security_level(_server_ssl_ctx, security_level); ++ ++ const char* error_description = nullptr; ++ if (SSLUtils::ssl_ctx_use_certificate_file(_server_ssl_ctx, ssl_cert, SSL_FILETYPE_PEM) != 1) { ++ error_description = "Failed to add certificate file to SSL context."; ++ SSLUtils::handle_ssl_ctx_error(error_description); ++ } ++ if (SSLUtils::ssl_ctx_use_privatekey_file(_server_ssl_ctx, ssl_key, SSL_FILETYPE_PEM) != 1) { ++ error_description = "Failed to add private key file to SSL context."; ++ SSLUtils::handle_ssl_ctx_error(error_description); ++ } ++ if (SSLUtils::ssl_ctx_check_private_key(_server_ssl_ctx) != 1) { ++ error_description = "Private key doesn't match certificate."; ++ SSLUtils::handle_ssl_ctx_error(error_description); ++ } ++} ++ + void ServerListeningThread::server_listener_thread_entry(JavaThread* thread, TRAPS) { + JB_TRY { + JB_THROW(_singleton->run_listener(thread)); +@@ -109,7 +154,7 @@ uint32_t ServerListeningThread::new_stream_id() { + /** + * Add the new connection to the connection thread pool. + */ +-void ServerListeningThread::handle_new_connection(int conn_fd, TRAPS) { ++void ServerListeningThread::handle_new_connection(int conn_fd, SSL* ssl, TRAPS) { + ThreadInVMfromNative tiv(THREAD); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); +@@ -117,12 +162,14 @@ void ServerListeningThread::handle_new_connection(int conn_fd, TRAPS) { + JavaValue result(T_BOOLEAN); + JavaCallArguments args; + args.push_int(conn_fd); ++ args.push_long((jlong)(uintptr_t) ssl); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + vmSymbols::receiveConnection_name(), +- vmSymbols::int_bool_signature(), ++ vmSymbols::receiveConnection_signature(), + &args, CATCH); + if (!result.get_jboolean()) { + log_warning(jbooster, rpc)("Failed to handle the new connection as the thread pool is full."); ++ SSLUtils::shutdown_and_free_ssl(ssl); + os::close(conn_fd); + } + } +@@ -133,11 +180,11 @@ void ServerListeningThread::handle_new_connection(int conn_fd, TRAPS) { + * This function is called in another java thread (not in ServerListeningThread thread). + * So do not use `this` here. + */ +-void ServerListeningThread::handle_connection(int conn_fd) { ++void ServerListeningThread::handle_connection(int conn_fd, long ssl) { + JavaThread* THREAD = JavaThread::current(); + ThreadToNativeFromVM ttn(THREAD); + +- ServerStream* server_stream = new ServerStream(conn_fd, THREAD); ++ ServerStream* server_stream = new ServerStream(conn_fd, (SSL*)(uintptr_t) ssl, THREAD); + ThreadServerStreamMark tssm(server_stream, true, THREAD); + server_stream->handle_meta_request(new_stream_id()); + if (server_stream->is_stream_closed()) return; +diff --git a/src/hotspot/share/jbooster/net/serverListeningThread.hpp b/src/hotspot/share/jbooster/net/serverListeningThread.hpp +index 49f5efb3f..abf6e4f79 100644 +--- a/src/hotspot/share/jbooster/net/serverListeningThread.hpp ++++ b/src/hotspot/share/jbooster/net/serverListeningThread.hpp +@@ -25,10 +25,15 @@ + #define SHARE_JBOOSTER_NET_SERVERLISTENINGTHREAD_HPP + + #include "runtime/thread.hpp" ++#include // for addrinfo ++ ++typedef struct ssl_st SSL; ++typedef struct ssl_ctx_st SSL_CTX; + + class ServerListeningThread : public CHeapObj { + private: + static ServerListeningThread* _singleton; ++ static SSL_CTX* _server_ssl_ctx; + + JavaThread* const _the_java_thread; + const char* const _address; +@@ -40,13 +45,15 @@ private: + volatile bool _exit_flag; + + private: ++ static void server_init_ssl_ctx(const char* ssl_key, const char* ssl_cert); + static void server_listener_thread_entry(JavaThread* thread, TRAPS); + + ServerListeningThread(JavaThread* the_java_thread, const char* address, uint16_t port, uint32_t timeout_ms); + + uint32_t new_stream_id(); + +- void handle_new_connection(int conn_fd, TRAPS); ++ bool prepare_and_handle_new_connection(int server_fd, sockaddr_in* acc_addr, socklen_t* acc_addrlen, TRAPS); ++ void handle_new_connection(int conn_fd, SSL* ssl, TRAPS); + + int run_listener(TRAPS); + +@@ -54,6 +61,8 @@ public: + static ServerListeningThread* start_thread(const char* address, + uint16_t port, + uint32_t timeout_ms, ++ const char* ssl_key, ++ const char* ssl_cert, + TRAPS); + + ~ServerListeningThread(); +@@ -61,7 +70,7 @@ public: + bool get_exit_flag() { return _exit_flag; } + void set_exit_flag() { _exit_flag = true; } + +- void handle_connection(int conn_fd); ++ void handle_connection(int conn_fd, long ssl); + }; + + #endif // SHARE_JBOOSTER_NET_SERVERLISTENINGTHREAD_HPP +diff --git a/src/hotspot/share/jbooster/net/serverStream.cpp b/src/hotspot/share/jbooster/net/serverStream.cpp +index a3e7fa5c6..3f70f273b 100644 +--- a/src/hotspot/share/jbooster/net/serverStream.cpp ++++ b/src/hotspot/share/jbooster/net/serverStream.cpp +@@ -30,16 +30,16 @@ + #include "runtime/thread.hpp" + #include "runtime/timerTrace.hpp" + +-ServerStream::ServerStream(int conn_fd): ++ServerStream::ServerStream(int conn_fd, SSL* ssl): + CommunicationStream(Thread::current_or_null()), + _session_data(nullptr) { +- init_stream(conn_fd); ++ init_stream(conn_fd, ssl); + } + +-ServerStream::ServerStream(int conn_fd, Thread* thread): ++ServerStream::ServerStream(int conn_fd, SSL* ssl, Thread* thread): + CommunicationStream(thread), + _session_data(nullptr) { +- init_stream(conn_fd); ++ init_stream(conn_fd, ssl); + } + + ServerStream::~ServerStream() { +@@ -105,7 +105,8 @@ int ServerStream::sync_session_meta__server() { + RpcCompatibility comp; + uint64_t client_random_id; + JClientArguments program_args(false); // on-stack allocation to prevent memory leakage +- JB_RETURN(parse_request(&comp, &client_random_id, &program_args)); ++ JClientBoostLevel boost_level; ++ JB_RETURN(parse_request(&comp, &client_random_id, &program_args, &boost_level)); + + const char* unsupport_reason = nullptr; + if (!program_args.check_compatibility_with_server(&unsupport_reason)) { +@@ -115,7 +116,7 @@ int ServerStream::sync_session_meta__server() { + JB_RETURN(JBErr::ABORT_CUR_PHRASE); + } + +- JClientSessionData* sd = sdm.create_session(client_random_id, &program_args, Thread::current()); ++ JClientSessionData* sd = sdm.create_session(client_random_id, &program_args, boost_level, Thread::current()); + JClientProgramData* pd = sd->program_data(); + set_session_data(sd); + +@@ -123,8 +124,8 @@ int ServerStream::sync_session_meta__server() { + uint32_t session_id = sd->session_id(); + uint32_t program_id = pd->program_id(); + bool has_remote_clr = pd->clr_cache_state().is_cached(); +- bool has_remote_cds = pd->cds_cache_state().is_cached(); +- bool has_remote_aot = pd->aot_cache_state().is_cached(); ++ bool has_remote_cds = boost_level.is_cds_agg_enabled() ? pd->agg_cds_cache_state().is_cached() : pd->dy_cds_cache_state().is_cached(); ++ bool has_remote_aot = boost_level.is_aot_pgo_enabled() ? pd->aot_pgo_cache_state().is_cached() : pd->aot_static_cache_state().is_cached(); + JB_RETURN(send_response(stream_id_addr(), &server_random_id, &session_id, &program_id, + &has_remote_clr, &has_remote_cds, &has_remote_aot)); + log_info(jbooster, rpc)("New client: session_id=%u, program_id=%u, " +@@ -138,10 +139,10 @@ int ServerStream::sync_session_meta__server() { + BOOL_TO_STR(has_remote_aot), + stream_id()); + +- return handle_sync_requests(pd); ++ return handle_sync_requests(pd, boost_level.is_cds_agg_enabled(), boost_level.is_aot_pgo_enabled()); + } + +-int ServerStream::handle_sync_requests(JClientProgramData* pd) { ++int ServerStream::handle_sync_requests(JClientProgramData* pd, bool enable_cds_agg, bool enable_aot_pgo) { + bool not_end = true; + do { + MessageType type; +@@ -155,13 +156,15 @@ int ServerStream::handle_sync_requests(JClientProgramData* pd) { + } + case MessageType::GetAggressiveCDSCache: { + TraceTime tt("Send cds", TRACETIME_LOG(Info, jbooster)); +- FileWrapper file(pd->cds_cache_state().file_path(), SerializationMode::SERIALIZE); ++ const char* cds_file_path = enable_cds_agg ? pd->agg_cds_cache_state().file_path() : pd->dy_cds_cache_state().file_path(); ++ FileWrapper file(cds_file_path, SerializationMode::SERIALIZE); + JB_RETURN(file.send_file(this)); + break; + } + case MessageType::GetLazyAOTCache: { + TraceTime tt("Send aot", TRACETIME_LOG(Info, jbooster)); +- FileWrapper file(pd->aot_cache_state().file_path(), SerializationMode::SERIALIZE); ++ const char* aot_file_path = enable_aot_pgo ? pd->aot_pgo_cache_state().file_path() : pd->aot_static_cache_state().file_path(); ++ FileWrapper file(aot_file_path, SerializationMode::SERIALIZE); + JB_RETURN(file.send_file(this)); + break; + } +diff --git a/src/hotspot/share/jbooster/net/serverStream.hpp b/src/hotspot/share/jbooster/net/serverStream.hpp +index 56d05dabb..7239813cd 100644 +--- a/src/hotspot/share/jbooster/net/serverStream.hpp ++++ b/src/hotspot/share/jbooster/net/serverStream.hpp +@@ -40,11 +40,11 @@ private: + int sync_stream_meta__server(); + int resync_session_and_stream_meta__server(); + +- int handle_sync_requests(JClientProgramData* pd); ++ int handle_sync_requests(JClientProgramData* pd, bool enable_cds_agg, bool enable_aot_pgo); + + public: +- ServerStream(int conn_fd); +- ServerStream(int conn_fd, Thread* thread); ++ ServerStream(int conn_fd, SSL* ssl); ++ ServerStream(int conn_fd, SSL* ssl, Thread* thread); + ~ServerStream(); + + void handle_meta_request(uint32_t stream_id); +diff --git a/src/hotspot/share/jbooster/net/sslUtils.cpp b/src/hotspot/share/jbooster/net/sslUtils.cpp +new file mode 100644 +index 000000000..96a159871 +--- /dev/null ++++ b/src/hotspot/share/jbooster/net/sslUtils.cpp +@@ -0,0 +1,231 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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 "logging/log.hpp" ++#include "jbooster/net/sslUtils.hpp" ++#include "runtime/java.hpp" ++#include ++#include ++ ++void* SSLUtils::_lib_handle = nullptr; ++enum SSLUtils::SSLVersion SSLUtils::_version = V0; ++const char* SSLUtils::OPENSSL_VERSION_1_0 = "OpenSSL 1.0."; ++const char* SSLUtils::OPENSSL_VERSION_1_1 = "OpenSSL 1.1."; ++const char* SSLUtils::OPENSSL_VERSION_3_X = "OpenSSL 3."; ++ ++SSLUtils::openssl_version_func_t SSLUtils::_openssl_version = nullptr; ++SSLUtils::openssl_init_ssl_func_t SSLUtils::_openssl_init_ssl = nullptr; ++SSLUtils::ssl_ctx_new_func_t SSLUtils::_ssl_ctx_new = nullptr; ++SSLUtils::sslv23_client_method_func_t SSLUtils::_sslv23_client_method = nullptr; ++SSLUtils::sslv23_server_method_func_t SSLUtils::_sslv23_server_method = nullptr; ++SSLUtils::ssl_ctx_ctrl_func_t SSLUtils::_ssl_ctx_ctrl = nullptr; ++SSLUtils::ssl_ctx_set_options_func_t SSLUtils::_ssl_ctx_set_options = nullptr; ++SSLUtils::ssl_ctx_set_cipher_list_func_t SSLUtils::_ssl_ctx_set_cipher_list = nullptr; ++SSLUtils::ssl_ctx_set_ciphersuites_func_t SSLUtils::_ssl_ctx_set_ciphersuites = nullptr; ++SSLUtils::ssl_ctx_set_verify_func_t SSLUtils::_ssl_ctx_set_verify = nullptr; ++SSLUtils::ssl_ctx_load_verify_locations_func_t SSLUtils::_ssl_ctx_load_verify_locations = nullptr; ++SSLUtils::ssl_ctx_use_certificate_file_func_t SSLUtils::_ssl_ctx_use_certificate_file = nullptr; ++SSLUtils::ssl_ctx_use_privatekey_file_func_t SSLUtils::_ssl_ctx_use_privatekey_file = nullptr; ++SSLUtils::ssl_ctx_check_private_key_func_t SSLUtils::_ssl_ctx_check_private_key = nullptr; ++SSLUtils::ssl_ctx_set_security_level_func_t SSLUtils::_ssl_ctx_set_security_level = nullptr; ++SSLUtils::ssl_new_func_t SSLUtils::_ssl_new = nullptr; ++SSLUtils::ssl_set_fd_func_t SSLUtils::_ssl_set_fd = nullptr; ++SSLUtils::ssl_get_error_func_t SSLUtils::_ssl_get_error = nullptr; ++SSLUtils::ssl_accept_func_t SSLUtils::_ssl_accept = nullptr; ++SSLUtils::ssl_connect_func_t SSLUtils::_ssl_connect = nullptr; ++SSLUtils::ssl_read_func_t SSLUtils::_ssl_read = nullptr; ++SSLUtils::ssl_write_func_t SSLUtils::_ssl_write = nullptr; ++SSLUtils::ssl_shutdown_func_t SSLUtils::_ssl_shutdown = nullptr; ++SSLUtils::ssl_free_func_t SSLUtils::_ssl_free = nullptr; ++SSLUtils::ssl_ctx_free_func_t SSLUtils::_ssl_ctx_free = nullptr; ++SSLUtils::ssl_get_peer_certificate_func_t SSLUtils::_ssl_get_peer_certificate = nullptr; ++SSLUtils::ssl_get_verify_result_func_t SSLUtils::_ssl_get_verify_result = nullptr; ++SSLUtils::err_peak_error_func_t SSLUtils::_err_peak_error = nullptr; ++SSLUtils::err_error_string_n_func_t SSLUtils::_err_error_string_n = nullptr; ++SSLUtils::err_get_error_func_t SSLUtils::_err_get_error = nullptr; ++SSLUtils::err_print_errors_fp_func_t SSLUtils::_err_print_errors_fp = nullptr; ++ ++static void* open_ssl_lib() { ++ const char* const lib_names[] = { ++ "libssl.so.3", ++ "libssl.so.1.1", ++ "libssl.so.1.0.0", ++ "libssl.so.10", ++ "libssl.so" ++ }; ++ const int lib_names_len = sizeof(lib_names) / sizeof(lib_names[0]); ++ ++ void* res = nullptr; ++ for (int i = 0; i < lib_names_len; ++i) { ++ res = ::dlopen(lib_names[i], RTLD_NOW); ++ if (res != nullptr) { ++ break; ++ } ++ } ++ return res; ++} ++ ++void SSLUtils::find_ssl_version() { ++ const char* openssl_version_res = nullptr; ++ _openssl_version = (openssl_version_func_t)::dlsym(_lib_handle, "OpenSSL_version"); ++ if (_openssl_version) { ++ openssl_version_res = openssl_version(); ++ if (0 == strncmp(openssl_version_res, OPENSSL_VERSION_1_1, strlen(OPENSSL_VERSION_1_1))) { ++ _version = V1_1; ++ } else if (0 == strncmp(openssl_version_res, OPENSSL_VERSION_3_X, strlen(OPENSSL_VERSION_3_X))) { ++ _version = V3; ++ } ++ } else { ++ _openssl_version = (openssl_version_func_t) ::dlsym(_lib_handle, "SSLeay_version"); ++ if (_openssl_version) { ++ openssl_version_res = openssl_version(); ++ if (0 == strncmp(openssl_version_res, OPENSSL_VERSION_1_0, strlen(OPENSSL_VERSION_1_0))) { ++ _version = V1_0; ++ } ++ } ++ } ++} ++ ++void SSLUtils::find_address_in_dynamic_lib() { ++ if (_version == V3) { ++ _ssl_get_peer_certificate = (ssl_get_peer_certificate_func_t) ::dlsym(_lib_handle, "SSL_get1_peer_certificate"); ++ } else { ++ _ssl_get_peer_certificate = (ssl_get_peer_certificate_func_t) ::dlsym(_lib_handle, "SSL_get_peer_certificate"); ++ } ++ ++ _openssl_init_ssl = (openssl_init_ssl_func_t) ::dlsym(_lib_handle, "OPENSSL_init_ssl"); ++ _sslv23_client_method = (sslv23_client_method_func_t) ::dlsym(_lib_handle, "TLS_client_method"); ++ _sslv23_server_method = (sslv23_server_method_func_t) ::dlsym(_lib_handle, "TLS_server_method"); ++ _ssl_ctx_new = (ssl_ctx_new_func_t) ::dlsym(_lib_handle, "SSL_CTX_new"); ++ _ssl_ctx_ctrl = (ssl_ctx_ctrl_func_t) ::dlsym(_lib_handle, "SSL_CTX_ctrl"); ++ _ssl_ctx_set_options = (ssl_ctx_set_options_func_t) ::dlsym(_lib_handle, "SSL_CTX_set_options"); ++ _ssl_ctx_set_cipher_list = (ssl_ctx_set_cipher_list_func_t) ::dlsym(_lib_handle, "SSL_CTX_set_cipher_list"); ++ _ssl_ctx_set_ciphersuites = (ssl_ctx_set_ciphersuites_func_t) ::dlsym(_lib_handle, "SSL_CTX_set_ciphersuites"); ++ _ssl_ctx_set_verify = (ssl_ctx_set_verify_func_t) ::dlsym(_lib_handle, "SSL_CTX_set_verify"); ++ _ssl_ctx_load_verify_locations = (ssl_ctx_load_verify_locations_func_t) ::dlsym(_lib_handle, "SSL_CTX_load_verify_locations"); ++ _ssl_ctx_use_certificate_file = (ssl_ctx_use_certificate_file_func_t) ::dlsym(_lib_handle, "SSL_CTX_use_certificate_file"); ++ _ssl_ctx_use_privatekey_file = (ssl_ctx_use_privatekey_file_func_t) ::dlsym(_lib_handle, "SSL_CTX_use_PrivateKey_file"); ++ _ssl_ctx_check_private_key = (ssl_ctx_check_private_key_func_t) ::dlsym(_lib_handle, "SSL_CTX_check_private_key"); ++ _ssl_ctx_set_security_level = (ssl_ctx_set_security_level_func_t) ::dlsym(_lib_handle, "SSL_CTX_set_security_level"); ++ _ssl_new = (ssl_new_func_t) ::dlsym(_lib_handle, "SSL_new"); ++ _ssl_set_fd = (ssl_set_fd_func_t) ::dlsym(_lib_handle, "SSL_set_fd"); ++ _ssl_get_error = (ssl_get_error_func_t) ::dlsym(_lib_handle, "SSL_get_error"); ++ _ssl_accept = (ssl_accept_func_t) ::dlsym(_lib_handle, "SSL_accept"); ++ _ssl_connect = (ssl_connect_func_t) ::dlsym(_lib_handle, "SSL_connect"); ++ _ssl_read = (ssl_read_func_t) ::dlsym(_lib_handle, "SSL_read"); ++ _ssl_write = (ssl_write_func_t) ::dlsym(_lib_handle, "SSL_write"); ++ _ssl_shutdown = (ssl_shutdown_func_t) ::dlsym(_lib_handle, "SSL_shutdown"); ++ _ssl_free = (ssl_free_func_t) ::dlsym(_lib_handle, "SSL_free"); ++ _ssl_ctx_free = (ssl_ctx_free_func_t) ::dlsym(_lib_handle, "SSL_CTX_free"); ++ _ssl_get_verify_result = (ssl_get_verify_result_func_t) ::dlsym(_lib_handle, "SSL_get_verify_result"); ++ _err_peak_error = (err_peak_error_func_t) ::dlsym(_lib_handle, "ERR_peek_error"); ++ _err_error_string_n = (err_error_string_n_func_t) ::dlsym(_lib_handle, "ERR_error_string_n"); ++ _err_get_error = (err_get_error_func_t) ::dlsym(_lib_handle, "ERR_get_error"); ++ _err_print_errors_fp = (err_print_errors_fp_func_t) ::dlsym(_lib_handle, "ERR_print_errors_fp"); ++} ++ ++bool SSLUtils::check_if_find_all_functions() { ++ if ( ++ (_openssl_version == nullptr) || ++ (_openssl_init_ssl == nullptr) || ++ (_ssl_ctx_new == nullptr) || ++ (_sslv23_client_method == nullptr) || ++ (_sslv23_server_method == nullptr) || ++ (_ssl_ctx_ctrl == nullptr) || ++ (_ssl_ctx_set_options == nullptr) || ++ (_ssl_ctx_set_cipher_list == nullptr) || ++ (_ssl_ctx_set_ciphersuites == nullptr) || ++ (_ssl_ctx_set_verify == nullptr) || ++ (_ssl_ctx_load_verify_locations == nullptr) || ++ (_ssl_ctx_use_certificate_file == nullptr) || ++ (_ssl_ctx_use_privatekey_file == nullptr) || ++ (_ssl_ctx_check_private_key == nullptr) || ++ (_ssl_ctx_set_security_level == nullptr) || ++ (_ssl_new == nullptr) || ++ (_ssl_set_fd == nullptr) || ++ (_ssl_get_error == nullptr) || ++ (_ssl_accept == nullptr) || ++ (_ssl_connect == nullptr) || ++ (_ssl_read == nullptr) || ++ (_ssl_write == nullptr) || ++ (_ssl_shutdown == nullptr) || ++ (_ssl_free == nullptr) || ++ (_ssl_ctx_free == nullptr) || ++ (_ssl_get_peer_certificate == nullptr) || ++ (_ssl_get_verify_result == nullptr) || ++ (_err_peak_error == nullptr) || ++ (_err_error_string_n == nullptr) || ++ (_err_get_error == nullptr) || ++ (_err_print_errors_fp == nullptr) ++ ) { return false; } ++ return true; ++} ++ ++bool SSLUtils::init_ssl_lib() { ++ _lib_handle = open_ssl_lib(); ++ if (_lib_handle == nullptr) { ++ return false; ++ } ++ ++ find_ssl_version(); ++ if (_version == V0) { ++ return false; ++ } else if (_version == V1_0) { ++ log_error(jbooster, rpc)("JBooster only supports OpenSSL 1.1.0+."); ++ return false; ++ } ++ ++ find_address_in_dynamic_lib(); ++ return check_if_find_all_functions(); ++} ++ ++void SSLUtils::shutdown_and_free_ssl(SSL*& ssl) { ++ if (ssl != nullptr) { ++ SSLUtils::ssl_shutdown(ssl); ++ SSLUtils::ssl_free(ssl); ++ ssl = nullptr; ++ } ++} ++ ++void SSLUtils::handle_ssl_ctx_error(const char* error_description) { ++ log_error(jbooster, rpc)("%s", error_description); ++ if (log_is_enabled(Error, jbooster, rpc)) { ++ SSLUtils::err_print_errors_fp(stderr); ++ } ++ vm_exit(1); ++} ++ ++void SSLUtils::handle_ssl_error(SSL* ssl, int ret, const char* error_description) { ++ const char* errno_copy = strerror(errno); ++ int error = SSLUtils::ssl_get_error(ssl, ret); ++ unsigned long earliest_error = SSLUtils::err_peak_error(); ++ ++ char error_string[256] = {0}; ++ SSLUtils::err_error_string_n(earliest_error, error_string, sizeof(error_string)); ++ ++ log_error(jbooster, rpc)("%s! Error Code: %d, Earliest Error: %lu, Error String: %s, Errno: %s", ++ error_description, error, earliest_error, error_string, errno_copy); ++ if (log_is_enabled(Error, jbooster, rpc)) { ++ SSLUtils::err_print_errors_fp(stderr); ++ } ++} +\ No newline at end of file +diff --git a/src/hotspot/share/jbooster/net/sslUtils.hpp b/src/hotspot/share/jbooster/net/sslUtils.hpp +new file mode 100644 +index 000000000..0b8253b40 +--- /dev/null ++++ b/src/hotspot/share/jbooster/net/sslUtils.hpp +@@ -0,0 +1,148 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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_JBOOSTER_UTILITIES_SSLUtils_HPP ++#define SHARE_JBOOSTER_UTILITIES_SSLUtils_HPP ++ ++#include "memory/allocation.hpp" ++#include ++ ++class SSLUtils : AllStatic { ++ typedef const char* (*openssl_version_func_t)(int num); ++ typedef int (*openssl_init_ssl_func_t)(uint64_t opts, const void* settings); ++ typedef SSL_CTX* (*ssl_ctx_new_func_t)(const SSL_METHOD* meth); ++ typedef SSL_METHOD* (*sslv23_client_method_func_t)(void); ++ typedef SSL_METHOD* (*sslv23_server_method_func_t)(void); ++ typedef long (*ssl_ctx_ctrl_func_t)(SSL_CTX* ctx, int cmd, long larg, void* parg); ++ typedef uint64_t (*ssl_ctx_set_options_func_t)(SSL_CTX* ctx, uint64_t options); ++ typedef int (*ssl_ctx_set_cipher_list_func_t)(SSL_CTX* ctx, const char* str); ++ typedef int (*ssl_ctx_set_ciphersuites_func_t)(SSL_CTX* ctx, const char* str); ++ typedef void (*ssl_ctx_set_verify_func_t)(SSL_CTX* ctx, int mode, SSL_verify_cb callback); ++ typedef int (*ssl_ctx_load_verify_locations_func_t)(SSL_CTX* ctx, const char* CAfile, const char* CApath); ++ typedef int (*ssl_ctx_use_certificate_file_func_t)(SSL_CTX* ctx, const char* file, int type); ++ typedef int (*ssl_ctx_use_privatekey_file_func_t)(SSL_CTX* ctx, const char* file, int type); ++ typedef int (*ssl_ctx_check_private_key_func_t)(const SSL_CTX* ctx); ++ typedef void (*ssl_ctx_set_security_level_func_t)(SSL_CTX *ctx, int level); ++ typedef SSL* (*ssl_new_func_t)(SSL_CTX* ctx); ++ typedef int (*ssl_set_fd_func_t)(SSL* s, int fd); ++ typedef int (*ssl_get_error_func_t)(const SSL* s, int ret_code); ++ typedef int (*ssl_accept_func_t)(const SSL* ssl); ++ typedef int (*ssl_connect_func_t)(const SSL* ssl); ++ typedef int (*ssl_read_func_t)(SSL* ssl, void* buf, int num); ++ typedef int (*ssl_write_func_t)(SSL* ssl, const void* buf, int num); ++ typedef int (*ssl_shutdown_func_t)(SSL* ssl); ++ typedef void (*ssl_free_func_t)(SSL* ssl); ++ typedef void (*ssl_ctx_free_func_t)(SSL_CTX* ctx); ++ typedef X509* (*ssl_get_peer_certificate_func_t)(const SSL* ssl); ++ typedef long (*ssl_get_verify_result_func_t)(const SSL* ssl); ++ typedef unsigned long (*err_peak_error_func_t)(void); ++ typedef void (*err_error_string_n_func_t)(unsigned long e, char *buf, size_t len); ++ typedef unsigned long (*err_get_error_func_t)(void); ++ typedef void (*err_print_errors_fp_func_t)(FILE *fp); ++ ++ static enum SSLVersion { ++ V0, V1_0, V1_1, V3 ++ } _version; ++ ++ static void* _lib_handle; ++ static const char* OPENSSL_VERSION_1_0; ++ static const char* OPENSSL_VERSION_1_1; ++ static const char* OPENSSL_VERSION_3_X; ++ ++ static openssl_version_func_t _openssl_version; ++ static openssl_init_ssl_func_t _openssl_init_ssl; ++ static ssl_ctx_new_func_t _ssl_ctx_new; ++ static sslv23_client_method_func_t _sslv23_client_method; ++ static sslv23_server_method_func_t _sslv23_server_method; ++ static ssl_ctx_ctrl_func_t _ssl_ctx_ctrl; ++ static ssl_ctx_set_options_func_t _ssl_ctx_set_options; ++ static ssl_ctx_set_cipher_list_func_t _ssl_ctx_set_cipher_list; ++ static ssl_ctx_set_ciphersuites_func_t _ssl_ctx_set_ciphersuites; ++ static ssl_ctx_set_verify_func_t _ssl_ctx_set_verify; ++ static ssl_ctx_load_verify_locations_func_t _ssl_ctx_load_verify_locations; ++ static ssl_ctx_use_certificate_file_func_t _ssl_ctx_use_certificate_file; ++ static ssl_ctx_use_privatekey_file_func_t _ssl_ctx_use_privatekey_file; ++ static ssl_ctx_check_private_key_func_t _ssl_ctx_check_private_key; ++ static ssl_ctx_set_security_level_func_t _ssl_ctx_set_security_level; ++ static ssl_new_func_t _ssl_new; ++ static ssl_set_fd_func_t _ssl_set_fd; ++ static ssl_get_error_func_t _ssl_get_error; ++ static ssl_accept_func_t _ssl_accept; ++ static ssl_connect_func_t _ssl_connect; ++ static ssl_read_func_t _ssl_read; ++ static ssl_write_func_t _ssl_write; ++ static ssl_shutdown_func_t _ssl_shutdown; ++ static ssl_free_func_t _ssl_free; ++ static ssl_ctx_free_func_t _ssl_ctx_free; ++ static ssl_get_peer_certificate_func_t _ssl_get_peer_certificate; ++ static ssl_get_verify_result_func_t _ssl_get_verify_result; ++ static err_peak_error_func_t _err_peak_error; ++ static err_error_string_n_func_t _err_error_string_n; ++ static err_get_error_func_t _err_get_error; ++ static err_print_errors_fp_func_t _err_print_errors_fp; ++ ++public: ++ static bool init_ssl_lib(); ++ static void find_ssl_version(); ++ static void find_address_in_dynamic_lib(); ++ static bool check_if_find_all_functions(); ++ ++ static const char* openssl_version() { return (*_openssl_version)(0); } ++ ++ static int openssl_init_ssl() { return (*_openssl_init_ssl)(0, NULL); } ++ static SSL_CTX* ssl_ctx_new(const SSL_METHOD* meth) { return (*_ssl_ctx_new)(meth); } ++ static SSL_METHOD* sslv23_client_method() { return (*_sslv23_client_method)(); } ++ static SSL_METHOD* sslv23_server_method() { return (*_sslv23_server_method)(); } ++ static long ssl_ctx_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) { return (*_ssl_ctx_ctrl)(ctx, cmd, larg, parg); } ++ static uint64_t ssl_ctx_set_options(SSL_CTX* ctx, uint64_t options) { return (*_ssl_ctx_set_options)(ctx, options); }; ++ static int ssl_ctx_set_cipher_list(SSL_CTX* ctx, const char* str) { return (*_ssl_ctx_set_cipher_list)(ctx, str); }; ++ static int ssl_ctx_set_ciphersuites(SSL_CTX* ctx, const char* str) { return (*_ssl_ctx_set_ciphersuites)(ctx, str); }; ++ static void ssl_ctx_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb callback) { return (*_ssl_ctx_set_verify)(ctx, mode, callback); } ++ static int ssl_ctx_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { return (*_ssl_ctx_load_verify_locations)(ctx, CAfile, CApath); } ++ static int ssl_ctx_use_certificate_file(SSL_CTX* ctx, const char* file, int type) { return (*_ssl_ctx_use_certificate_file)(ctx, file, type); } ++ static int ssl_ctx_use_privatekey_file(SSL_CTX* ctx, const char* file, int type) { return (*_ssl_ctx_use_privatekey_file)(ctx, file, type); } ++ static void ssl_ctx_set_security_level(SSL_CTX *ctx, int level) { return (*_ssl_ctx_set_security_level)(ctx, level); } ++ static int ssl_ctx_check_private_key(const SSL_CTX* ctx) { return (*_ssl_ctx_check_private_key)(ctx); } ++ static SSL* ssl_new(SSL_CTX* ctx) { return (*_ssl_new)(ctx); } ++ static int ssl_set_fd(SSL* s, int fd) { return (*_ssl_set_fd)(s, fd); } ++ static int ssl_get_error(const SSL* s, int ret_code) { return (*_ssl_get_error)(s, ret_code); } ++ static int ssl_accept(const SSL* ssl) { return (*_ssl_accept)(ssl); } ++ static int ssl_connect(const SSL* ssl) { return (*_ssl_connect)(ssl); } ++ static int ssl_read(SSL* ssl, void* buf, int num) { return (*_ssl_read)(ssl, buf, num); } ++ static int ssl_write(SSL* ssl, const void* buf, int num) { return (*_ssl_write)(ssl, buf, num); } ++ static int ssl_shutdown(SSL* ssl) { return (*_ssl_shutdown)(ssl); } ++ static void ssl_free(SSL* ssl) { return (*_ssl_free)(ssl); } ++ static void ssl_ctx_free(SSL_CTX* ctx) { return (*_ssl_ctx_free)(ctx); } ++ static X509* ssl_get_peer_certificate(const SSL* ssl) { return (*_ssl_get_peer_certificate)(ssl); } ++ static long ssl_get_verify_result(const SSL* ssl) { return (*_ssl_get_verify_result)(ssl); } ++ static unsigned long err_peak_error() { return (*_err_peak_error)(); } ++ static void err_error_string_n(unsigned long e, char *buf, size_t len) { return (*_err_error_string_n)(e, buf, len); } ++ static unsigned long err_get_error() { return (*_err_get_error)(); } ++ static void err_print_errors_fp(FILE *fp) { return (*_err_print_errors_fp)(fp); }; ++ ++ static void shutdown_and_free_ssl(SSL*& ssl); ++ static void handle_ssl_ctx_error(const char* error_description); ++ static void handle_ssl_error(SSL* ssl, int ret, const char* error_description); ++}; ++ ++#endif // SHARE_JBOOSTER_UTILITIES_SSLUtils_HPP +diff --git a/src/hotspot/share/jbooster/server/serverDataManager.cpp b/src/hotspot/share/jbooster/server/serverDataManager.cpp +index 8753102a4..b2f36c4e5 100644 +--- a/src/hotspot/share/jbooster/server/serverDataManager.cpp ++++ b/src/hotspot/share/jbooster/server/serverDataManager.cpp +@@ -170,15 +170,13 @@ jlong RefCntWithTime::no_ref_time(jlong current_time) const { + + // -------------------------------- JClientCacheState --------------------------------- + +-JClientCacheState::JClientCacheState(): _is_allowed(false), +- _state(NOT_GENERATED), ++JClientCacheState::JClientCacheState(): _state(NOT_GENERATED), + _file_path(nullptr), + _file_timestamp(0) { + // The real assignment is in JClientProgramData::JClientProgramData(). + } + +-void JClientCacheState::init(bool allow, const char* file_path) { +- _is_allowed = allow; ++void JClientCacheState::init(const char* file_path) { + _file_path = file_path; + bool file_exists = FileUtils::is_file(file_path); + _state = file_exists ? GENERATED : NOT_GENERATED; +@@ -259,6 +257,11 @@ bool JClientCacheState::is_cached() { + return false; + } + ++const char* JClientCacheState::cache_state_str() { ++ return is_cached() ? "generated" : (is_being_generated() ? "generating" : "none"); ++} ++ ++ + void JClientCacheState::remove_file() { + if (is_cached()) { + remove_file_and_set_not_generated_sync(); +@@ -277,18 +280,16 @@ JClientProgramData::JClientProgramData(uint32_t program_id, JClientArguments* pr + _program_args->program_entry(), + _program_args->is_jar(), + _program_args->hash()); +- bool allow_clr = _program_args->jbooster_allow_clr(); +- bool allow_cds = _program_args->jbooster_allow_cds(); +- bool allow_aot = _program_args->jbooster_allow_aot(); +- bool allow_pgo = _program_args->jbooster_allow_pgo(); + const char* clr_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "clr.log"); +- const char* cds_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "cds.jsa"); +- const char* aot_path_suffix = allow_pgo ? "aot-pgo.so" : "aot.so"; +- const char* aot_path = JBoosterManager::calc_cache_path(sd, _program_str_id, aot_path_suffix); +- clr_cache_state().init(allow_clr, clr_path); +- cds_cache_state().init(allow_cds, cds_path); +- aot_cache_state().init(allow_aot, aot_path); +- _using_pgo = allow_pgo; ++ const char* dy_cds_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "cds-dy.jsa"); ++ const char* agg_cds_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "cds-agg.jsa"); ++ const char* aot_static_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "aot-static.so"); ++ const char* aot_pgo_path = JBoosterManager::calc_cache_path(sd, _program_str_id, "aot-pgo.so"); ++ clr_cache_state().init(clr_path); ++ dy_cds_cache_state().init(dy_cds_path); ++ agg_cds_cache_state().init(agg_cds_path); ++ aot_static_cache_state().init(aot_static_path); ++ aot_pgo_cache_state().init(aot_pgo_path); + } + + JClientProgramData::~JClientProgramData() { +@@ -357,10 +358,12 @@ public: + + JClientSessionData::JClientSessionData(uint32_t session_id, + uint64_t client_random_id, +- JClientProgramData* program_data): ++ JClientProgramData* program_data, ++ JClientBoostLevel boost_level): + _session_id(session_id), + _random_id(client_random_id), + _program_data(program_data), ++ _boost_level(boost_level), + _cl_s2c(), + _cl_c2s(), + _k_c2s(), +@@ -540,7 +543,7 @@ void ServerDataManager::init_cache_path(const char* optional_cache_path) { + FileUtils::mkdirs(JBoosterCachePath); + } + +-void ServerDataManager::init_phase3(int server_port, int connection_timeout, int cleanup_timeout, const char* cache_path, TRAPS) { ++void ServerDataManager::init_phase3(int server_port, int connection_timeout, int cleanup_timeout, const char* cache_path, const char* ssl_key, const char* ssl_cert, TRAPS) { + JBoosterManager::server_only(); + init_cache_path(cache_path); + +@@ -548,7 +551,7 @@ void ServerDataManager::init_phase3(int server_port, int connection_timeout, int + + ServerControlThread::set_unused_shared_data_cleanup_timeout(cleanup_timeout); + _singleton->_control_thread = ServerControlThread::start_thread(CHECK); +- _singleton->_listening_thread = ServerListeningThread::start_thread(JBoosterAddress, (uint16_t) server_port, connection_timeout, CHECK); ++ _singleton->_listening_thread = ServerListeningThread::start_thread(JBoosterAddress, (uint16_t) server_port, connection_timeout, ssl_key, ssl_cert, CHECK); + } + + /** +@@ -610,10 +613,11 @@ JClientSessionData* ServerDataManager::get_session(uint32_t session_id, Thread* + */ + JClientSessionData* ServerDataManager::create_session(uint64_t client_random_id, + JClientArguments* program_args, ++ JClientBoostLevel boost_level, + Thread* thread) { + uint32_t session_id = Atomic::add(&_next_session_allocation_id, 1u); + JClientProgramData* pd = get_or_create_program(program_args, thread); +- JClientSessionData* sd = new JClientSessionData(session_id, client_random_id, pd); ++ JClientSessionData* sd = new JClientSessionData(session_id, client_random_id, pd, boost_level); + + JClientSessionData** res = _sessions.put_if_absent(session_id, sd, thread); + guarantee(res != nullptr && *res == sd, "sanity"); +diff --git a/src/hotspot/share/jbooster/server/serverDataManager.hpp b/src/hotspot/share/jbooster/server/serverDataManager.hpp +index 9181acd10..2cb7d2de0 100644 +--- a/src/hotspot/share/jbooster/server/serverDataManager.hpp ++++ b/src/hotspot/share/jbooster/server/serverDataManager.hpp +@@ -133,7 +133,6 @@ class JClientCacheState final: public StackObj { + static const int BEING_GENERATED = 1; + static const int GENERATED = 2; + +- bool _is_allowed; + volatile int _state; + const char* _file_path; + uint64_t _file_timestamp; +@@ -149,9 +148,7 @@ public: + JClientCacheState(); + ~JClientCacheState(); + +- void init(bool allow, const char* file_path); +- +- bool is_allowed() { return _is_allowed; } ++ void init(const char* file_path); + + // /server/cache-- + const char* file_path() { return _file_path; } +@@ -167,6 +164,7 @@ public: + // Unlike the APIs above, the APIs above only change the atomic variables, + // while the following APIs checks whether the cache file exists. + bool is_cached(); ++ const char* cache_state_str(); + + void remove_file(); + }; +@@ -188,10 +186,10 @@ private: + RefCntWithTime _ref_cnt; + + JClientCacheState _clr_cache_state; +- JClientCacheState _cds_cache_state; +- JClientCacheState _aot_cache_state; +- +- bool _using_pgo; // use pgo if or not, as boost level 4 ++ JClientCacheState _dy_cds_cache_state; ++ JClientCacheState _agg_cds_cache_state; ++ JClientCacheState _aot_static_cache_state; ++ JClientCacheState _aot_pgo_cache_state; + + NONCOPYABLE(JClientProgramData); + +@@ -211,9 +209,10 @@ public: + RefCntWithTime& ref_cnt() { return _ref_cnt; } + + JClientCacheState& clr_cache_state() { return _clr_cache_state; } +- JClientCacheState& cds_cache_state() { return _cds_cache_state; } +- JClientCacheState& aot_cache_state() { return _aot_cache_state; } +- bool using_pgo() { return _using_pgo; } ++ JClientCacheState& dy_cds_cache_state() { return _dy_cds_cache_state; } ++ JClientCacheState& agg_cds_cache_state() { return _agg_cds_cache_state; } ++ JClientCacheState& aot_static_cache_state() { return _aot_static_cache_state; } ++ JClientCacheState& aot_pgo_cache_state() { return _aot_pgo_cache_state; } + }; + + /** +@@ -248,6 +247,8 @@ private: + + JClientProgramData* const _program_data; + ++ JClientBoostLevel _boost_level; ++ + // server-side CLD pointer -> client-side CLD pointer + AddressMap _cl_s2c; + // client-side CLD pointer -> server-side CLD pointer +@@ -269,7 +270,7 @@ private: + static bool remove_address(AddressMap& table, address key, Thread* thread); + + public: +- JClientSessionData(uint32_t session_id, uint64_t client_random_id, JClientProgramData* program_data); ++ JClientSessionData(uint32_t session_id, uint64_t client_random_id, JClientProgramData* program_data, JClientBoostLevel boost_level); + ~JClientSessionData(); + + uint32_t session_id() const { return _session_id; } +@@ -278,6 +279,8 @@ public: + + JClientProgramData* program_data() const { return _program_data; } + ++ const JClientBoostLevel& boost_level() const { return _boost_level; } ++ + address client_cld_address(ClassLoaderData* server_data, Thread* thread); + ClassLoaderData* server_cld_address(address client_data, Thread* thread); + ClassLoaderData* add_class_loader_if_absent(address client_cld_addr, +@@ -399,7 +402,7 @@ public: + + static jint init_phase1(); + static void init_phase2(TRAPS) { /* do nothing */ } +- static void init_phase3(int server_port, int connection_timeout, int cleanup_timeout, const char* cache_path, TRAPS); ++ static void init_phase3(int server_port, int connection_timeout, int cleanup_timeout, const char* cache_path, const char* ssl_key, const char* ssl_cert, TRAPS); + + // $HOME/.jbooster/server + const char* cache_dir_path() { return _cache_dir_path; } +@@ -416,6 +419,7 @@ public: + JClientSessionData* get_session(uint32_t session_id, Thread* thread); + JClientSessionData* create_session(uint64_t client_random_id, + JClientArguments* program_args, ++ JClientBoostLevel boost_level, + Thread* thread); + bool try_remove_session(uint32_t session_id, Thread* thread); + +diff --git a/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp b/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp +index 47e9c6b01..c0a7b8647 100644 +--- a/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp ++++ b/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp +@@ -93,19 +93,20 @@ public: + bool operator () (ServerDataManager::JClientProgramDataMap::KVNode* kv_node) { + JClientProgramData* pd = kv_node->value(); + JClientCacheState& clr = pd->clr_cache_state(); +- JClientCacheState& cds = pd->cds_cache_state(); +- JClientCacheState& aot = pd->aot_cache_state(); +- const char* clr_stat = (clr.is_cached() ? "generated" : (clr.is_being_generated() ? "generating" : "none")); +- const char* cds_stat = (cds.is_cached() ? "generated" : (cds.is_being_generated() ? "generating" : "none")); +- const char* aot_stat = (aot.is_cached() ? "generated" : (aot.is_being_generated() ? "generating" : "none")); ++ JClientCacheState& agg_cds = pd->agg_cds_cache_state(); ++ JClientCacheState& dy_cds = pd->dy_cds_cache_state(); ++ JClientCacheState& aot_static = pd->aot_static_cache_state(); ++ JClientCacheState& aot_pgo = pd->aot_pgo_cache_state(); + OUT_1("-"); + OUT_2("program_id: %u", pd->program_id()); + OUT_2("program_name: %s", pd->program_args()->program_name()); + OUT_2("program_hash: %x", pd->program_args()->hash()); + OUT_2("ref_cnt: %d", pd->ref_cnt().get()); +- OUT_2("clr_cache: %s", clr_stat); +- OUT_2("cds_cache: %s", cds_stat); +- OUT_2("aot_cache: %s", aot_stat); ++ OUT_2("clr_cache: %s", clr.cache_state_str()); ++ OUT_2("dy_cds_cache: %s", dy_cds.cache_state_str()); ++ OUT_2("agg_cds_cache: %s", agg_cds.cache_state_str()); ++ OUT_2("aot_static_cache: %s", aot_static.cache_state_str()); ++ OUT_2("aot_pgo_cache: %s", aot_pgo.cache_state_str()); + OUT_2("class_loader_size: " SIZE_FORMAT, pd->class_loaders()->size()); + if (pd->class_loaders()->size() > 0) { + OUT_2("class_loaders:"); +diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +index 962e93cc7..49c25efb1 100644 +--- a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp ++++ b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +@@ -275,7 +275,7 @@ int ServerMessageHandler::request_methods_not_compile(GrowableArray* me + } + + int ServerMessageHandler::request_client_cache(MessageType msg_type, JClientCacheState& cache) { +- if (cache.is_allowed() && !cache.is_cached() && cache.set_being_generated()) { ++ if (!cache.is_cached() && cache.set_being_generated()) { + JB_TRY { + JB_THROW(ss().send_request(msg_type)); + FileWrapper file(cache.file_path(), SerializationMode::DESERIALIZE); +@@ -294,10 +294,14 @@ int ServerMessageHandler::request_client_cache(MessageType msg_type, JClientCach + int ServerMessageHandler::handle_cache_file_sync_task(TRAPS) { + DebugUtils::assert_thread_nonjava_or_in_native(); + +- JClientProgramData* pd = ss().session_data()->program_data(); ++ JClientSessionData* sd = ss().session_data(); ++ JClientProgramData* pd = sd->program_data(); ++ bool enabling_cds_agg = sd->boost_level().is_cds_agg_enabled(); + + JB_RETURN(request_client_cache(MessageType::CacheClassLoaderResource, pd->clr_cache_state())); +- JB_RETURN(request_client_cache(MessageType::CacheAggressiveCDS, pd->cds_cache_state())); ++ ++ JClientCacheState& cds_cache_state = enabling_cds_agg ? pd->agg_cds_cache_state() : pd->dy_cds_cache_state(); ++ JB_RETURN(request_client_cache(MessageType::CacheAggressiveCDS, cds_cache_state)); + + JB_RETURN(ss().send_request(MessageType::EndOfCurrentPhase)); + return 0; +@@ -306,8 +310,10 @@ int ServerMessageHandler::handle_cache_file_sync_task(TRAPS) { + int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + DebugUtils::assert_thread_in_native(); + +- JClientProgramData* pd = ss().session_data()->program_data(); +- JClientCacheState& aot_cache_state = pd->aot_cache_state(); ++ JClientSessionData* sd = ss().session_data(); ++ JClientProgramData* pd = sd->program_data(); ++ bool enabling_aot_pgo = sd->boost_level().is_aot_pgo_enabled(); ++ JClientCacheState& aot_cache_state = enabling_aot_pgo ? pd->aot_pgo_cache_state() : pd->aot_static_cache_state(); + ResourceMark rm(THREAD); + GrowableArray klasses_to_compile; + GrowableArray methods_to_compile; +@@ -321,7 +327,7 @@ int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + JB_THROW(request_missing_class_loaders(THREAD)); + JB_THROW(request_missing_klasses(THREAD)); + JB_THROW(request_methods_to_compile(&klasses_to_compile, &methods_to_compile, THREAD)); +- if (pd->using_pgo()) { ++ if (enabling_aot_pgo) { + JB_THROW(request_methods_not_compile(&methods_not_compile, THREAD)); + JB_THROW(request_method_data(THREAD)); + } +@@ -338,7 +344,7 @@ int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + JB_RETURN(try_to_compile_lazy_aot(&klasses_to_compile, + &methods_to_compile, + &methods_not_compile, +- pd->using_pgo(), ++ enabling_aot_pgo, + THREAD)); + } else { // not compile in current thread + if (aot_cache_state.is_being_generated()) { +@@ -358,10 +364,10 @@ int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + int ServerMessageHandler::try_to_compile_lazy_aot(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, +- bool use_pgo, ++ bool enabling_aot_pgo, + TRAPS) { + JClientProgramData* pd = ss().session_data()->program_data(); +- JClientCacheState& aot_cache_state = pd->aot_cache_state(); ++ JClientCacheState& aot_cache_state = enabling_aot_pgo ? pd->aot_pgo_cache_state() : pd->aot_static_cache_state(); + if (klasses_to_compile->is_empty()) { + // the expected path without plugin + aot_cache_state.set_not_generated(); +@@ -376,17 +382,17 @@ int ServerMessageHandler::try_to_compile_lazy_aot(GrowableArray* + + ThreadInVMfromNative tiv(THREAD); + if (methods_to_compile->is_empty()) { +- successful = LazyAOT::compile_classes_by_graal(session_id, file_path, klasses_to_compile, use_pgo, THREAD); ++ successful = LazyAOT::compile_classes_by_graal(session_id, file_path, klasses_to_compile, enabling_aot_pgo, THREAD); + } else { + successful = LazyAOT::compile_methods_by_graal(session_id, file_path, klasses_to_compile, +- methods_to_compile, methods_not_compile, use_pgo, THREAD); ++ methods_to_compile, methods_not_compile, enabling_aot_pgo, THREAD); + } + + if (successful) { + guarantee(!HAS_PENDING_EXCEPTION, "sanity"); + chmod(file_path, S_IREAD); + aot_cache_state.set_generated(); +- log_info(jbooster, compilation)("Successfully comiled %d classes. session_id=%u.", ++ log_info(jbooster, compilation)("Successfully compiled %d classes. session_id=%u.", + klasses_to_compile->length(), + ss().session_id()); + } else if (HAS_PENDING_EXCEPTION) { +diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp +index c9d3768cd..b5a3bf95d 100644 +--- a/src/hotspot/share/prims/jvm.cpp ++++ b/src/hotspot/share/prims/jvm.cpp +@@ -3860,20 +3860,28 @@ JVM_END + + // JBooster //////////////////////////////////////////////////////////////////////// + +-JVM_ENTRY(void, JVM_JBoosterInitVM(JNIEnv *env, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path)) ++JVM_ENTRY(void, JVM_JBoosterInitVM(JNIEnv *env, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path, jstring ssl_key, jstring ssl_cert)) + #if INCLUDE_JBOOSTER + ResourceMark rm(THREAD); + const char* cache_path_c = NULL; + if (cache_path != NULL) { + cache_path_c = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(cache_path)); + } +- ServerDataManager::init_phase3(server_port, connection_timeout, cleanup_timeout, cache_path_c, THREAD); ++ const char* ssl_key_c = NULL; ++ if (ssl_key != NULL) { ++ ssl_key_c = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(ssl_key)); ++ } ++ const char* ssl_cert_c = NULL; ++ if (ssl_cert != NULL) { ++ ssl_cert_c = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(ssl_cert)); ++ } ++ ServerDataManager::init_phase3(server_port, connection_timeout, cleanup_timeout, cache_path_c, ssl_key_c, ssl_cert_c, THREAD); + #endif // INCLUDE_JBOOSTER + JVM_END + +-JVM_ENTRY(void, JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd)) ++JVM_ENTRY(void, JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd, jlong connection_ssl)) + #if INCLUDE_JBOOSTER +- ServerDataManager::get().listening_thread()->handle_connection(connection_fd); ++ ServerDataManager::get().listening_thread()->handle_connection(connection_fd, connection_ssl); + #endif // INCLUDE_JBOOSTER + JVM_END + +diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp +index 43da20b3f..a77f30b8c 100644 +--- a/src/hotspot/share/runtime/java.cpp ++++ b/src/hotspot/share/runtime/java.cpp +@@ -526,7 +526,7 @@ void before_exit(JavaThread* thread, bool halt) { + os::terminate_signal_thread(); + + #if INCLUDE_CDS +- if (DynamicDumpSharedSpaces JBOOSTER_ONLY(&& !(UseJBooster && ClientDataManager::get().is_cds_allowed()))) { ++ if (DynamicDumpSharedSpaces JBOOSTER_ONLY(&& !(UseJBooster && ClientDataManager::get().boost_level().is_cds_allowed()))) { + ExceptionMark em(thread); + DynamicArchive::dump(); + if (thread->has_pending_exception()) { +diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java +index 642fbafa6..db5d916ac 100644 +--- a/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java ++++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java +@@ -99,7 +99,8 @@ public final class JBooster { + connectionPool = new ConnectionPool(); + Main.initForJBooster(); + initInVM(options.getServerPort(), options.getConnectionTimeout(), +- options.getCleanupTimeout(), options.getCachePath()); ++ options.getCleanupTimeout(), options.getCachePath(), ++ options.getSSLKey(), options.getSSLCert()); + } + + private static void loop() { +@@ -167,8 +168,8 @@ public final class JBooster { + /** + * This method is invoked only in C++. + */ +- private static boolean receiveConnection(int connectionFd) { +- return connectionPool.execute(() -> handleConnection(connectionFd)); ++ private static boolean receiveConnection(int connectionFd, long connectionSSL) { ++ return connectionPool.execute(() -> handleConnection(connectionFd, connectionSSL)); + } + + /** +@@ -198,9 +199,9 @@ public final class JBooster { + return false; + } + +- private static native void initInVM(int serverPort, int connectionTimeout, int cleanupTimeout, String cachePath); ++ private static native void initInVM(int serverPort, int connectionTimeout, int cleanupTimeout, String cachePath, String sslKey, String sslCert); + +- private static native void handleConnection(int connectionFd); ++ private static native void handleConnection(int connectionFd, long connectionSSL); + + static native void printStoredClientData(boolean printAll); + +diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java b/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java +index 869e5ca97..f81d69287 100644 +--- a/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java ++++ b/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java +@@ -93,6 +93,18 @@ public final class Options { + protected void process(Options options, String arg) { + options.interactive = false; + } ++ }, new Option(of("--ssl-key"), "file-path", ++ "The file path to save server SSL key.") { ++ @Override ++ protected void process(Options options, String arg) { ++ options.sslKey = arg; ++ } ++ }, new Option(of("--ssl-cert"), "file-path", ++ "The file path to save server SSL certificate.") { ++ @Override ++ protected void process(Options options, String arg) { ++ options.sslCert = arg; ++ } + }}; + + private int serverPort = UNSET_PORT; +@@ -100,6 +112,8 @@ public final class Options { + private int cleanupTimeout = CLEANUP_TIMEOUT; + private String cachePath = null; // set on C++ side + private boolean interactive = true; ++ private String sslKey = null; ++ private String sslCert = null; + + public int getServerPort() { + return serverPort; +@@ -121,6 +135,14 @@ public final class Options { + return interactive; + } + ++ public String getSSLKey() { ++ return sslKey; ++ } ++ ++ public String getSSLCert() { ++ return sslCert; ++ } ++ + /** + * Parse the args of main(). + * +diff --git a/src/jdk.jbooster/share/native/libjbooster/JBooster.c b/src/jdk.jbooster/share/native/libjbooster/JBooster.c +index 0c25710ae..1c489b57b 100644 +--- a/src/jdk.jbooster/share/native/libjbooster/JBooster.c ++++ b/src/jdk.jbooster/share/native/libjbooster/JBooster.c +@@ -27,15 +27,15 @@ + #include "jdk_jbooster_JBooster.h" + + JNIEXPORT void JNICALL +-Java_jdk_jbooster_JBooster_initInVM(JNIEnv * env, jclass unused, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path) ++Java_jdk_jbooster_JBooster_initInVM(JNIEnv * env, jclass unused, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path, jstring ssl_key, jstring ssl_cert) + { +- JVM_JBoosterInitVM(env, server_port, connection_timeout, cleanup_timeout, cache_path); ++ JVM_JBoosterInitVM(env, server_port, connection_timeout, cleanup_timeout, cache_path, ssl_key, ssl_cert); + } + + JNIEXPORT void JNICALL +-Java_jdk_jbooster_JBooster_handleConnection(JNIEnv * env, jclass unused, jint connection_fd) ++Java_jdk_jbooster_JBooster_handleConnection(JNIEnv * env, jclass unused, jint connection_fd, jlong connection_ssl) + { +- JVM_JBoosterHandleConnection(env, connection_fd); ++ JVM_JBoosterHandleConnection(env, connection_fd, connection_ssl); + } + + JNIEXPORT void JNICALL +diff --git a/test/jdk/tools/jbooster/JBoosterSSLTest.java b/test/jdk/tools/jbooster/JBoosterSSLTest.java +new file mode 100644 +index 000000000..ba6bec8d5 +--- /dev/null ++++ b/test/jdk/tools/jbooster/JBoosterSSLTest.java +@@ -0,0 +1,152 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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. ++ */ ++ ++ import java.io.File; ++ import java.util.List; ++ import java.util.ArrayList; ++ import java.util.concurrent.TimeUnit; ++ import java.util.regex.Pattern; ++ ++import static jdk.test.lib.Asserts.*; ++ ++/* ++* jbooster testing. ++* @test ++* @summary Test jbooster server ++* @library ../lib ++* /test/lib ++* @modules jdk.jbooster ++* @build SimpleClient ++* @run main/othervm/timeout=300 JBoosterSSLTest ++*/ ++public class JBoosterSSLTest extends JBoosterTestBase { ++ private static void testSSLCorrectArgs(TestContext ctx) throws Exception { ++ ArrayList serverArgs = new ArrayList<>(SERVER_STANDARD_ARGS); ++ ++ final String sep = File.separator; ++ String testSrc = System.getProperty("test.src", "") + sep; ++ ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-cert=%sserver-cert.pem", testSrc)); ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-key=%sserver-key.pem", testSrc)); ++ Process server = jbooster(ctx, List.of(), serverArgs); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS); ++ clientArgs.add(4, String.format("-XX:JBoosterServerSSLRootCerts=%sserver-cert.pem", testSrc)); ++ Process client = java(ctx, clientArgs); ++ client.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertEquals(client.exitValue(), 0, "good"); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testSSLNoArgs(TestContext ctx) throws Exception { ++ ArrayList serverArgs = new ArrayList<>(SERVER_STANDARD_ARGS); ++ Process server = jbooster(ctx, List.of(), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS); ++ Process client = java(ctx, CLIENT_STANDARD_ARGS); ++ client.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertEquals(client.exitValue(), 0, "good"); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testSSLDifferentCert(TestContext ctx) throws Exception { ++ ArrayList serverArgs = new ArrayList<>(SERVER_STANDARD_ARGS); ++ ++ final String sep = File.separator; ++ String testSrc = System.getProperty("test.src", "") + sep; ++ ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-cert=%sserver-cert.pem", testSrc)); ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-key=%sserver-key.pem", testSrc)); ++ Process server = jbooster(ctx, List.of(), serverArgs); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS); ++ clientArgs.add(4, String.format("-XX:JBoosterServerSSLRootCerts=%sunrelated-cert.pem", testSrc)); ++ Process client = java(ctx, clientArgs); ++ client.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertNotEquals(client.exitValue(), 0, "Both certs must be the same."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testSSLClientNoArg(TestContext ctx) throws Exception { ++ ArrayList serverArgs = new ArrayList<>(SERVER_STANDARD_ARGS); ++ ++ final String sep = File.separator; ++ String testSrc = System.getProperty("test.src", "") + sep; ++ ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-cert=%sserver-cert.pem", testSrc)); ++ serverArgs.add(serverArgs.size() - 1, String.format("--ssl-key=%sserver-key.pem", testSrc)); ++ Process server = jbooster(ctx, List.of(), serverArgs); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ Process client = java(ctx, CLIENT_STANDARD_ARGS); ++ client.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertNotEquals(client.exitValue(), 0, "Client choose not to use SSL."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testSSLServerNoArg(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of(), SERVER_STANDARD_ARGS); ++ ++ final String sep = File.separator; ++ String testSrc = System.getProperty("test.src", "") + sep; ++ ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS); ++ clientArgs.add(4, String.format("-XX:JBoosterServerSSLRootCerts=%sserver-cert.pem", testSrc)); ++ Process client = java(ctx, clientArgs); ++ client.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertNotEquals(client.exitValue(), 0, "Server choose not to use SSL."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testSSLServerOneArg(TestContext ctx) throws Exception { ++ ArrayList serverArgs1 = new ArrayList<>(SERVER_STANDARD_ARGS); ++ ++ final String sep = File.separator; ++ String testSrc = System.getProperty("test.src", "") + sep; ++ ++ serverArgs1.add(serverArgs1.size() - 1, String.format("--ssl-cert=%sserver-cert.pem", testSrc)); ++ Process server1 = jbooster(ctx, List.of(), serverArgs1); ++ server1.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ assertEquals(server1.exitValue(), 1, "Missing Arg --ssl-key."); ++ ++ ArrayList serverArgs2 = new ArrayList<>(SERVER_STANDARD_ARGS); ++ serverArgs2.add(serverArgs2.size() - 1, String.format("--ssl-key=%sserver-key.pem", testSrc)); ++ Process server2 = jbooster(ctx, List.of(), serverArgs2); ++ server2.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ assertEquals(server2.exitValue(), 1, "Missing Arg --ssl-cert."); ++ } ++ ++ public static void main(String[] args) throws Exception { ++ testCases(JBoosterSSLTest.class); ++ } ++} +\ No newline at end of file +diff --git a/test/jdk/tools/jbooster/JBoosterSharedCacheTest.java b/test/jdk/tools/jbooster/JBoosterSharedCacheTest.java +new file mode 100644 +index 000000000..874bce0ea +--- /dev/null ++++ b/test/jdk/tools/jbooster/JBoosterSharedCacheTest.java +@@ -0,0 +1,150 @@ ++/* ++ * Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. 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. ++ */ ++ ++ import java.io.File; ++ import java.util.List; ++ import java.util.ArrayList; ++ import java.util.concurrent.TimeUnit; ++ import java.util.regex.Pattern; ++ ++ import static jdk.test.lib.Asserts.*; ++ ++ /* ++ * jbooster testing. ++ * @test ++ * @summary Test jbooster server ++ * @library ../lib ++ * /test/lib ++ * @modules jdk.jbooster ++ * @build SimpleClient ++ * @run main/othervm/timeout=300 JBoosterSharedCacheTest ++ */ ++ public class JBoosterSharedCacheTest extends JBoosterTestBase { ++ ++ public static boolean checkFileNumAndCacheType(String cacheType, int num) throws Exception { ++ File dir = new File(SERVER_CACHE_PATH); ++ File[] fileList = dir.listFiles(); ++ if (fileList.length != num) { ++ return false; ++ } ++ ++ if (fileList != null){ ++ for (File subFile : fileList) { ++ if (subFile.isFile() && subFile.getName().endsWith(cacheType)){ ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ ++ public static boolean checkCacheFilesSameProgram() throws Exception { ++ File dir = new File(SERVER_CACHE_PATH); ++ File[] fileList = dir.listFiles(); ++ String hash = null; ++ for (File subFile : fileList) { ++ String file_hash = subFile.getName().split("-")[2]; ++ if (hash == null) { ++ hash = file_hash; ++ } else { ++ if (!hash.equals(file_hash)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } ++ ++ private static void testCacheAtLevel1(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of("-Xlog:jbooster*=trace"), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS_WITHOUT_LEVEL); ++ clientArgs.add(4, "-XX:BoostStopAtLevel=1"); ++ Process p = java(ctx, clientArgs); ++ p.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertEquals(checkFileNumAndCacheType("clr.log", 1), true, "clr not generated."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testCacheAtLevel2(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of("-Xlog:jbooster*=trace"), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS_WITHOUT_LEVEL); ++ clientArgs.add(4, "-XX:BoostStopAtLevel=2"); ++ Process p = java(ctx, clientArgs); ++ p.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertEquals(checkFileNumAndCacheType("cds-agg.jsa", 2), true, "cds-agg not generated."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testCacheAtLevel3(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of("-Xlog:jbooster*=trace"), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS_WITHOUT_LEVEL); ++ clientArgs.add(4, "-XX:BoostStopAtLevel=3"); ++ Process p = java(ctx, clientArgs); ++ p.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ Thread.currentThread().sleep(5000); ++ assertEquals(checkFileNumAndCacheType("aot-static.so", 3), true, "aot-static not generated."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testCacheAtLevel4(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of("-Xlog:jbooster*=trace"), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS_WITHOUT_LEVEL); ++ clientArgs.add(4, "-XX:BoostStopAtLevel=4"); ++ Process p = java(ctx, clientArgs); ++ p.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ Thread.currentThread().sleep(5000); ++ assertEquals(checkFileNumAndCacheType("aot-pgo.so", 4), true, "aot-pgo not generated."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ private static void testCacheWithDyCDS(TestContext ctx) throws Exception { ++ Process server = jbooster(ctx, List.of("-Xlog:jbooster*=trace"), SERVER_STANDARD_ARGS); ++ server.waitFor(WAIT_START_TIME, TimeUnit.SECONDS); ++ ++ ArrayList clientArgs = new ArrayList<>(CLIENT_STANDARD_ARGS_WITHOUT_LEVEL); ++ clientArgs.add(4, "-XX:BoostStopAtLevel=4"); ++ clientArgs.add(5, "-XX:-UseAggressiveCDS"); ++ Process p = java(ctx, clientArgs); ++ p.waitFor(WAIT_EXIT_TIME, TimeUnit.SECONDS); ++ ++ assertEquals(checkFileNumAndCacheType("cds-dy.jsa", 5), true, "cds-dy not generated."); ++ assertEquals(checkCacheFilesSameProgram(), true, "hash not same."); ++ exitNormallyByCommandQ(server); ++ } ++ ++ public static void main(String[] args) throws Exception { ++ testCases(JBoosterSharedCacheTest.class); ++ } ++ } +diff --git a/test/jdk/tools/jbooster/JBoosterTestBase.java b/test/jdk/tools/jbooster/JBoosterTestBase.java +index 8f925d7f3..08792a77b 100644 +--- a/test/jdk/tools/jbooster/JBoosterTestBase.java ++++ b/test/jdk/tools/jbooster/JBoosterTestBase.java +@@ -67,6 +67,9 @@ public class JBoosterTestBase { + "SimpleClient" + ); + ++ public static final List CLIENT_STANDARD_ARGS_WITHOUT_LEVEL = CLIENT_STANDARD_ARGS.stream() ++ .filter(s -> !"-XX:BoostStopAtLevel=4".equals(s)).toList(); ++ + public static final List CLIENT_OFFLINE_ARGS = List.of( + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseJBooster", +@@ -84,7 +87,7 @@ public class JBoosterTestBase { + + public static Process jbooster(TestContext ctx, List vmArgs, List jboosterArgs) throws Exception { + JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jbooster"); +- launcher.addVMArgs(Utils.getTestJavaOpts()); ++ // launcher.addVMArgs(Utils.getTestJavaOpts()); + if (vmArgs != null) { + for (String vmArg : vmArgs) { + launcher.addVMArg(vmArg); +@@ -151,6 +154,7 @@ public class JBoosterTestBase { + } finally { + for (Process p : ctx.getProcesses()) { + if (p.isAlive()) { ++ p.waitFor(WAIT_SHORT_TIME, TimeUnit.SECONDS); + p.destroyForcibly(); + } + } +diff --git a/test/jdk/tools/jbooster/server-cert.pem b/test/jdk/tools/jbooster/server-cert.pem +new file mode 100644 +index 000000000..923cf333a +--- /dev/null ++++ b/test/jdk/tools/jbooster/server-cert.pem +@@ -0,0 +1,22 @@ ++-----BEGIN CERTIFICATE----- ++MIIDjTCCAnWgAwIBAgIUUo4hM/1j2s4ZT0VPHU/1rOzu5DIwDQYJKoZIhvcNAQEL ++BQAwVjELMAkGA1UEBhMCY24xETAPBgNVBAgMCHNoYW5naGFpMREwDwYDVQQHDAhz ++aGFuZ2hhaTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI0 ++MDYyNjEwMDcxNVoXDTI1MDYyNjEwMDcxNVowVjELMAkGA1UEBhMCY24xETAPBgNV ++BAgMCHNoYW5naGFpMREwDwYDVQQHDAhzaGFuZ2hhaTEhMB8GA1UECgwYSW50ZXJu ++ZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC ++AQEAoZ3+SQavSX6hZXLJtDlXe1TrNROWxySmnL3FBuEEH7SlwOoVxRW1P+ZivPp4 ++vjnWDXaafb5yVc0w7A+6Clt/OFrgvhw92quxHnGGvy3IxNMdgL2lNZ6gjeUXs2IA ++59FdboSgDbJvJAcemq7v2UhbAAmq47+pQ+ZtVnF2LCN424PObGlnxJDDVb+vWTF9 ++DlUvvkv3XW8anP5iPnZ0Ou91Lu4WTSvD2TZCDUVWrP52jj9H08WlTA8SUOxluixu ++f4SJ0WO767VABBkYAZ+/4fERZL4PSf6l1XLjJBFEb0rn1dECEe559OHPHjBZly0i ++t39Fc8kCfTxKVjdpBwhC98gCyQIDAQABo1MwUTAdBgNVHQ4EFgQU4fQgXJlXPnbA ++TlDDncohnRoP7mMwHwYDVR0jBBgwFoAU4fQgXJlXPnbATlDDncohnRoP7mMwDwYD ++VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALbIy4RBXvkej98ZoxshC ++VbrVQdjTY9DkZIOIOHn2jwwyiezSI+Aff+mg0XgWEB+pC7i1CnqOk0EW2pUsTmvd ++fY2fUCrGr7mH58mq5ADwOURByPtpstFtjWNcUuiqoKGwP+hP64mf+gY8r7Gc9k9V ++zCS0e1r4i87kW2pvqdY9aJ5xNOb6PfWc31c5f8306MfnFiy/U5m2lGCs0KVkwjtm ++3Xm5hI3OYYdZ5c4mHGtVmFnzLz+moxywWu6vXYTcuDe6v2JRJ2ZB6znwNOWO56RR ++WhEssi4Szig1206d5idUgAB8OwttUtEMtT+labAvUKeeI0eiYlpsgyMbrW0VE4qQ ++bg== ++-----END CERTIFICATE----- +diff --git a/test/jdk/tools/jbooster/server-key.pem b/test/jdk/tools/jbooster/server-key.pem +new file mode 100644 +index 000000000..77bf13bb3 +--- /dev/null ++++ b/test/jdk/tools/jbooster/server-key.pem +@@ -0,0 +1,27 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpAIBAAKCAQEAoZ3+SQavSX6hZXLJtDlXe1TrNROWxySmnL3FBuEEH7SlwOoV ++xRW1P+ZivPp4vjnWDXaafb5yVc0w7A+6Clt/OFrgvhw92quxHnGGvy3IxNMdgL2l ++NZ6gjeUXs2IA59FdboSgDbJvJAcemq7v2UhbAAmq47+pQ+ZtVnF2LCN424PObGln ++xJDDVb+vWTF9DlUvvkv3XW8anP5iPnZ0Ou91Lu4WTSvD2TZCDUVWrP52jj9H08Wl ++TA8SUOxluixuf4SJ0WO767VABBkYAZ+/4fERZL4PSf6l1XLjJBFEb0rn1dECEe55 ++9OHPHjBZly0it39Fc8kCfTxKVjdpBwhC98gCyQIDAQABAoIBAEOMJB5jY9mkylH7 ++QuBHYw/R8yhQ0qDS6avzCKkSXMXfm7NgYs7nfsSBRt1TFinzREqGOpW5hlHkG8Fd ++5VS03xwvp2TtqtB9F97wde/rc9EHp3oKSUV60gHcMSaxHE3HTHSHi9mNrTPyodDm ++sqouupHueHUNwvH1GdeaPli+TqH373vknnrPJENsz2dWrLlkeY3EIAud1NnFcuWV ++FLhvYybAtb9xrq11TcXZwir9/2/mxppwDT1NyQ50YVgmV8obtXhvAbV4gXad1ivx ++cTUq/Dc7qHKjttfC/wcTNS7HQmzekl2096pWNAZhOgjxdooxOf81nxk+gHF1IjtP ++LVKyZG0CgYEA13rdC+6EV8RO4+djgJ5NObOZOx9Pvq3Q5R7b8ZlccBTfK7D2lM2d ++jDcQK9up/WUH8t1a7BDMz8/NnSsYHL4SACe5NtM9XtUSMvzHXXjiCCa8NUs9Ii1C ++93eMHAKekkOALnw8cS3VkjJ99gxbF1H3qWqZ9aDTHbIhi9WbJcdbtPcCgYEAwAIx ++Whf/llcQX0ooWAw9lJP6K2rYS8wW6V6/Cp830v7v5FZlvsJvZfHNRoM7a+/pueh+ ++jx+yZC+D2rtwZebWLkspsQI0KuB4SK3HvzPUqBPncT2pyg6Vv6vwQLvjC21tMvzM ++hIup52Xp9QNmUQKE2cNVl0AegG3wFwEOYCyQ1j8CgYAEVV44rTVQJoyHnNGtdoGL ++FYeRXtHVvd0jnnlB5JmtT14lnMt4tqHifgD/QM2sYOnGApmfgb78tuslrAYyHchy ++5FmfNqRXUeWpzNedk57IFy9VywuRsmNUYr0YmxzhSCY9yRXWGNvgss9BsYHreCSI ++7bHm9LMKN9jg3Qzft6CqhQKBgQCagek2x3L7hqn9FA6s84SmSAGa6IqPS/bv6jcv ++E/p3BoECLGgJfiroTRDTegzaCZ/54tXc1DPYHMgsvoJu7bdSX1d4Qf66thD04yqG ++eO3aJtIqNOWpW90y9OgLH2ZPrsmdqwHkcwLQ2xjN+eNesvk2xtHcOVOSI2V9DXOE ++/re6PwKBgQC0x6sw/egYWibdM815zRZ1onvQNm1XiyTc4XBAwD3wMGcFsRzjmEJs ++/eRNGCP3uTaixF+JueLI3bap5fouk9m2wLezrkSkUZQT6qQMQe6APBPRWqHNdWRr ++WodIiUX8toXwajlJAOZwvzUaqDrW3pIfiv9yQIHfyAaHzpnX4m9/uQ== ++-----END RSA PRIVATE KEY----- +diff --git a/test/jdk/tools/jbooster/unrelated-cert.pem b/test/jdk/tools/jbooster/unrelated-cert.pem +new file mode 100644 +index 000000000..a865fe54c +--- /dev/null ++++ b/test/jdk/tools/jbooster/unrelated-cert.pem +@@ -0,0 +1,22 @@ ++-----BEGIN CERTIFICATE----- ++MIIDjTCCAnWgAwIBAgIUUZE/fNRyso3SrZMwXumPUplx03IwDQYJKoZIhvcNAQEL ++BQAwVjELMAkGA1UEBhMCY24xETAPBgNVBAgMCHNoYW5naGFpMREwDwYDVQQHDAhz ++aGFuZ2hhaTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI0 ++MDYyNjEwMDc0MloXDTI1MDYyNjEwMDc0MlowVjELMAkGA1UEBhMCY24xETAPBgNV ++BAgMCHNoYW5naGFpMREwDwYDVQQHDAhzaGFuZ2hhaTEhMB8GA1UECgwYSW50ZXJu ++ZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC ++AQEAsAT1DkzNXE1kPiO3RZK4HnUROwa0dAaylFb5B5CxsuTSkR+j5Jl9syyoRnRf ++tu+GZ0MsA4OG5BbSxEZjiHtgEJO7FgvvxSCade0cD/OqQqMY9bnX+nhbW++JCRNc ++WcHlZODSxuQjYNdQOi/+Y4vKJhvlbHFujRpKqb6zbfehIjRTXNCCZInno2gcAV/f ++JWVT6umhTXHqH82UnsaLGYELOLrpuXAVGxu9ThCa8GInM9/rMrlk8ejSU6bx/bh9 ++FajFfGIcdOa8uDFQGLiPaRg6IAncSRLruurJIWJ8CrXQSMnUUCjlHy07vNrJnnXr ++ONZdJLMDY12x0Ss2LIGuHE+rDQIDAQABo1MwUTAdBgNVHQ4EFgQUV7nqiMC46+Wp ++z+Sa+6522O+jGQ0wHwYDVR0jBBgwFoAUV7nqiMC46+Wpz+Sa+6522O+jGQ0wDwYD ++VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb6nhPg0WBQyDKXsX0tAS ++Al11vKHrzLd3/KqAzrb78YZKmQIHb6jjwYz/S5CBuHsxq37MIGSmZlr0VMgnOTTq ++WO2KpEZBQAIAEV2JOMkSzkjhQA3Ftwon8phuEghiODW2dFLtFv+NdS4crJ4A9jFa ++XnFhQlWqA0WUr98ZZWjpCXxE+SFjY+FGvR19GCDF1iOQPSmV2fr57gxGD9R7M38L ++jgR/2TzNxR8+vVeC0V4R0wOZJsacxdJBn3XgvsJIhm3npnlWgtL1ND7cH9VVB/H7 ++P2THAgyviUEbHL4LuZbIIONJPQqMwnNUAprDgcVWQG/Fgvt8mPUO/ksCs1uOGFEX ++9Q== ++-----END CERTIFICATE----- +-- +2.22.0 + diff --git a/Implement-JBooster-RPC-byte-alignment.patch b/Implement-JBooster-RPC-byte-alignment.patch new file mode 100644 index 0000000..259c19e --- /dev/null +++ b/Implement-JBooster-RPC-byte-alignment.patch @@ -0,0 +1,699 @@ +From bcc1964f3ac7ffe6995f6734bc14cbbe07a09363 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:38:42 +0800 +Subject: Implement JBooster RPC byte alignment + +--- + .../share/jbooster/jClientArguments.cpp | 21 +++ + .../share/jbooster/jClientArguments.hpp | 5 + + .../share/jbooster/net/messageBuffer.cpp | 34 ++++- + .../share/jbooster/net/messageBuffer.hpp | 19 ++- + .../jbooster/net/messageBuffer.inline.hpp | 44 +++++- + .../share/jbooster/net/rpcCompatibility.cpp | 43 ++++-- + .../share/jbooster/net/serialization.hpp | 13 +- + .../jbooster/server/serverDataManager.cpp | 22 +-- + .../jbooster/server/serverDataManager.hpp | 4 +- + .../jbooster/server/serverMessageHandler.cpp | 6 +- + test/hotspot/gtest/jbooster/test_net.cpp | 129 ++++++++++++++---- + 11 files changed, 272 insertions(+), 68 deletions(-) + +diff --git a/src/hotspot/share/jbooster/jClientArguments.cpp b/src/hotspot/share/jbooster/jClientArguments.cpp +index 37093d031..b215913ea 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.cpp ++++ b/src/hotspot/share/jbooster/jClientArguments.cpp +@@ -34,6 +34,27 @@ + #include "runtime/arguments.hpp" + #include "utilities/stringUtils.hpp" + ++int JClientBoostLevel::serialize(MessageBuffer& buf) const { ++ uint8_t v = (_allow_clr ? (1 << 0) : 0) ++ | (_allow_cds ? (1 << 1) : 0) ++ | (_allow_aot ? (1 << 2) : 0) ++ | (_enable_aot_pgo ? (1 << 3) : 0) ++ | (_enable_cds_agg ? (1 << 4) : 0); ++ return buf.serialize_no_meta(v); ++} ++ ++int JClientBoostLevel::deserialize(MessageBuffer& buf) { ++ uint8_t v = 0b10000000; ++ JB_RETURN(buf.deserialize_ref_no_meta(v)); ++ assert((v & 0b10000000) == 0, "sanity"); ++ _allow_clr = (v & (1 << 0)); ++ _allow_cds = (v & (1 << 1)); ++ _allow_aot = (v & (1 << 2)); ++ _enable_aot_pgo = (v & (1 << 3)); ++ _enable_cds_agg = (v & (1 << 4)); ++ return 0; ++} ++ + static JClientArguments::CpuArch calc_cpu() { + #ifdef X86 + return JClientArguments::CpuArch::CPU_X86; +diff --git a/src/hotspot/share/jbooster/jClientArguments.hpp b/src/hotspot/share/jbooster/jClientArguments.hpp +index be057a07d..09c5dfdc0 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.hpp ++++ b/src/hotspot/share/jbooster/jClientArguments.hpp +@@ -49,8 +49,13 @@ public: + void set_allow_aot(bool allow_aot) { _allow_aot = allow_aot; } + void set_enable_aot_pgo(bool enable_aot_pgo) { _enable_aot_pgo = enable_aot_pgo; } + void set_enable_cds_agg(bool enable_cds_agg) { _enable_cds_agg = enable_cds_agg; } ++ ++ int serialize(MessageBuffer& buf) const; ++ int deserialize(MessageBuffer& buf); + }; + ++DECLARE_SERIALIZER_INTRUSIVE(JClientBoostLevel); ++ + /** + * Arguments that identify a program. + */ +diff --git a/src/hotspot/share/jbooster/net/messageBuffer.cpp b/src/hotspot/share/jbooster/net/messageBuffer.cpp +index 4673bb784..980cfbfed 100644 +--- a/src/hotspot/share/jbooster/net/messageBuffer.cpp ++++ b/src/hotspot/share/jbooster/net/messageBuffer.cpp +@@ -23,15 +23,34 @@ + + #include "jbooster/net/messageBuffer.inline.hpp" + ++/** ++ * Allocate an extra eight bytes in case the array object is not 8-byte aligned. ++ */ ++char* MessageBuffer::alloc_buf_obj(uint32_t new_buf_size) { ++ return NEW_C_HEAP_ARRAY(char, new_buf_size + sizeof(int64_t), mtJBooster); ++} ++ ++void MessageBuffer::del_buf_obj(char* buf_obj) { ++ FREE_C_HEAP_ARRAY(char, buf_obj); ++} ++ ++/** ++ * Make the beginning of buf be 8-byte aligned. ++ */ ++char* MessageBuffer::calc_buf_start(char* buf_obj) { ++ return buf_obj + calc_padding((uint32_t) reinterpret_cast(buf_obj)); ++} ++ + MessageBuffer::MessageBuffer(SerializationMode smode, CommunicationStream* stream): + _smode(smode), + _buf_size(_default_buf_size), +- _buf(NEW_C_HEAP_ARRAY(char, _buf_size, mtJBooster)), ++ _buf_obj(alloc_buf_obj(_buf_size)), ++ _buf(calc_buf_start(_buf_obj)), + _cur_offset(0), + _stream(stream) {} + + MessageBuffer::~MessageBuffer() { +- FREE_C_HEAP_ARRAY(char, _buf); ++ del_buf_obj(_buf_obj); + } + + /** +@@ -50,12 +69,19 @@ uint32_t MessageBuffer::calc_new_buf_size(uint32_t required_size) { + } + + void MessageBuffer::expand_buf(uint32_t required_size, uint32_t copy_size) { ++ char* old_buf_obj = _buf_obj; + char* old_buf = _buf; ++ + uint32_t new_buf_size = calc_new_buf_size(required_size); +- char* new_buf = NEW_C_HEAP_ARRAY(char, new_buf_size, mtJBooster); ++ char* new_buf_obj = alloc_buf_obj(new_buf_size); ++ char* new_buf = calc_buf_start(new_buf_obj); ++ guarantee((new_buf_obj + new_buf_size + sizeof(int64_t)) >= (new_buf + copy_size), "sanity"); + memcpy(new_buf, old_buf, copy_size); + ++ _buf_obj = new_buf_obj; + _buf = new_buf; + _buf_size = new_buf_size; +- FREE_C_HEAP_ARRAY(char, old_buf); ++ del_buf_obj(old_buf_obj); ++ ++ assert(((int) (reinterpret_cast(_buf)) & 7) == 0, "8-byte aligned"); + } +diff --git a/src/hotspot/share/jbooster/net/messageBuffer.hpp b/src/hotspot/share/jbooster/net/messageBuffer.hpp +index aaa8e7c3b..04e41e9d6 100644 +--- a/src/hotspot/share/jbooster/net/messageBuffer.hpp ++++ b/src/hotspot/share/jbooster/net/messageBuffer.hpp +@@ -56,7 +56,7 @@ public: + void assert_can_deserialize() const NOT_DEBUG_RETURN; + }; + +-class MessageBuffer final: public StackObj { ++class MessageBuffer: public StackObj { + friend class Message; + + private: +@@ -64,14 +64,23 @@ private: + + SerializationMode _smode; + uint32_t _buf_size; +- char* _buf; ++ char* _buf_obj; // malloced buf object ++ char* _buf; // 8-byte aligned buf start + uint32_t _cur_offset; + CommunicationStream* const _stream; + + private: +- static uint32_t calc_new_buf_size(uint32_t required_size); ++ static char* alloc_buf_obj(uint32_t new_buf_size); ++ static void del_buf_obj(char* buf_obj); ++ + void expand_buf(uint32_t required_size, uint32_t copy_size); + ++protected: // for gtest ++ template ++ static uint32_t calc_padding(uint32_t offset); ++ static char* calc_buf_start(char* buf_obj); ++ static uint32_t calc_new_buf_size(uint32_t required_size); ++ + public: + MessageBuffer(SerializationMode smode, CommunicationStream* stream = nullptr); + ~MessageBuffer(); +@@ -95,6 +104,8 @@ public: + } + + // serializers ++ template ++ int serialize_base(Arg v); + int serialize_memcpy(const void* from, uint32_t arg_size); + template + int serialize_no_meta(const Arg& arg); +@@ -102,6 +113,8 @@ public: + int serialize_with_meta(const Arg* arg_ptr); + + // deserializers ++ template ++ int deserialize_base(Arg& to); + int deserialize_memcpy(void* to, uint32_t arg_size); + template + int deserialize_ref_no_meta(Arg& arg); +diff --git a/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp b/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp +index c7dc0986f..9500c1aaa 100644 +--- a/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp ++++ b/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp +@@ -38,6 +38,26 @@ inline void SerializationMode::assert_can_deserialize() const { + } + #endif + ++template ++inline uint32_t MessageBuffer::calc_padding(uint32_t offset) { ++ static_assert((sizeof(Arg) & (sizeof(Arg) - 1)) == 0, "Should be 1, 2, 4, 8!"); ++ static_assert(sizeof(Arg) <= 8, "Should be 1, 2, 4, 8!"); ++ return (-offset) & (sizeof(Arg) - 1); ++} ++ ++template ++inline int MessageBuffer::serialize_base(Arg v) { ++ static_assert(std::is_arithmetic::value || std::is_enum::value, "Base types or enums only!"); ++ _smode.assert_can_serialize(); ++ uint32_t arg_size = (uint32_t) sizeof(Arg); ++ uint32_t padding = calc_padding(_cur_offset); ++ uint32_t nxt_offset = _cur_offset + padding + arg_size; ++ expand_if_needed(nxt_offset, _cur_offset); ++ *((Arg*) (_buf + _cur_offset + padding)) = v; ++ _cur_offset = nxt_offset; ++ return 0; ++} ++ + inline int MessageBuffer::serialize_memcpy(const void* from, uint32_t arg_size) { + _smode.assert_can_serialize(); + assert(from != nullptr, "sanity"); +@@ -56,11 +76,13 @@ inline int MessageBuffer::serialize_no_meta(const Arg& arg) { + + template + inline int MessageBuffer::serialize_with_meta(const Arg* arg_ptr) { ++ static_assert(MessageConst::arg_meta_size == sizeof(uint32_t), "Meta changed?"); + _smode.assert_can_serialize(); + if (arg_ptr == nullptr) { + return serialize_no_meta(MessageConst::NULL_PTR); + } + const Arg& arg = *arg_ptr; ++ skip_cur_offset(calc_padding(_cur_offset)); + uint32_t meta_offset = _cur_offset; + skip_cur_offset(MessageConst::arg_meta_size); + expand_if_needed(_cur_offset, _cur_offset); +@@ -68,7 +90,25 @@ inline int MessageBuffer::serialize_with_meta(const Arg* arg_ptr) { + + // fill arg meta at last + uint32_t arg_size = _cur_offset - meta_offset - MessageConst::arg_meta_size; +- memcpy((void*) (_buf + meta_offset), &arg_size, sizeof(arg_size)); ++ *((uint32_t*) (_buf + meta_offset)) = arg_size; ++ return 0; ++} ++ ++template ++inline int MessageBuffer::deserialize_base(Arg& to) { ++ static_assert(std::is_arithmetic::value || std::is_enum::value, "Base types or enums only!"); ++ _smode.assert_can_deserialize(); ++ uint32_t arg_size = (uint32_t) sizeof(Arg); ++ uint32_t padding = calc_padding(_cur_offset); ++ uint32_t nxt_offset = _cur_offset + padding + arg_size; ++ if (_buf_size < nxt_offset) { ++ log_warning(jbooster, rpc)("The size to parse is longer than the msg size: " ++ "arg_size=%u, cur_offset=%u, nxt_offset=%u, buf_size=%u", ++ arg_size, _cur_offset, nxt_offset, _buf_size); ++ return JBErr::BAD_MSG_DATA; ++ } ++ to = *((Arg*) (_buf + _cur_offset + padding)); ++ _cur_offset = nxt_offset; + return 0; + } + +@@ -125,7 +165,7 @@ inline int MessageBuffer::deserialize_with_meta(Arg* const& arg_ptr) { + return 0; + } + const char* type_name = DebugUtils::type_name(); +- log_warning(jbooster, rpc)("The arg size does match the parsed size: " ++ log_warning(jbooster, rpc)("The arg size does not match the parsed size: " + "arg=%s, arg_size=%u, (cur_size - arg_begin)=%u.", + type_name, arg_size, _cur_offset - arg_begin); + FREE_C_HEAP_ARRAY(char, type_name); +diff --git a/src/hotspot/share/jbooster/net/rpcCompatibility.cpp b/src/hotspot/share/jbooster/net/rpcCompatibility.cpp +index 7f849a6fe..a48d1c2d8 100644 +--- a/src/hotspot/share/jbooster/net/rpcCompatibility.cpp ++++ b/src/hotspot/share/jbooster/net/rpcCompatibility.cpp +@@ -21,6 +21,7 @@ + * questions. + */ + ++#include "classfile/vmSymbols.hpp" + #include "jbooster/dataTransmissionUtils.hpp" + #include "jbooster/jBoosterManager.hpp" + #include "jbooster/jClientArguments.hpp" +@@ -33,9 +34,18 @@ + #include "jbooster/net/rpcCompatibility.hpp" + #include "jbooster/net/serializationWrappers.hpp" + #include "jbooster/net/serverStream.hpp" ++#include "oops/instanceKlass.hpp" ++#include "oops/method.hpp" ++#include "oops/methodData.hpp" + +-static constexpr uint32_t calc_new_hash(uint32_t old_hash, uint32_t ele_hash) { +- return 31 * old_hash + ele_hash; ++static constexpr uint32_t calc_new_hash(uint32_t old_hash) { ++ return old_hash; ++} ++ ++template ++static constexpr uint32_t calc_new_hash(uint32_t old_hash, uint32_t ele_hash, Rest... rest_hashes) { ++ uint32_t new_hash = old_hash * 31u + ele_hash; ++ return calc_new_hash(new_hash, rest_hashes...); + } + + template +@@ -54,20 +64,35 @@ static constexpr uint32_t calc_classes_hash() { + return calc_new_hash(calc_classes_hash(), calc_class_hash()); + } + +-/** +- * Returns a magic number computed at compile time based on the sizes of some classes. +- * It is just an crude way to check compatibility for now. More policies can be added later. +- */ +-static constexpr uint32_t calc_magic_num() { ++static constexpr uint32_t classes_hash() { + return calc_classes_hash< + JBoosterManager, JClientVMFlags, JClientArguments, + JBErr, Message, MessageBuffer, + CommunicationStream, ClientStream, ServerStream, + ArrayWrapper, MemoryWrapper, StringWrapper, FileWrapper, +- ClassLoaderKey, ClassLoaderChain, ClassLoaderLocator, KlassLocator, MethodLocator, ProfileDataCollector ++ ClassLoaderKey, ClassLoaderChain, ClassLoaderLocator, KlassLocator, MethodLocator, ProfileDataCollector, ++ InstanceKlass, Method, MethodData + >(); + } + ++static constexpr uint32_t little_or_big_endian() { ++ return (uint32_t) LITTLE_ENDIAN_ONLY('L') BIG_ENDIAN_ONLY('B'); ++} ++ ++/** ++ * Returns a magic number computed at compile time based on the sizes of some classes. ++ * It is just an crude way to check compatibility for now. More policies can be added later. ++ */ ++static constexpr uint32_t calc_magic_num() { ++ return calc_new_hash( ++ classes_hash(), ++ little_or_big_endian(), ++ static_cast(vmSymbolID::SID_LIMIT) ++ ); ++} ++ ++static constexpr uint32_t _magic_num = calc_magic_num(); ++ + uint32_t RpcCompatibility::magic_num() { +- return calc_magic_num(); ++ return _magic_num; + } +diff --git a/src/hotspot/share/jbooster/net/serialization.hpp b/src/hotspot/share/jbooster/net/serialization.hpp +index f9a2996c5..ed8387eb6 100644 +--- a/src/hotspot/share/jbooster/net/serialization.hpp ++++ b/src/hotspot/share/jbooster/net/serialization.hpp +@@ -72,21 +72,20 @@ + // ------------------------------ Default Serializer ------------------------------- + // The types it support for serialization/deserialization: + // - Base types: bool, int, long long, size_t, uint64_t, and so on. +-// - POD classes without pointers. + // +-// memcpy() is good enough in most cases. Even for base types such as char and size_t, +-// memcpy() has the similar performance as assignment (`=`) according to our tests. +-// It's also a choice to use assignment here. But if a class overloads the operator `=` +-// and allocates something on the heap, it can cause a memory leak. ++// No default serializer for classes! Implement them manually. ++// ++// Use uintptr_t instead of address (aka unsigned char*) if you want to serialize a ++// pointer. + + template + struct SerializationImpl { + static int serialize(MessageBuffer& buf, const Arg& arg) { +- return buf.serialize_memcpy(&arg, sizeof(Arg)); ++ return buf.serialize_base(arg); + } + + static int deserialize_ref(MessageBuffer& buf, Arg& arg) { +- return buf.deserialize_memcpy(&arg, sizeof(Arg)); ++ return buf.deserialize_base(arg); + } + + CANNOT_DESERIALIZE_POINTER(Arg); +diff --git a/src/hotspot/share/jbooster/server/serverDataManager.cpp b/src/hotspot/share/jbooster/server/serverDataManager.cpp +index b2f36c4e5..c3d5f290e 100644 +--- a/src/hotspot/share/jbooster/server/serverDataManager.cpp ++++ b/src/hotspot/share/jbooster/server/serverDataManager.cpp +@@ -340,18 +340,18 @@ ClassLoaderData* JClientProgramData::add_class_loader_if_absent(const ClassLoade + // ------------------------------ JClientSessionData ------------------------------- + + class AddressMapKVArray: public StackObj { +- GrowableArray

* _key_array; +- GrowableArray
* _value_array; ++ GrowableArray* _key_array; ++ GrowableArray* _value_array; + public: +- AddressMapKVArray(GrowableArray
* key_array, +- GrowableArray
* value_array) : ++ AddressMapKVArray(GrowableArray* key_array, ++ GrowableArray* value_array) : + _key_array(key_array), + _value_array(value_array) {} + + bool operator () (JClientSessionData::AddressMap::KVNode* kv_node) { + assert(kv_node->key() != nullptr && kv_node->value() != nullptr, "sanity"); +- _key_array->append(kv_node->key()); +- _value_array->append(kv_node->value()); ++ _key_array->append(reinterpret_cast(kv_node->key())); ++ _value_array->append(reinterpret_cast(kv_node->value())); + return true; + } + }; +@@ -422,17 +422,17 @@ void JClientSessionData::add_klass_address(address client_klass_addr, + put_address(_k_c2s, client_klass_addr, server_cld_addr, thread); + } + +-void JClientSessionData::klass_array(GrowableArray
* key_array, +- GrowableArray
* value_array, ++void JClientSessionData::klass_array(GrowableArray* key_array, ++ GrowableArray* value_array, + Thread* thread) { + AddressMapKVArray array(key_array, value_array); + _k_c2s.for_each(array, thread); + } + +-void JClientSessionData::klass_pointer_map_to_server(GrowableArray
* klass_array, Thread* thread) { +- for (GrowableArrayIterator
iter = klass_array->begin(); ++void JClientSessionData::klass_pointer_map_to_server(GrowableArray* klass_array, Thread* thread) { ++ for (GrowableArrayIterator iter = klass_array->begin(); + iter != klass_array->end(); ++iter) { +- InstanceKlass* klass = (InstanceKlass*) (*iter); ++ InstanceKlass* klass = reinterpret_cast(*iter); + Array* methods = klass->methods(); + for (int method_index = 0; method_index < methods->length(); method_index++) { + MethodData* method_data = method_data_address((address)(methods->at(method_index)), thread); +diff --git a/src/hotspot/share/jbooster/server/serverDataManager.hpp b/src/hotspot/share/jbooster/server/serverDataManager.hpp +index 2cb7d2de0..c6ef4c99a 100644 +--- a/src/hotspot/share/jbooster/server/serverDataManager.hpp ++++ b/src/hotspot/share/jbooster/server/serverDataManager.hpp +@@ -291,9 +291,9 @@ public: + Klass* server_klass_address(address client_klass_addr, Thread* thread); + void add_klass_address(address client_klass_addr, address server_cld_addr, Thread* thread); + +- void klass_array(GrowableArray
* key_array, GrowableArray
* value_array, Thread* thread); ++ void klass_array(GrowableArray* key_array, GrowableArray* value_array, Thread* thread); + +- void klass_pointer_map_to_server(GrowableArray
* klass_array, Thread* thread); ++ void klass_pointer_map_to_server(GrowableArray* klass_array, Thread* thread); + + void add_method_data(address method, address method_data, Thread* thread); + bool remove_method_data(address method, Thread* thread); +diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +index 49c25efb1..f46b839bb 100644 +--- a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp ++++ b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +@@ -196,14 +196,14 @@ int ServerMessageHandler::request_missing_klasses(TRAPS) { + + int ServerMessageHandler::request_method_data(TRAPS) { + ResourceMark rm(THREAD); +- GrowableArray
client_klass_array; +- GrowableArray
server_klass_array; ++ GrowableArray client_klass_array; ++ GrowableArray server_klass_array; + ss().session_data()->klass_array(&client_klass_array, + &server_klass_array, + THREAD); + + { +- ArrayWrapper
aw(&client_klass_array); ++ ArrayWrapper aw(&client_klass_array); + JB_RETURN(ss().send_request(MessageType::Profilinginfo, &aw)); + InstanceKlass** klass_array_base = NULL; + if (server_klass_array.length() > 0) { +diff --git a/test/hotspot/gtest/jbooster/test_net.cpp b/test/hotspot/gtest/jbooster/test_net.cpp +index 9eb29fc3a..bf20b0aa3 100644 +--- a/test/hotspot/gtest/jbooster/test_net.cpp ++++ b/test/hotspot/gtest/jbooster/test_net.cpp +@@ -26,10 +26,12 @@ + + #if INCLUDE_JBOOSTER + ++#include "jbooster/jClientArguments.hpp" + #include "jbooster/net/message.inline.hpp" + #include "jbooster/net/messageBuffer.inline.hpp" + #include "jbooster/net/serializationWrappers.inline.hpp" + #include "jbooster/utilities/fileUtils.hpp" ++#include "memory/resourceArea.hpp" + #include "runtime/os.inline.hpp" + #include "utilities/globalDefinitions.hpp" + #include "utilities/growableArray.hpp" +@@ -105,6 +107,47 @@ static void copy_message_buffer(MessageBuffer& to, MessageBuffer& from) { + memcpy(to.buf(), from.buf(), from.cur_offset()); + } + ++class MockMessageBuffer: public MessageBuffer { ++public: ++ static bool test() { ++ // calc_padding(0); // should not pass the compilation ++ EXPECT_EQ(calc_padding(123), 0u); ++ EXPECT_EQ(calc_padding(124), 0u); ++ EXPECT_EQ(calc_padding(123), 1u); ++ EXPECT_EQ(calc_padding(120), 0u); ++ EXPECT_EQ(calc_padding(0), 0u); ++ EXPECT_EQ(calc_padding(1), 3u); ++ EXPECT_EQ(calc_padding(2), 2u); ++ EXPECT_EQ(calc_padding(3), 1u); ++ EXPECT_EQ(calc_padding(4), 0u); ++ EXPECT_EQ(calc_padding(400), 0u); ++ EXPECT_EQ(calc_padding(1), 7u); ++ EXPECT_EQ(calc_padding(3), 5u); ++ EXPECT_EQ(calc_padding(12), 4u); ++ EXPECT_EQ(calc_padding(7), 1u); ++ ++ EXPECT_EQ(calc_buf_start(reinterpret_cast(7)), reinterpret_cast(8)); ++ EXPECT_EQ(calc_buf_start(reinterpret_cast(9)), reinterpret_cast(16)); ++ EXPECT_EQ(calc_buf_start(reinterpret_cast(12)), reinterpret_cast(16)); ++ EXPECT_EQ(calc_buf_start(reinterpret_cast(65535)), reinterpret_cast(65536)); ++ EXPECT_EQ(calc_buf_start(reinterpret_cast(65536)), reinterpret_cast(65536)); ++ ++ EXPECT_EQ(calc_new_buf_size(0), 0u); ++ EXPECT_EQ(calc_new_buf_size(1), 1u); ++ EXPECT_EQ(calc_new_buf_size(2), 2u); ++ EXPECT_EQ(calc_new_buf_size(3), 4u); ++ EXPECT_EQ(calc_new_buf_size(65535), 65536u); ++ EXPECT_EQ(calc_new_buf_size(65536), 65536u); ++ EXPECT_EQ(calc_new_buf_size(65537), 131072u); ++ ++ return true; ++ } ++}; ++ ++TEST(JBoosterNet, padding) { ++ ASSERT_TRUE(MockMessageBuffer::test()); ++} ++ + TEST(JBoosterNet, try_catch) { + int i; + for (i = 0; i < 9; ++i) { +@@ -145,24 +188,25 @@ TEST(JBoosterNet, serializationn_basics) { + int i1 = 1234; + int64_t l1 = 900000000000000ll; + EXPECT_EQ(buf.serialize_no_meta(c1), 0); +- EXPECT_EQ(buf.cur_offset(), 1u); ++ EXPECT_EQ(buf.cur_offset(), 1u); // 1 (char) + EXPECT_EQ(buf.serialize_no_meta(i1), 0); +- EXPECT_EQ(buf.cur_offset(), 5u); ++ EXPECT_EQ(buf.cur_offset(), 8u); // 1 (last) + 3 (padding) + 4 (int32) + EXPECT_EQ(buf.serialize_no_meta(l1), 0); +- EXPECT_EQ(buf.cur_offset(), 13u); ++ EXPECT_EQ(buf.cur_offset(), 16u); // 8 (last) + 8 (int64) + + uint32_t u1 = 2468; + const char* s1 = nullptr; + const char* s2 = "hello"; + const char* s3 = "world!"; + EXPECT_EQ(buf.serialize_with_meta(&u1), 0); +- EXPECT_EQ(buf.cur_offset(), 21u); ++ EXPECT_EQ(buf.cur_offset(), 24u); // 16 (last) + 4 (int32) + 4 (int32) + EXPECT_EQ(buf.serialize_with_meta(&s1), 0); +- EXPECT_EQ(buf.cur_offset(), 29u); ++ EXPECT_EQ(buf.cur_offset(), 32u); // 24 (last) + 4 (int32) + 4 (int32) + EXPECT_EQ(buf.serialize_with_meta(&s2), 0); +- EXPECT_EQ(buf.cur_offset(), 42u); ++ EXPECT_EQ(buf.cur_offset(), 45u); // 32 (last) + 4 (int32) + 4 (int32) + 5 (strlen) + EXPECT_EQ(buf.serialize_with_meta(&s3), 0); +- EXPECT_EQ(buf.cur_offset(), 56u); ++ EXPECT_EQ(buf.cur_offset(), 62u); // 45 (last) + 3 (padding) + 4 (int32) + 4 (int32) + 6 (strlen) ++ EXPECT_EQ((int) (reinterpret_cast(buf.buf())) & 7, 0); // 8-byte aligned + + cache_size = buf.cur_offset(); + memcpy(cache, buf.buf(), cache_size); +@@ -177,9 +221,9 @@ TEST(JBoosterNet, serializationn_basics) { + EXPECT_EQ(buf.deserialize_ref_no_meta(c1), 0); + EXPECT_EQ(buf.cur_offset(), 1u); + EXPECT_EQ(buf.deserialize_ref_no_meta(i1), 0); +- EXPECT_EQ(buf.cur_offset(), 5u); ++ EXPECT_EQ(buf.cur_offset(), 8u); + EXPECT_EQ(buf.deserialize_ref_no_meta(l1), 0); +- EXPECT_EQ(buf.cur_offset(), 13u); ++ EXPECT_EQ(buf.cur_offset(), 16u); + EXPECT_EQ(c1, '6'); + EXPECT_EQ(i1, 1234); + EXPECT_EQ(l1, 900000000000000ll); +@@ -189,13 +233,15 @@ TEST(JBoosterNet, serializationn_basics) { + char s2[6]; + StringWrapper s3; + EXPECT_EQ(buf.deserialize_with_meta(&u1), 0); +- EXPECT_EQ(buf.cur_offset(), 21u); ++ EXPECT_EQ(buf.cur_offset(), 24u); + EXPECT_EQ(buf.deserialize_with_meta(&s1), 0); +- EXPECT_EQ(buf.cur_offset(), 29u); ++ EXPECT_EQ(buf.cur_offset(), 32u); + EXPECT_EQ(buf.deserialize_with_meta(&s2), 0); +- EXPECT_EQ(buf.cur_offset(), 42u); ++ EXPECT_EQ(buf.cur_offset(), 45u); + EXPECT_EQ(buf.deserialize_with_meta(&s3), 0); +- EXPECT_EQ(buf.cur_offset(), 56u); ++ EXPECT_EQ(buf.cur_offset(), 62u); ++ EXPECT_EQ(((int) reinterpret_cast(buf.buf())) & 7, 0); ++ + EXPECT_EQ(u1, 2468u); + EXPECT_STREQ(s1, nullptr); + EXPECT_STREQ(s2, "hello"); +@@ -227,18 +273,11 @@ TEST_VM(JBoosterNet, serializationn_string) { + EXPECT_STREQ(ss3, s3); + } + +- { MessageBuffer buf(SerializationMode::DESERIALIZE); +- copy_message_buffer(buf, buf0); +- char s1[1]; +- ASSERT_DEATH(buf.deserialize_with_meta(&s1), ""); +- } +- + { MessageBuffer buf(SerializationMode::DESERIALIZE); + copy_message_buffer(buf, buf0); + char s1[64]; + EXPECT_EQ(buf.deserialize_with_meta(&s1), 0); + EXPECT_STREQ(ss1, s1); +- ASSERT_DEATH(buf.deserialize_with_meta(&s1), ""); + } + + { MessageBuffer buf(SerializationMode::DESERIALIZE); +@@ -269,8 +308,20 @@ TEST_VM(JBoosterNet, serializationn_string) { + } + } + +-TEST(JBoosterNet, serializationn_crash) { +- int err; ++#ifdef ASSERT ++ ++TEST_VM_ASSERT_MSG(JBoosterNet, serializationn_string_crash_null, ".*cannot set array to null") { ++ MessageBuffer buf(SerializationMode::BOTH); ++ const char* s = nullptr; ++ EXPECT_EQ(buf.serialize_with_meta(&s), 0); ++ ++ char s1[8]; ++ buf.reset_cur_offset(); ++ buf.deserialize_with_meta(&s1); // should crash here ++ ASSERT_TRUE(false); ++} ++ ++TEST_VM_ASSERT_MSG(JBoosterNet, serializationn_string_crash_arr_too_small, ".*array index out of bounds") { + MessageBuffer buf(SerializationMode::BOTH); + const char* s = "hello"; + EXPECT_EQ(buf.serialize_with_meta(&s), 0); +@@ -282,12 +333,12 @@ TEST(JBoosterNet, serializationn_crash) { + + char s2[5]; + buf.reset_cur_offset(); +- bool old = SuppressFatalErrorMessage; +- SuppressFatalErrorMessage = true; +- ASSERT_DEATH(buf.deserialize_with_meta(&s2), ""); +- SuppressFatalErrorMessage = old; ++ buf.deserialize_with_meta(&s2); // should crash here ++ ASSERT_TRUE(false); + } + ++#endif // ASSERT ++ + TEST(JBoosterNet, serializationn_wrappers) { + MessageBuffer buf(SerializationMode::BOTH); + uint32_t mem_size = 16u * 1024; +@@ -303,7 +354,7 @@ TEST(JBoosterNet, serializationn_wrappers) { + ga.append(&s4); + ArrayWrapper aw(&ga); + EXPECT_EQ(buf.serialize_with_meta(&aw), 0); +- EXPECT_EQ(buf.cur_offset(), 0u + (4 + 4) + (1 + 2 + 3 + 4 + 4 * (4 + 4))); ++ EXPECT_EQ(buf.cur_offset(), 0u + (4 + 4) + ((1 + 3) + (2 + 2) + (3 + 1) + 4 + 4 * (4 + 4))); + + char* mem = NEW_C_HEAP_ARRAY(char, mem_size, mtJBooster); + memset(mem, 0x68, mem_size); +@@ -385,6 +436,30 @@ TEST(JBoosterNet, serializationn_file_wrapper) { + FileUtils::remove(file_name2); + } + ++TEST(JBoosterNet, serializationn_others) { ++ MessageBuffer buf(SerializationMode::BOTH); ++ ++ { ++ JClientBoostLevel lvl; ++ lvl.set_allow_clr(true); ++ lvl.set_allow_aot(true); ++ lvl.set_enable_cds_agg(true); ++ EXPECT_EQ(buf.serialize_with_meta(&lvl), 0); ++ } ++ ++ buf.reset_cur_offset(); ++ ++ { ++ JClientBoostLevel lvl; ++ EXPECT_EQ(buf.deserialize_with_meta(&lvl), 0); ++ EXPECT_EQ(lvl.is_clr_allowed(), true); ++ EXPECT_EQ(lvl.is_cds_allowed(), false); ++ EXPECT_EQ(lvl.is_aot_allowed(), true); ++ EXPECT_EQ(lvl.is_aot_pgo_enabled(), false); ++ EXPECT_EQ(lvl.is_cds_agg_enabled(), true); ++ } ++} ++ + TEST(JBoosterNet, expansion_of_message_buffer) { + MessageBuffer buf(SerializationMode::SERIALIZE); + ASSERT_EQ(buf.buf_size(), 4096u); +-- +2.22.0 + diff --git a/Optimize-LazyAOT-klasses-sending-strategy.patch b/Optimize-LazyAOT-klasses-sending-strategy.patch new file mode 100644 index 0000000..573b061 --- /dev/null +++ b/Optimize-LazyAOT-klasses-sending-strategy.patch @@ -0,0 +1,880 @@ +From 3cc21f911715cc0112f9a2e80a199723b29262e1 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:39:13 +0800 +Subject: Optimize LazyAOT klasses sending strategy + +--- + .../jbooster/client/clientMessageHandler.cpp | 6 ++ + .../jbooster/client/clientMessageHandler.hpp | 1 + + .../share/jbooster/jClientArguments.cpp | 15 +++ + .../share/jbooster/jClientArguments.hpp | 1 + + .../share/jbooster/jbooster_globals.hpp | 4 + + src/hotspot/share/jbooster/lazyAot.cpp | 15 ++- + src/hotspot/share/jbooster/lazyAot.hpp | 2 + + .../jbooster/net/communicationStream.cpp | 15 ++- + .../net/communicationStream.inline.hpp | 1 + + src/hotspot/share/jbooster/net/errorCode.hpp | 1 + + src/hotspot/share/jbooster/net/message.hpp | 5 + + .../share/jbooster/net/message.inline.hpp | 2 + + .../share/jbooster/net/messageType.hpp | 1 + + src/hotspot/share/jbooster/net/netCommon.hpp | 8 +- + .../jbooster/server/serverMessageHandler.cpp | 10 +- + .../jbooster/server/serverMessageHandler.hpp | 3 +- + .../share/jbooster/utilities/fileUtils.cpp | 64 ++----------- + .../share/jbooster/utilities/fileUtils.hpp | 3 +- + .../jbooster/JBoosterCompilationContext.java | 5 + + .../graalvm/compiler/java/BytecodeParser.java | 29 ++++++ + .../share/classes/jdk/jbooster/JBooster.java | 8 +- + .../JBoosterCompilationContextImpl.java | 10 +- + test/hotspot/gtest/jbooster/common.hpp | 96 +++++++++++++++++++ + test/hotspot/gtest/jbooster/test_net.cpp | 4 +- + test/hotspot/gtest/jbooster/test_util.cpp | 12 ++- + test/jdk/tools/jbooster/JBoosterTestBase.java | 6 +- + 26 files changed, 244 insertions(+), 83 deletions(-) + create mode 100644 test/hotspot/gtest/jbooster/common.hpp + +diff --git a/src/hotspot/share/jbooster/client/clientMessageHandler.cpp b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp +index 9119b897a..b8278d41c 100644 +--- a/src/hotspot/share/jbooster/client/clientMessageHandler.cpp ++++ b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp +@@ -289,6 +289,12 @@ int ClientMessageHandler::handle_ArrayKlasses() { + return 0; + } + ++int ClientMessageHandler::handle_ResolveExtraKlasses() { ++ ResourceMark rm; ++ bool should_resolve_extra_Klasses = JBoosterResolveExtraKlasses; ++ JB_RETURN(cs().send_response(&should_resolve_extra_Klasses)); ++ return 0; ++} + // ---------------------------------- Some Tasks ----------------------------------- + + int ClientMessageHandler::send_cache_file_sync_task() { +diff --git a/src/hotspot/share/jbooster/client/clientMessageHandler.hpp b/src/hotspot/share/jbooster/client/clientMessageHandler.hpp +index 66844247c..94ff7387d 100644 +--- a/src/hotspot/share/jbooster/client/clientMessageHandler.hpp ++++ b/src/hotspot/share/jbooster/client/clientMessageHandler.hpp +@@ -38,6 +38,7 @@ + F(MethodLocators ) \ + F(Profilinginfo ) \ + F(ArrayKlasses ) \ ++ F(ResolveExtraKlasses ) \ + + class ArrayKlass; + class ClassLoaderData; +diff --git a/src/hotspot/share/jbooster/jClientArguments.cpp b/src/hotspot/share/jbooster/jClientArguments.cpp +index b215913ea..42aba4181 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.cpp ++++ b/src/hotspot/share/jbooster/jClientArguments.cpp +@@ -171,6 +171,14 @@ const char* calc_java_commands_by_class(const char* full_cmd, int full_cmd_len) + return StringUtils::copy_to_heap(start, mtJBooster); + } + ++static int64_t calc_cds_file_size() { ++ char* default_archive_path = Arguments::get_default_shared_archive_path(); ++ if (default_archive_path == nullptr) { return -1; } ++ int64_t cds_file_size = FileUtils::file_size(default_archive_path); ++ StringUtils::free_from_heap(default_archive_path); ++ return cds_file_size; ++} ++ + JClientArguments::JClientArguments(bool is_client) { + if (is_client) { + init_for_client(); +@@ -224,6 +232,7 @@ void JClientArguments::init_for_client() { + _classpath_name_hash = calc_classpath_name_hash(app_cp, app_cp_len); + _classpath_timestamp_hash = calc_classpath_timestamp_hash(app_cp, app_cp_len); + _agent_name_hash = calc_agent_name_hash(); ++ _cds_file_size = calc_cds_file_size(); + if (JBoosterClientStrictMatch) { + _java_commands = is_jar ? calc_java_commands_by_jar(full_cmd, app_cp_len) + : calc_java_commands_by_class(full_cmd, full_cmd_len); +@@ -248,6 +257,7 @@ uint32_t JClientArguments::calc_hash() { + result = calc_new_hash(result, _classpath_name_hash); + result = calc_new_hash(result, _classpath_timestamp_hash); + result = calc_new_hash(result, _agent_name_hash); ++ result = calc_new_hash(result, _cds_file_size); + result = calc_new_hash(result, StringUtils::hash_code(_java_commands)); + result = calc_new_hash(result, _related_flags->hash()); + +@@ -267,6 +277,7 @@ bool JClientArguments::equals(const JClientArguments* that) const { + if (this->_classpath_name_hash != that->_classpath_name_hash) return false; + if (this->_classpath_timestamp_hash != that->_classpath_timestamp_hash) return false; + if (this->_agent_name_hash != that->_agent_name_hash) return false; ++ if (this->_cds_file_size != that->_cds_file_size) return false; + if (StringUtils::compare(this->_java_commands, that->_java_commands) != 0) return false; + if (!this->_related_flags->equals(that->_related_flags)) return false; + return true; +@@ -284,6 +295,7 @@ void JClientArguments::print_args(outputStream* st) const { + st->print_cr(" classpath_name_hash: %x", _classpath_name_hash); + st->print_cr(" classpath_timestamp_hash: %x", _classpath_timestamp_hash); + st->print_cr(" agent_name_hash: %x", _agent_name_hash); ++ st->print_cr(" cds_file_size: %lu", _cds_file_size); + st->print_cr(" java_commands: \"%s\"", _java_commands); + st->print_cr(" vm_flags:"); + st->print_cr(" hash: %u", _related_flags->hash()); +@@ -307,6 +319,7 @@ int JClientArguments::serialize(MessageBuffer& buf) const { + JB_RETURN(buf.serialize_no_meta(_classpath_name_hash)); + JB_RETURN(buf.serialize_no_meta(_classpath_timestamp_hash)); + JB_RETURN(buf.serialize_no_meta(_agent_name_hash)); ++ JB_RETURN(buf.serialize_no_meta(_cds_file_size)); + JB_RETURN(buf.serialize_with_meta(&_java_commands)); + JB_RETURN(buf.serialize_with_meta(_related_flags)); + +@@ -343,6 +356,8 @@ int JClientArguments::deserialize(MessageBuffer& buf) { + + JB_RETURN(buf.deserialize_ref_no_meta(_agent_name_hash)); + ++ JB_RETURN(buf.deserialize_ref_no_meta(_cds_file_size)); ++ + StringWrapper sw_java_commands; + JB_RETURN(buf.deserialize_with_meta(&sw_java_commands)); + _java_commands = sw_java_commands.export_string(); +diff --git a/src/hotspot/share/jbooster/jClientArguments.hpp b/src/hotspot/share/jbooster/jClientArguments.hpp +index 09c5dfdc0..491ac3159 100644 +--- a/src/hotspot/share/jbooster/jClientArguments.hpp ++++ b/src/hotspot/share/jbooster/jClientArguments.hpp +@@ -92,6 +92,7 @@ private: + uint32_t _classpath_name_hash; + uint32_t _classpath_timestamp_hash; + uint32_t _agent_name_hash; ++ int64_t _cds_file_size; + const char* _java_commands; + JClientVMFlags* _related_flags; + // ========================= end ========================= +diff --git a/src/hotspot/share/jbooster/jbooster_globals.hpp b/src/hotspot/share/jbooster/jbooster_globals.hpp +index 74968af75..c3cdd71f8 100644 +--- a/src/hotspot/share/jbooster/jbooster_globals.hpp ++++ b/src/hotspot/share/jbooster/jbooster_globals.hpp +@@ -119,6 +119,10 @@ + \ + product(ccstr, JBoosterServerSSLRootCerts, NULL, EXPERIMENTAL, \ + "The file path to save server SSL root certificate.") \ ++ \ ++ product(bool, JBoosterResolveExtraKlasses, true, EXPERIMENTAL, \ ++ "Whether resolve additional klasses " \ ++ "while collecting klasses for AOT.") \ + + + // end of JBOOSTER_FLAGS +diff --git a/src/hotspot/share/jbooster/lazyAot.cpp b/src/hotspot/share/jbooster/lazyAot.cpp +index 410d575e9..462b0c72b 100644 +--- a/src/hotspot/share/jbooster/lazyAot.cpp ++++ b/src/hotspot/share/jbooster/lazyAot.cpp +@@ -199,7 +199,11 @@ void LazyAOT::collect_klasses_in_constant_pool(GrowableArray* ds + int len = dst->length(); + for (int i = last_len; i < len; ++i) { + ThreadInVMfromNative tiv(THREAD); +- collect_klasses_in_constant_pool(dst, vis, dst->at(i), ALL_KLASSES, CHECK); ++ if (JBoosterResolveExtraKlasses) { ++ collect_klasses_in_constant_pool(dst, vis, dst->at(i), ALL_KLASSES, CHECK); ++ } else { ++ collect_klasses_in_constant_pool(dst, vis, dst->at(i), RESOLVED_KLASSES, CHECK); ++ } + } + last_len = len; + } +@@ -527,6 +531,7 @@ bool LazyAOT::compile_classes_by_graal(int session_id, + const char* file_path, + GrowableArray* klasses, + bool use_pgo, ++ bool resolve_no_extra_klasses, + TRAPS) { + DebugUtils::assert_thread_in_vm(); + +@@ -541,9 +546,11 @@ bool LazyAOT::compile_classes_by_graal(int session_id, + java_args.push_int(session_id); + java_args.push_oop(file_path_h); + java_args.push_oop(hash_set_h); ++ java_args.push_int(use_pgo); ++ java_args.push_int(resolve_no_extra_klasses); + + TempNewSymbol compile_classes_name = SymbolTable::new_symbol("compileClasses"); +- TempNewSymbol compile_classes_signature = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/util/Set;)Z"); ++ TempNewSymbol compile_classes_signature = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/util/Set;ZZ)Z"); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + compile_classes_name, + compile_classes_signature, +@@ -557,6 +564,7 @@ bool LazyAOT::compile_methods_by_graal(int session_id, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + bool use_pgo, ++ bool resolve_no_extra_klasses, + TRAPS) { + DebugUtils::assert_thread_in_vm(); + +@@ -580,9 +588,10 @@ bool LazyAOT::compile_methods_by_graal(int session_id, + java_args.push_oop(method_name_set_h); + java_args.push_oop(not_method_name_set_h); + java_args.push_int(use_pgo); ++ java_args.push_int(resolve_no_extra_klasses); + + TempNewSymbol compile_methods_name = SymbolTable::new_symbol("compileMethods"); +- TempNewSymbol compile_methods_signature = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;Z)Z"); ++ TempNewSymbol compile_methods_signature = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;ZZ)Z"); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + compile_methods_name, + compile_methods_signature, +diff --git a/src/hotspot/share/jbooster/lazyAot.hpp b/src/hotspot/share/jbooster/lazyAot.hpp +index 1d2a49351..5b4183118 100644 +--- a/src/hotspot/share/jbooster/lazyAot.hpp ++++ b/src/hotspot/share/jbooster/lazyAot.hpp +@@ -112,6 +112,7 @@ public: + const char* file_path, + GrowableArray* klasses, + bool use_pgo, ++ bool resolve_extra_klasses, + TRAPS); + static bool compile_methods_by_graal(int session_id, + const char* file_path, +@@ -119,6 +120,7 @@ public: + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + bool use_pgo, ++ bool resolve_extra_klasses, + TRAPS); + }; + +diff --git a/src/hotspot/share/jbooster/net/communicationStream.cpp b/src/hotspot/share/jbooster/net/communicationStream.cpp +index cdb4b8fa7..37bb45032 100644 +--- a/src/hotspot/share/jbooster/net/communicationStream.cpp ++++ b/src/hotspot/share/jbooster/net/communicationStream.cpp +@@ -145,16 +145,18 @@ int CommunicationStream::recv_message() { + Message& msg = _msg_recv; + // read once (or memmove from the overflowed buffer) to get message size + uint32_t read_size, msg_size; ++ uint16_t magic_num; ++ const uint32_t min_read_size = sizeof(msg_size) + sizeof(magic_num); + if (msg.has_overflow()) { + read_size = msg.move_overflow(); +- if (read_size < sizeof(msg_size)) { ++ if (read_size < min_read_size) { + read_size += read_once_from_stream(msg.buf_beginning() + read_size, msg.buf_size() - read_size); + } + } else { + read_size = read_once_from_stream(msg.buf_beginning(), msg.buf_size()); + } + +- if (read_size < sizeof(msg_size)) { ++ if (read_size < min_read_size) { + if (!is_stream_closed()) { + log_warning(jbooster, rpc)("Failed to read the size of the message (read_size=%d). stream_id=%u.", + read_size, stream_id()); +@@ -162,7 +164,16 @@ int CommunicationStream::recv_message() { + return return_errno_or_flag(JBErr::BAD_MSG_SIZE); + } + ++ magic_num = msg.deserialize_magic_num_only(); ++ if (magic_num != MessageConst::RPC_MAGIC) { ++ log_warning(jbooster, rpc)("Message not from JBooster client. stream_id=%u.", stream_id()); ++ return return_errno_or_flag(JBErr::BAD_MAGIC_NUM); ++ } + msg_size = msg.deserialize_size_only(); ++ if (msg_size > MessageConst::MAX_MSG_SIZE) { ++ log_warning(jbooster, rpc)("Message size should be no more than 2GB. stream_id=%u.", stream_id()); ++ return return_errno_or_flag(JBErr::BAD_MSG_SIZE); ++ } + if (read_size > msg_size) { // read too much + msg.set_overflow(msg_size, read_size - msg_size); + } else if (read_size < msg_size) { // read the rest then +diff --git a/src/hotspot/share/jbooster/net/communicationStream.inline.hpp b/src/hotspot/share/jbooster/net/communicationStream.inline.hpp +index 45311c2be..bc5c0565f 100644 +--- a/src/hotspot/share/jbooster/net/communicationStream.inline.hpp ++++ b/src/hotspot/share/jbooster/net/communicationStream.inline.hpp +@@ -58,6 +58,7 @@ inline bool CommunicationStream::check_received_message_size() { + template + inline int CommunicationStream::send_request(MessageType type, const Args* const... args) { + _msg_send.set_msg_type(type); ++ _msg_send.set_magic_num(MessageConst::RPC_MAGIC); + _msg_send.set_cur_buf_offset_after_meta(); + JB_RETURN(_msg_send.serialize(args...)); + _msg_send.set_msg_size_based_on_cur_buf_offset(); +diff --git a/src/hotspot/share/jbooster/net/errorCode.hpp b/src/hotspot/share/jbooster/net/errorCode.hpp +index 625ef6951..cbb4cc54c 100644 +--- a/src/hotspot/share/jbooster/net/errorCode.hpp ++++ b/src/hotspot/share/jbooster/net/errorCode.hpp +@@ -30,6 +30,7 @@ + f(CONN_CLOSED, "Connection has been closed" ) \ + f(CONN_CLOSED_BY_PEER, "Connection is closed by the other end" ) \ + f(BAD_SSL, "Unexpected SSL error during initialization" ) \ ++ f(BAD_MAGIC_NUM, "Unexpected magic number of the received message" ) \ + f(BAD_MSG_SIZE, "Unexpected size of the received message" ) \ + f(BAD_MSG_TYPE, "Unexpected message type of the received message" ) \ + f(BAD_MSG_DATA, "Unexpected payload data of the received message" ) \ +diff --git a/src/hotspot/share/jbooster/net/message.hpp b/src/hotspot/share/jbooster/net/message.hpp +index 47d2634e2..413ff84a5 100644 +--- a/src/hotspot/share/jbooster/net/message.hpp ++++ b/src/hotspot/share/jbooster/net/message.hpp +@@ -33,6 +33,7 @@ class Message: public MessageConst { + private: + struct { + uint32_t msg_size; ++ uint16_t magic_num; + MessageType msg_type; + } _meta; + +@@ -58,6 +59,7 @@ public: + uint32_t msg_size() const { return _meta.msg_size; } + void set_msg_size(uint32_t size) { _meta.msg_size = size; } + void set_msg_size_based_on_cur_buf_offset() { set_msg_size(cur_buf_offset()); } ++ void set_magic_num(uint16_t magic_num) { _meta.magic_num = magic_num; } + MessageType msg_type() const { return _meta.msg_type; } + void set_msg_type(MessageType type) { _meta.msg_type = type; } + +@@ -76,6 +78,9 @@ public: + } + + uint32_t deserialize_size_only() { return *((uint32_t*)_buf.buf()); } ++ uint16_t deserialize_magic_num_only() { return *((uint16_t*)(_buf.buf() + sizeof(_meta.msg_size))); } ++ ++ bool check_magic_num(uint16_t magic_num) { return magic_num == MessageConst::RPC_MAGIC; } + + template + int serialize(const Args* const... args); +diff --git a/src/hotspot/share/jbooster/net/message.inline.hpp b/src/hotspot/share/jbooster/net/message.inline.hpp +index 5b5add47f..b85362efe 100644 +--- a/src/hotspot/share/jbooster/net/message.inline.hpp ++++ b/src/hotspot/share/jbooster/net/message.inline.hpp +@@ -78,6 +78,7 @@ inline int Message::deserialize(Args* const&... args) { + inline void Message::serialize_meta() { + _buf.set_cur_offset(0); + _buf.serialize_no_meta(_meta.msg_size); ++ _buf.serialize_no_meta(_meta.magic_num); + _buf.serialize_no_meta(_meta.msg_type); + assert(cur_buf_offset() == meta_size, "sanity"); + } +@@ -85,6 +86,7 @@ inline void Message::serialize_meta() { + inline void Message::deserialize_meta() { + _buf.set_cur_offset(0); + _buf.deserialize_ref_no_meta(_meta.msg_size); ++ _buf.deserialize_ref_no_meta(_meta.magic_num); + _buf.deserialize_ref_no_meta(_meta.msg_type); + assert(cur_buf_offset() == meta_size, "sanity"); + } +diff --git a/src/hotspot/share/jbooster/net/messageType.hpp b/src/hotspot/share/jbooster/net/messageType.hpp +index f8cb8f3e6..dbd89c07c 100644 +--- a/src/hotspot/share/jbooster/net/messageType.hpp ++++ b/src/hotspot/share/jbooster/net/messageType.hpp +@@ -54,6 +54,7 @@ + f(ArrayKlasses, "from server" ) \ + f(DataOfKlasses, "from server" ) \ + f(MethodLocators, "from server" ) \ ++ f(ResolveExtraKlasses, "from server" ) \ + \ + /* others */ \ + f(FileSegment, "from both" ) \ +diff --git a/src/hotspot/share/jbooster/net/netCommon.hpp b/src/hotspot/share/jbooster/net/netCommon.hpp +index 8706b7f22..2e30708cc 100644 +--- a/src/hotspot/share/jbooster/net/netCommon.hpp ++++ b/src/hotspot/share/jbooster/net/netCommon.hpp +@@ -133,13 +133,15 @@ + + class MessageConst { + public: ++ static constexpr uint32_t MAX_MSG_SIZE = 0x80000000; ++ static constexpr uint16_t RPC_MAGIC = 0xB05E; + enum: uint32_t { + /** + * The layout of the message in the buffer: +- * | msg_size | msg_type | ... (all of the arguments) ... | +- * | 4 bytes | 2 bytes | msg_size - 4 - 2 bytes | ++ * | msg_size | magic_num | msg_type | ... (all of the arguments) ... | ++ * | 4 bytes | 2 bytes | 2 bytes | msg_size - 4 - 2 bytes | + */ +- meta_size = sizeof(uint32_t) + sizeof(MessageType), ++ meta_size = sizeof(uint32_t) + sizeof(uint16_t) + sizeof(MessageType), + /** + * The layout of each argument in the buffer: + * | arg_size | ... (payload of the argument) ... | +diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +index f46b839bb..713ea1374 100644 +--- a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp ++++ b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp +@@ -319,11 +319,15 @@ int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + GrowableArray methods_to_compile; + GrowableArray methods_not_compile; + bool compile_in_current_thread = false; ++ bool resolve_extra_klasses = true; + + JB_TRY { + compile_in_current_thread = !aot_cache_state.is_cached() && aot_cache_state.set_being_generated(); + JB_THROW(ss().send_response(&compile_in_current_thread)); + if (compile_in_current_thread) { ++ JB_RETURN(ss().send_request(MessageType::ResolveExtraKlasses)); ++ JB_RETURN(ss().recv_response(&resolve_extra_klasses)); ++ + JB_THROW(request_missing_class_loaders(THREAD)); + JB_THROW(request_missing_klasses(THREAD)); + JB_THROW(request_methods_to_compile(&klasses_to_compile, &methods_to_compile, THREAD)); +@@ -345,6 +349,7 @@ int ServerMessageHandler::handle_lazy_aot_compilation_task(TRAPS) { + &methods_to_compile, + &methods_not_compile, + enabling_aot_pgo, ++ resolve_extra_klasses, + THREAD)); + } else { // not compile in current thread + if (aot_cache_state.is_being_generated()) { +@@ -365,6 +370,7 @@ int ServerMessageHandler::try_to_compile_lazy_aot(GrowableArray* + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + bool enabling_aot_pgo, ++ bool resolve_extra_klasses, + TRAPS) { + JClientProgramData* pd = ss().session_data()->program_data(); + JClientCacheState& aot_cache_state = enabling_aot_pgo ? pd->aot_pgo_cache_state() : pd->aot_static_cache_state(); +@@ -382,10 +388,10 @@ int ServerMessageHandler::try_to_compile_lazy_aot(GrowableArray* + + ThreadInVMfromNative tiv(THREAD); + if (methods_to_compile->is_empty()) { +- successful = LazyAOT::compile_classes_by_graal(session_id, file_path, klasses_to_compile, enabling_aot_pgo, THREAD); ++ successful = LazyAOT::compile_classes_by_graal(session_id, file_path, klasses_to_compile, enabling_aot_pgo, resolve_extra_klasses, THREAD); + } else { + successful = LazyAOT::compile_methods_by_graal(session_id, file_path, klasses_to_compile, +- methods_to_compile, methods_not_compile, enabling_aot_pgo, THREAD); ++ methods_to_compile, methods_not_compile, enabling_aot_pgo, resolve_extra_klasses, THREAD); + } + + if (successful) { +diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.hpp b/src/hotspot/share/jbooster/server/serverMessageHandler.hpp +index 26926dd41..c761b9c44 100644 +--- a/src/hotspot/share/jbooster/server/serverMessageHandler.hpp ++++ b/src/hotspot/share/jbooster/server/serverMessageHandler.hpp +@@ -59,7 +59,8 @@ private: + int try_to_compile_lazy_aot(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, +- bool use_pgo, ++ bool enabling_aot_pgo, ++ bool resolve_extra_klasses, + TRAPS); + public: + ServerMessageHandler(ServerStream* server_stream); +diff --git a/src/hotspot/share/jbooster/utilities/fileUtils.cpp b/src/hotspot/share/jbooster/utilities/fileUtils.cpp +index f19bf8fb3..598013523 100644 +--- a/src/hotspot/share/jbooster/utilities/fileUtils.cpp ++++ b/src/hotspot/share/jbooster/utilities/fileUtils.cpp +@@ -75,6 +75,13 @@ bool FileUtils::is_dir(const char* path) { + return S_ISDIR(st.st_mode); + } + ++int64_t FileUtils::file_size(const char* path) { ++ struct stat st = {0}; ++ if (os::stat(path, &st) != 0) return -1; ++ // We don't care if it is a regular file. ++ return st.st_size; ++} ++ + uint64_t FileUtils::modify_time(const char* path) { + struct stat st = {0}; + if (os::stat(path, &st) != 0) return 0; +@@ -119,60 +126,3 @@ bool FileUtils::move(const char* path_from, const char* path_to) { + bool FileUtils::remove(const char* path) { + return ::remove(path) == 0; + } +- +-bool FileUtils::is_same(const char* path1, const char* path2) { +- bool res = false; +- char* buf1 = nullptr; +- char* buf2 = nullptr; +- int fd1 = os::open(path1, O_BINARY | O_RDONLY, 0); +- int fd2 = os::open(path2, O_BINARY | O_RDONLY, 0); +- do { +- if (fd1 < 0 || fd2 < 0) break; +- int64_t size1 = os::lseek(fd1, 0, SEEK_END); +- int64_t size2 = os::lseek(fd2, 0, SEEK_END); +- if (size1 != size2) break; +- int64_t size = size1; +- os::lseek(fd1, 0, SEEK_SET); +- os::lseek(fd2, 0, SEEK_SET); +- // We don't use NEW_RESOURCE_ARRAY here as Thread::current() may +- // not be initialized yet. +- buf1 = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); +- buf2 = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); +- size1 = (int64_t) os::read(fd1, buf1, size); +- size2 = (int64_t) os::read(fd2, buf2, size); +- guarantee(size1 == size && size2 == size, "sanity"); +- res = memcmp(buf1, buf2, size) == 0; +- } while (false); +- if (fd1 >= 0) os::close(fd1); +- if (fd2 >= 0) os::close(fd2); +- if (buf1 != nullptr) { +- FREE_C_HEAP_ARRAY(char, buf1); +- } +- if (buf2 != nullptr) { +- FREE_C_HEAP_ARRAY(char, buf2); +- } +- return res; +-} +- +-bool FileUtils::is_same(const char* path, const char* content, int64_t size) { +- bool res = false; +- char* buf = nullptr; +- int fd = os::open(path, O_BINARY | O_RDONLY, 0); +- do { +- if (fd < 0) break; +- int64_t fsize = os::lseek(fd, 0, SEEK_END); +- if (fsize != size) break; +- os::lseek(fd, 0, SEEK_SET); +- // We don't use NEW_RESOURCE_ARRAY here as Thread::current() may +- // not be initialized yet. +- buf = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); +- fsize = (int64_t) os::read(fd, buf, size); +- guarantee(fsize == size, "sanity"); +- res = memcmp(content, buf, size) == 0; +- } while (false); +- if (fd >= 0) os::close(fd); +- if (buf != nullptr) { +- FREE_C_HEAP_ARRAY(char, buf); +- } +- return res; +-} +diff --git a/src/hotspot/share/jbooster/utilities/fileUtils.hpp b/src/hotspot/share/jbooster/utilities/fileUtils.hpp +index 2b5734754..ccc677af7 100644 +--- a/src/hotspot/share/jbooster/utilities/fileUtils.hpp ++++ b/src/hotspot/share/jbooster/utilities/fileUtils.hpp +@@ -44,14 +44,13 @@ public: + static bool exists(const char* path); + static bool is_file(const char* path); + static bool is_dir(const char* path); ++ static int64_t file_size(const char* path); + static uint64_t modify_time(const char* path); + static bool mkdir(const char* path); + static bool mkdirs(const char* path); + static bool rename(const char* path_from, const char* path_to); + static bool move(const char* path_from, const char* path_to); + static bool remove(const char* path); +- static bool is_same(const char* path1, const char* path2); +- static bool is_same(const char* path, const char* content, int64_t size); + + class ListDir: public StackObj { + const char* _path; +diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java +index b26786a52..85c9bf464 100644 +--- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java ++++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java +@@ -94,6 +94,11 @@ public interface JBoosterCompilationContext { + */ + boolean usePGO(); + ++ /** ++ * return true if compilation uses extra-resolved klasses ++ */ ++ boolean resolveExtraKlasses(); ++ + /** + * Get methodCount of CompiledMethodInfo. + * (To support multi-task concurrent compilation of AOT) +diff --git a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java +index e429e43e8..01ee855d3 100644 +--- a/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java ++++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java +@@ -263,6 +263,7 @@ import static org.graalvm.compiler.nodes.extended.BranchProbabilityNode.LUDICROU + import static org.graalvm.compiler.nodes.graphbuilderconf.IntrinsicContext.CompilationContext.INLINE_DURING_PARSING; + import static org.graalvm.compiler.nodes.type.StampTool.isPointerNonNull; + ++import java.lang.annotation.Target; + import java.util.ArrayList; + import java.util.Collections; + import java.util.Comparator; +@@ -4386,6 +4387,20 @@ public class BytecodeParser implements GraphBuilderContext { + } + + protected void maybeEagerlyResolve(int cpi, int bytecode) { ++ JBoosterCompilationContext ctx = JBoosterCompilationContext.get(); ++ if (ctx != null && !ctx.resolveExtraKlasses()) { ++ try{ ++ maybeEagerlyResolveBase(cpi, bytecode); ++ } catch (NoClassDefFoundError e) { ++ debug.log("Cannot resolve all elements in constant pool"); ++ return; ++ } ++ } else { ++ maybeEagerlyResolveBase(cpi, bytecode); ++ } ++ } ++ ++ protected void maybeEagerlyResolveBase(int cpi, int bytecode) { + if (intrinsicContext != null) { + constantPool.loadReferencedType(cpi, bytecode); + } else if (graphBuilderConfig.eagerResolving()) { +@@ -4420,6 +4435,20 @@ public class BytecodeParser implements GraphBuilderContext { + } + + protected JavaType maybeEagerlyResolve(JavaType type, ResolvedJavaType accessingClass) { ++ JBoosterCompilationContext ctx = JBoosterCompilationContext.get(); ++ if (ctx != null && !ctx.resolveExtraKlasses()) { ++ try{ ++ return maybeEagerlyResolveBase(type, accessingClass); ++ } catch (NoClassDefFoundError e) { ++ debug.log("Cannot resolve all elements in constant pool"); ++ return type; ++ } ++ } else { ++ return maybeEagerlyResolveBase(type, accessingClass); ++ } ++ } ++ ++ protected JavaType maybeEagerlyResolveBase(JavaType type, ResolvedJavaType accessingClass) { + if (graphBuilderConfig.eagerResolving() || parsingIntrinsic()) { + return type.resolve(accessingClass); + } +diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java +index db5d916ac..76d62e9b2 100644 +--- a/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java ++++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java +@@ -175,15 +175,15 @@ public final class JBooster { + /** + * This method is invoked only in C++. + */ +- private static boolean compileClasses(int sessionId, String filePath, Set> classes, boolean usePGO) { +- return compileMethods(sessionId, filePath, classes, null, null, usePGO); ++ private static boolean compileClasses(int sessionId, String filePath, Set> classes, boolean usePGO, boolean resolveExtraKlasses) { ++ return compileMethods(sessionId, filePath, classes, null, null, usePGO, resolveExtraKlasses); + } + + /** + * This method is invoked only in C++. + */ + private static boolean compileMethods(int sessionId, String filePath, Set> classes, +- Set methodsToCompile, Set methodsNotToCompile, boolean usePGO) { ++ Set methodsToCompile, Set methodsNotToCompile, boolean usePGO, boolean resolveExtraKlasses) { + LOGGER.log(INFO, "Compilation task received: classes_to_compile={0}, methods_to_compile={1}, methods_not_compile={2}, session_id={3}.", + classes.size(), + (methodsToCompile == null ? "all" : String.valueOf(methodsToCompile.size())), +@@ -191,7 +191,7 @@ public final class JBooster { + sessionId); + try { + JBoosterCompilationContextImpl ctx = new JBoosterCompilationContextImpl( +- sessionId, filePath, classes, methodsToCompile, methodsNotToCompile, usePGO); ++ sessionId, filePath, classes, methodsToCompile, methodsNotToCompile, usePGO, resolveExtraKlasses); + return new Main(ctx).compileForJBooster(); + } catch (Exception e) { + e.printStackTrace(); +diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java +index 4918f8552..efdeee9e5 100644 +--- a/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java ++++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java +@@ -43,6 +43,7 @@ public class JBoosterCompilationContextImpl implements JBoosterCompilationContex + private final Set methodsToCompile; + private final Set methodsNotToCompile; + private final boolean usePGO; ++ private final boolean resolveExtraKlasses; + + // These values are used to replace the static values in AOT classes. + private final AtomicInteger compiledMethodInfoMethodsCount = new AtomicInteger(0); +@@ -63,13 +64,15 @@ public class JBoosterCompilationContextImpl implements JBoosterCompilationContex + Set> classesToCompile, + Set methodsToCompile, + Set methodsNotCompile, +- boolean usePGO) { ++ boolean usePGO, ++ boolean resolveExtraKlasses) { + this.sessionId = sessionId; + this.filePath = filePath; + this.classesToCompile = classesToCompile; + this.methodsToCompile = methodsToCompile; + this.methodsNotToCompile = methodsNotCompile; + this.usePGO = usePGO; ++ this.resolveExtraKlasses = resolveExtraKlasses; + } + + @Override +@@ -102,6 +105,11 @@ public class JBoosterCompilationContextImpl implements JBoosterCompilationContex + return usePGO; + } + ++ @Override ++ public boolean resolveExtraKlasses() { ++ return resolveExtraKlasses; ++ } ++ + @Override + public AtomicInteger getCompiledMethodInfoMethodsCount() { + return compiledMethodInfoMethodsCount; +diff --git a/test/hotspot/gtest/jbooster/common.hpp b/test/hotspot/gtest/jbooster/common.hpp +new file mode 100644 +index 000000000..15b773786 +--- /dev/null ++++ b/test/hotspot/gtest/jbooster/common.hpp +@@ -0,0 +1,96 @@ ++/* ++ * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. 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 GTEST_JBOOSTER_COMMON ++#define GTEST_JBOOSTER_COMMON ++ ++#if INCLUDE_JBOOSTER ++ ++// @see src/hotspot/share/memory/filemap.cpp ++#ifndef O_BINARY // if defined (Win32) use binary files. ++#define O_BINARY 0 // otherwise do nothing. ++#endif ++ ++class TestUtils { ++public: ++ static bool is_same(const char* path1, const char* path2) { ++ bool res = false; ++ char* buf1 = nullptr; ++ char* buf2 = nullptr; ++ int fd1 = os::open(path1, O_BINARY | O_RDONLY, 0); ++ int fd2 = os::open(path2, O_BINARY | O_RDONLY, 0); ++ do { ++ if (fd1 < 0 || fd2 < 0) break; ++ int64_t size1 = os::lseek(fd1, 0, SEEK_END); ++ int64_t size2 = os::lseek(fd2, 0, SEEK_END); ++ if (size1 != size2) break; ++ int64_t size = size1; ++ os::lseek(fd1, 0, SEEK_SET); ++ os::lseek(fd2, 0, SEEK_SET); ++ // We don't use NEW_RESOURCE_ARRAY here as Thread::current() may ++ // not be initialized yet. ++ buf1 = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); ++ buf2 = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); ++ size1 = (int64_t) os::read(fd1, buf1, size); ++ size2 = (int64_t) os::read(fd2, buf2, size); ++ guarantee(size1 == size && size2 == size, "sanity"); ++ res = memcmp(buf1, buf2, size) == 0; ++ } while (false); ++ if (fd1 >= 0) os::close(fd1); ++ if (fd2 >= 0) os::close(fd2); ++ if (buf1 != nullptr) { ++ FREE_C_HEAP_ARRAY(char, buf1); ++ } ++ if (buf2 != nullptr) { ++ FREE_C_HEAP_ARRAY(char, buf2); ++ } ++ return res; ++ } ++ ++ static bool is_same(const char* path, const char* content, int64_t size) { ++ bool res = false; ++ char* buf = nullptr; ++ int fd = os::open(path, O_BINARY | O_RDONLY, 0); ++ do { ++ if (fd < 0) break; ++ int64_t fsize = os::lseek(fd, 0, SEEK_END); ++ if (fsize != size) break; ++ os::lseek(fd, 0, SEEK_SET); ++ // We don't use NEW_RESOURCE_ARRAY here as Thread::current() may ++ // not be initialized yet. ++ buf = NEW_C_HEAP_ARRAY(char, (size_t) size, mtJBooster); ++ fsize = (int64_t) os::read(fd, buf, size); ++ guarantee(fsize == size, "sanity"); ++ res = memcmp(content, buf, size) == 0; ++ } while (false); ++ if (fd >= 0) os::close(fd); ++ if (buf != nullptr) { ++ FREE_C_HEAP_ARRAY(char, buf); ++ } ++ return res; ++ } ++}; ++ ++#endif // INCLUDE_JBOOSTER ++ ++#endif // GTEST_JBOOSTER_COMMON +\ No newline at end of file +diff --git a/test/hotspot/gtest/jbooster/test_net.cpp b/test/hotspot/gtest/jbooster/test_net.cpp +index bf20b0aa3..dd45dd65a 100644 +--- a/test/hotspot/gtest/jbooster/test_net.cpp ++++ b/test/hotspot/gtest/jbooster/test_net.cpp +@@ -26,6 +26,8 @@ + + #if INCLUDE_JBOOSTER + ++#include "common.hpp" ++ + #include "jbooster/jClientArguments.hpp" + #include "jbooster/net/message.inline.hpp" + #include "jbooster/net/messageBuffer.inline.hpp" +@@ -431,7 +433,7 @@ TEST(JBoosterNet, serializationn_file_wrapper) { + } + EXPECT_EQ(times, 3); + } +- EXPECT_TRUE(FileUtils::is_same(file_name1, file_name2)); ++ EXPECT_TRUE(TestUtils::is_same(file_name1, file_name2)); + FileUtils::remove(file_name1); + FileUtils::remove(file_name2); + } +diff --git a/test/hotspot/gtest/jbooster/test_util.cpp b/test/hotspot/gtest/jbooster/test_util.cpp +index cd65804be..461e3faa7 100644 +--- a/test/hotspot/gtest/jbooster/test_util.cpp ++++ b/test/hotspot/gtest/jbooster/test_util.cpp +@@ -26,6 +26,8 @@ + + #if INCLUDE_JBOOSTER + ++#include "common.hpp" ++ + #include "classfile/symbolTable.hpp" + #include "jbooster/utilities/concurrentHashMap.inline.hpp" + #include "jbooster/utilities/debugUtils.inline.hpp" +@@ -95,13 +97,13 @@ TEST(JBoosterUtil, file) { + + write_file("gtest-jbooster-tmp5", "12345"); + write_file("gtest-jbooster-tmp6", "12345"); +- EXPECT_TRUE(FileUtils::is_same("gtest-jbooster-tmp5", "gtest-jbooster-tmp6")); ++ EXPECT_TRUE(TestUtils::is_same("gtest-jbooster-tmp5", "gtest-jbooster-tmp6")); + write_file("gtest-jbooster-tmp6", "123456"); +- EXPECT_FALSE(FileUtils::is_same("gtest-jbooster-tmp5", "gtest-jbooster-tmp6")); ++ EXPECT_FALSE(TestUtils::is_same("gtest-jbooster-tmp5", "gtest-jbooster-tmp6")); + +- EXPECT_TRUE(FileUtils::is_same("gtest-jbooster-tmp5", "12345", 6)); +- EXPECT_FALSE(FileUtils::is_same("gtest-jbooster-tmp5", "12346", 6)); +- EXPECT_FALSE(FileUtils::is_same("gtest-jbooster-tmp5", "123456", 7)); ++ EXPECT_TRUE(TestUtils::is_same("gtest-jbooster-tmp5", "12345", 6)); ++ EXPECT_FALSE(TestUtils::is_same("gtest-jbooster-tmp5", "12346", 6)); ++ EXPECT_FALSE(TestUtils::is_same("gtest-jbooster-tmp5", "123456", 7)); + + EXPECT_FALSE(FileUtils::is_file("gtest-jbooster-tmp4")); + EXPECT_TRUE(FileUtils::is_dir("gtest-jbooster-tmp4")); +diff --git a/test/jdk/tools/jbooster/JBoosterTestBase.java b/test/jdk/tools/jbooster/JBoosterTestBase.java +index 08792a77b..4c1dcbd4a 100644 +--- a/test/jdk/tools/jbooster/JBoosterTestBase.java ++++ b/test/jdk/tools/jbooster/JBoosterTestBase.java +@@ -44,11 +44,12 @@ import jdk.test.lib.Utils; + * @see JcmdBase + */ + public class JBoosterTestBase { +- public static final int WAIT_START_TIME = 2; ++ public static final int WAIT_START_TIME = 128; + public static final int WAIT_SHORT_TIME = 8; + public static final int WAIT_EXIT_TIME = 64; + + public static final int SERVER_PORT = 41567; ++ public static final int SERVER_CONNECTION_TIMEOUT = 256 * 1000; + public static final String SERVER_PORT_STR = "41567"; + + public static final String CLIENT_CACHE_PATH = "jbooster-cache-client"; +@@ -80,7 +81,8 @@ public class JBoosterTestBase { + + public static final List SERVER_STANDARD_ARGS = List.of( + "--server-port=" + SERVER_PORT_STR, +- "--cache-path=" + SERVER_CACHE_PATH ++ "--cache-path=" + SERVER_CACHE_PATH, ++ "--connection-timeout=" + SERVER_CONNECTION_TIMEOUT + ); + + private static final ProcessBuilder processBuilder = new ProcessBuilder(); +-- +2.22.0 + diff --git a/SA-redact-support-password.patch b/SA-redact-support-password.patch new file mode 100644 index 0000000..da53a87 --- /dev/null +++ b/SA-redact-support-password.patch @@ -0,0 +1,623 @@ +From fcf500b87f0ddcd1fff0b9a0040b1be1b8a37321 Mon Sep 17 00:00:00 2001 +Date: Fri, 29 Nov 2024 15:36:57 +0800 +Subject: SA redact support password + +--- + src/hotspot/share/runtime/arguments.cpp | 16 ++-- + src/hotspot/share/runtime/arguments.hpp | 5 - + src/hotspot/share/runtime/globals.hpp | 2 +- + src/hotspot/share/services/heapRedactor.cpp | 7 +- + src/hotspot/share/services/heapRedactor.hpp | 1 + + .../classes/sun/jvm/hotspot/SALauncher.java | 10 +- + .../classes/sun/jvm/hotspot/tools/JMap.java | 36 +++++++ + .../hotspot/utilities/HeapHprofBinWriter.java | 75 ++++++++++++++- + .../jvm/hotspot/utilities/HeapRedactor.java | 30 +++--- + .../share/classes/sun/tools/jmap/JMap.java | 96 +++++++++++++++---- + 10 files changed, 227 insertions(+), 51 deletions(-) + +diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp +index 42b4f90f1..f24cabb11 100644 +--- a/src/hotspot/share/runtime/arguments.cpp ++++ b/src/hotspot/share/runtime/arguments.cpp +@@ -121,8 +121,6 @@ bool Arguments::_has_jimage = false; + + char* Arguments::_ext_dirs = NULL; + +-char* Arguments::_heap_dump_redact_auth = NULL; +- + bool PathString::set_value(const char *value) { + if (_value != NULL) { + FreeHeap(_value); +@@ -3743,23 +3741,23 @@ jint Arguments::match_special_option_and_act(const JavaVMInitArgs* args, + warning("Heap dump redacting did not setup properly, using wrong argument?"); + vm_exit_during_initialization("Syntax error, expecting -XX:HeapDumpRedact=[off|names|basic|full|diyrules|annotation]",NULL); + } ++ continue; + } + + // heapDump redact password + if(match_option(option, "-XX:RedactPassword=", &tail)) { + if(tail == NULL || strlen(tail) == 0) { + VerifyRedactPassword = false; +- jio_fprintf(defaultStream::output_stream(), "redact password is null, disable verify heap dump authority.\n"); + } else { +- VerifyRedactPassword = true; +- size_t redact_password_len = strlen(tail); +- _heap_dump_redact_auth = NEW_C_HEAP_ARRAY(char, redact_password_len+1, mtArguments); +- memcpy(_heap_dump_redact_auth, tail, redact_password_len); +- _heap_dump_redact_auth[redact_password_len] = '\0'; +- memset((void*)tail, '0', redact_password_len); ++ char* split_char = strstr(const_cast(tail), ","); ++ VerifyRedactPassword = !(split_char == NULL || strlen(split_char) < SALT_LEN); ++ } ++ if(!VerifyRedactPassword) { ++ jio_fprintf(defaultStream::output_stream(), "redact password is null or with bad format, disable verify heap dump authority.\n"); + } + } + ++ + #ifndef PRODUCT + if (match_option(option, "-XX:+PrintFlagsWithComments")) { + JVMFlag::printFlags(tty, true); +diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp +index 6b9759906..cb2a04a2d 100644 +--- a/src/hotspot/share/runtime/arguments.hpp ++++ b/src/hotspot/share/runtime/arguments.hpp +@@ -468,8 +468,6 @@ class Arguments : AllStatic { + char** base_archive_path, + char** top_archive_path) NOT_CDS_RETURN; + +- static char* _heap_dump_redact_auth; +- + public: + // Parses the arguments, first phase + static jint parse(const JavaVMInitArgs* args); +@@ -555,9 +553,6 @@ class Arguments : AllStatic { + // Java launcher properties + static void process_sun_java_launcher_properties(JavaVMInitArgs* args); + +- // heap dump redact password +- static const char* get_heap_dump_redact_auth() { return _heap_dump_redact_auth; } +- + // System properties + static void init_system_properties(); + +diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp +index b51e50ddf..680e78c04 100644 +--- a/src/hotspot/share/runtime/globals.hpp ++++ b/src/hotspot/share/runtime/globals.hpp +@@ -568,7 +568,7 @@ const intx ObjectAlignmentInBytes = 8; + "verify authority for operating heapDump redact feature") \ + \ + product(ccstr, RedactPassword, NULL, \ +- "authority for operating heapDump redact feature") \ ++ "authority for operating heapDump redact feature, format {password,salt}, salt length >= 8") \ + \ + product(ccstr, NativeMemoryTracking, DEBUG_ONLY("summary") NOT_DEBUG("off"), \ + "Native memory tracking options") \ +diff --git a/src/hotspot/share/services/heapRedactor.cpp b/src/hotspot/share/services/heapRedactor.cpp +index 0e7b0a97c..cfb5b3f82 100644 +--- a/src/hotspot/share/services/heapRedactor.cpp ++++ b/src/hotspot/share/services/heapRedactor.cpp +@@ -170,12 +170,15 @@ void HeapRedactor::init(outputStream* out) { + * if HeapDumpRedact is NULL , jmap operation can not open redact feature without password + * if HeapDumpRedact is not NULL, jmap operation can not change redact level without password + **/ +- if(Arguments::get_heap_dump_redact_auth() == NULL) { ++ char* split_char = NULL; ++ if(RedactPassword == NULL || (split_char = strstr(const_cast(RedactPassword), ",")) == NULL || strlen(split_char) < SALT_LEN) { + VerifyRedactPassword = false; + } + if(VerifyRedactPassword && !_use_sys_params) { ++ size_t auth_len = strlen(RedactPassword); ++ size_t suffix_len = strlen(split_char); + if(_redact_params.redact_password == NULL || +- strcmp(_redact_params.redact_password, Arguments::get_heap_dump_redact_auth()) ) { ++ strncmp(_redact_params.redact_password, RedactPassword, auth_len-suffix_len) ) { + // no password or wrong password + _use_sys_params = true; + if(out != NULL) { +diff --git a/src/hotspot/share/services/heapRedactor.hpp b/src/hotspot/share/services/heapRedactor.hpp +index 790430507..e5a5bf440 100644 +--- a/src/hotspot/share/services/heapRedactor.hpp ++++ b/src/hotspot/share/services/heapRedactor.hpp +@@ -32,6 +32,7 @@ + #endif + + #define MAX_MAP_FILE_LENGTH 1024 ++#define SALT_LEN 9 + + enum HeapDumpRedactLevel { + REDACT_UNKNOWN, +diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java +index 291e483e0..91a432574 100644 +--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java ++++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java +@@ -322,7 +322,8 @@ public class SALauncher { + Map.entry("HeapDumpRedact=", "HeapDumpRedact"), + Map.entry("RedactMap=", "RedactMap"), + Map.entry("RedactMapFile=", "RedactMapFile"), +- Map.entry("RedactClassPath=", "RedactClassPath")); ++ Map.entry("RedactClassPath=", "RedactClassPath"), ++ Map.entry("RedactPassword", "RedactPassword")); + } + + private static void runJMAP(String[] oldArgs) { +@@ -337,6 +338,7 @@ public class SALauncher { + String redactMap = newArgMap.get("RedactMap"); + String redactMapFile = newArgMap.get("RedactMapFile"); + String redactClassPath = newArgMap.get("RedactClassPath"); ++ boolean hasRedactPassword = newArgMap.containsKey("RedactPassword"); + if (!requestHeapdump && (dumpfile != null)) { + throw new IllegalArgumentException("Unexpected argument: dumpfile"); + } +@@ -359,6 +361,9 @@ public class SALauncher { + if (redactClassPath != null) { + command += ",RedactClassPath=" + redactClassPath; + } ++ if(hasRedactPassword) { ++ command += ",RedactPassword"; ++ } + newArgMap.put(command, null); + } + +@@ -369,9 +374,12 @@ public class SALauncher { + newArgMap.remove("RedactMap"); + newArgMap.remove("RedactMapFile"); + newArgMap.remove("RedactClassPath"); ++ newArgMap.remove("RedactPassword"); + JMap.main(buildAttachArgs(newArgMap, false)); + } + ++ ++ + private static void runJINFO(String[] oldArgs) { + Map longOptsMap = Map.of("exe=", "exe", + "core=", "core", +diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java +index e52cd1fb1..fbead3ce4 100644 +--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java ++++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/JMap.java +@@ -25,6 +25,9 @@ + package sun.jvm.hotspot.tools; + + import java.io.*; ++import java.nio.CharBuffer; ++import java.util.regex.Pattern; ++ + import sun.jvm.hotspot.debugger.JVMDebugger; + import sun.jvm.hotspot.utilities.*; + +@@ -189,6 +192,9 @@ public class JMap extends Tool { + redactParams.setRedactMapFile(keyValue[1]); + } else if (keyValue[0].equals("RedactClassPath")) { + redactParams.setRedactClassPath(keyValue[1]); ++ } else if (keyValue[0].equals("RedactPassword")) { ++ redactParams.setRedactPassword(getRedactPassword()); ++ + } else { + System.err.println("unknown option:" + keyValue[0]); + +@@ -226,6 +232,36 @@ public class JMap extends Tool { + jmap.execute(args); + } + ++ private static CharBuffer getRedactPassword() { ++ CharBuffer redactPassword = CharBuffer.wrap(""); ++ // heap dump may need a password ++ Console console = System.console(); ++ char[] passwords = null; ++ if (console == null) { ++ return redactPassword; ++ } ++ ++ try { ++ passwords = console.readPassword("redact authority password:"); ++ } catch (Exception e) { ++ } ++ if(passwords == null) { ++ return redactPassword; ++ } ++ ++ try { ++ CharBuffer cb = CharBuffer.wrap(passwords); ++ String passwordPattern = "^[0-9a-zA-Z!@#$]{1,9}$"; ++ if(!Pattern.matches(passwordPattern, cb)) { ++ return redactPassword; ++ } ++ redactPassword = cb; ++ } catch (Exception e) { ++ } ++ ++ return redactPassword; ++ } ++ + public boolean writeHeapHprofBin(String fileName, int gzLevel) { + try { + HeapHprofBinWriter hgw; +diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java +index e73b6f9a3..566d88646 100644 +--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java ++++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java +@@ -28,6 +28,10 @@ import java.io.*; + import java.nio.ByteBuffer; + import java.nio.ByteOrder; + import java.nio.channels.*; ++import java.nio.CharBuffer; ++import java.security.NoSuchAlgorithmException; ++import java.security.spec.InvalidKeySpecException; ++import java.security.spec.KeySpec; + import java.util.*; + import java.util.zip.*; + import sun.jvm.hotspot.debugger.*; +@@ -37,6 +41,10 @@ import sun.jvm.hotspot.runtime.*; + import sun.jvm.hotspot.classfile.*; + import sun.jvm.hotspot.gc.z.ZCollectedHeap; + ++import javax.crypto.SecretKey; ++import javax.crypto.SecretKeyFactory; ++import javax.crypto.spec.PBEKeySpec; ++ + /* + * This class writes Java heap in hprof binary format. This format is + * used by Heap Analysis Tool (HAT). The class is heavily influenced +@@ -386,6 +394,11 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { + private static final long MAX_U4_VALUE = 0xFFFFFFFFL; + int serialNum = 1; + ++ // encrypt ++ private static int SALT_MIN_LENGTH = 8; ++ private static int HASH_BIT_SIZE = 256; ++ private static int HASH_ITERATIONS_COUNT = 10000; ++ + // Heap Redact + private HeapRedactor heapRedactor; + +@@ -404,6 +417,10 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { + return heapRedactor.getHeapDumpRedactLevel(); + } + ++ public Optional getHeapDumpRedactPassword() { ++ return heapRedactor == null ? Optional.empty() : Optional.ofNullable(heapRedactor.getRedactPassword()); ++ } ++ + private Optional lookupRedactName(String name){ + return heapRedactor == null ? Optional.empty() : heapRedactor.lookupRedactName(name); + } +@@ -454,10 +471,66 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter { + this.gzLevel = gzLevel; + } + ++ private boolean checkPassword() { ++ Optional redactAuthOption = getVMRedactParameter("RedactPassword"); ++ String redactAuth = redactAuthOption.isPresent() ? redactAuthOption.get() : null; ++ boolean redactAuthFlag = true; ++ if(redactAuth != null) { ++ String[] auths = redactAuth.split(","); ++ if(auths.length != 2) { ++ return redactAuthFlag; ++ } ++ ++ Optional passwordOption = getHeapDumpRedactPassword(); ++ CharBuffer password = passwordOption.isPresent() ? passwordOption.get() : CharBuffer.wrap(""); ++ char[] passwordChars = null; ++ try { ++ passwordChars = password.array(); ++ ++ byte[] saltBytes = auths[1].getBytes("UTF-8"); ++ if(saltBytes.length < SALT_MIN_LENGTH) { ++ return redactAuthFlag; ++ } ++ ++ String digestStr = getEncryptValue(passwordChars, saltBytes); ++ redactAuthFlag = auths[0].equals(digestStr); ++ } catch (Exception e) { ++ // ignore ++ redactAuthFlag = false; ++ } finally { ++ // clear all password ++ if(passwordChars != null) { ++ Arrays.fill(passwordChars, '0'); ++ } ++ } ++ } ++ ++ return redactAuthFlag; ++ } ++ ++ private String getEncryptValue(char[] passwordValue, byte[] saltBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { ++ StringBuilder digestStrBuilder = new StringBuilder(); ++ ++ KeySpec spec = new PBEKeySpec(passwordValue, saltBytes, HASH_ITERATIONS_COUNT, HASH_BIT_SIZE); ++ SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); ++ SecretKey secretKey = secretKeyFactory.generateSecret(spec); ++ byte[] digestBytes = secretKey.getEncoded(); ++ for (byte b : digestBytes) { ++ String hex = Integer.toHexString(0xff & b); ++ if (hex.length() == 1) { ++ digestStrBuilder.append('0'); ++ } ++ digestStrBuilder.append(hex); ++ } ++ String digestStr = digestStrBuilder.toString(); ++ ++ return digestStr; ++ } ++ + public synchronized void write(String fileName) throws IOException { + VM vm = VM.getVM(); + +- if(getHeapDumpRedactLevel() == HeapRedactor.HeapDumpRedactLevel.REDACT_UNKNOWN) { ++ if(getHeapDumpRedactLevel() == HeapRedactor.HeapDumpRedactLevel.REDACT_UNKNOWN || !checkPassword()) { + resetRedactParams(); + } + +diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java +index c2a916617..5c442b2bb 100644 +--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java ++++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java +@@ -30,6 +30,7 @@ import java.io.BufferedReader; + import java.io.File; + import java.io.FileReader; + import java.io.IOException; ++import java.nio.CharBuffer; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.List; +@@ -51,7 +52,7 @@ public class HeapRedactor { + private HeapDumpRedactLevel redactLevel; + private Map redactNameTable; + private Map> redactClassTable; +- private String redactClassFullName = null; ++ private String redactClassFullName = null; + private Map redactValueTable; + private RedactVectorNode headerNode; + private RedactVectorNode currentNode; +@@ -62,6 +63,7 @@ public class HeapRedactor { + public static final String REDACT_MAP_PREFIX = "RedactMap="; + public static final String REDACT_MAP_FILE_PREFIX = "RedactMapFile="; + public static final String REDACT_CLASS_PATH_PREFIX = "RedactClassPath="; ++ public static final String REDACT_PASSWORD_PREFIX = "RedactPassword="; + + public static final String REDACT_UNKNOWN_STR = "UNKNOWN"; + public static final String REDACT_OFF_STR = "OFF"; +@@ -82,14 +84,6 @@ public class HeapRedactor { + public static final int PATH_MAX = 4096; + public static final int REDACT_VECTOR_SIZE = 1024; + +- public HeapRedactor(String options) { +- redactLevel = HeapDumpRedactLevel.REDACT_UNKNOWN; +- redactNameTable = null; +- redactClassTable = null; +- redactValueTable = null; +- init(options); +- } +- + public HeapRedactor(RedactParams redactParams) { + this.redactParams = redactParams; + redactLevel = HeapDumpRedactLevel.REDACT_UNKNOWN; +@@ -167,6 +161,10 @@ public class HeapRedactor { + return redactParams.getRedactClassPath(); + } + ++ public CharBuffer getRedactPassword(){ ++ return redactParams.getRedactPassword(); ++ } ++ + public Optional> getRedactRulesTable(String key) { + return Optional.ofNullable(redactClassTable == null ? null: redactClassTable.get(key)); + } +@@ -218,7 +216,7 @@ public class HeapRedactor { + } + + private RedactParams parseRedactOptions(String optionStr) { +- RedactParams params = new RedactParams(REDACT_OFF_OPTION, null, null, null); ++ RedactParams params = new RedactParams(REDACT_OFF_OPTION, null, null, null, null); + if (optionStr != null) { + String[] options = optionStr.split(","); + for (String option : options) { +@@ -321,16 +319,18 @@ public class HeapRedactor { + private String redactMap; + private String redactMapFile; + private String redactClassPath; ++ private CharBuffer redactPassword; + private boolean enableRedact = false; + + public RedactParams() { + } + +- public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath) { ++ public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath, CharBuffer redactPassword) { + this.heapDumpRedact = heapDumpRedact; + this.redactMap = redactMap; + this.redactMapFile = redactMapFile; + this.redactClassPath = redactClassPath; ++ this.redactPassword = redactPassword; + } + + @Override +@@ -395,6 +395,14 @@ public class HeapRedactor { + this.redactClassPath = redactClassPath; + } + ++ public CharBuffer getRedactPassword() { ++ return redactPassword; ++ } ++ ++ public void setRedactPassword(CharBuffer redactPassword) { ++ this.redactPassword = redactPassword; ++ } ++ + public static boolean checkLauncherHeapdumpRedactSupport(String value) { + String[] validValues = {REDACT_BASIC_OPTION, REDACT_NAME_OPTION, REDACT_FULL_OPTION, REDACT_DIYRULES_OPTION, REDACT_ANNOTATION_OPTION, REDACT_OFF_OPTION}; + for (String validValue : validValues) { +diff --git a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java +index ef4ea7152..6479863a6 100644 +--- a/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java ++++ b/src/jdk.jcmd/share/classes/sun/tools/jmap/JMap.java +@@ -25,14 +25,17 @@ + + package sun.tools.jmap; + ++import java.io.BufferedInputStream; + import java.io.Console; + import java.io.File; + import java.io.IOException; + import java.io.InputStream; ++import java.io.InputStreamReader; + import java.io.UnsupportedEncodingException; + import java.nio.CharBuffer; +-import java.nio.charset.Charset; +-import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.security.spec.InvalidKeySpecException; ++import java.security.spec.KeySpec; + import java.util.Arrays; + import java.util.Collection; + import java.util.regex.Pattern; +@@ -43,6 +46,10 @@ import com.sun.tools.attach.AttachNotSupportedException; + import sun.tools.attach.HotSpotVirtualMachine; + import sun.tools.common.ProcessArgumentMatcher; + ++import javax.crypto.SecretKey; ++import javax.crypto.SecretKeyFactory; ++import javax.crypto.spec.PBEKeySpec; ++ + /* + * This class is the main class for the JMap utility. It parses its arguments + * and decides if the command should be satisfied using the VM attach mechanism +@@ -51,6 +58,10 @@ import sun.tools.common.ProcessArgumentMatcher; + * options are mapped to SA tools. + */ + public class JMap { ++ // encrypt ++ private static int SALT_MIN_LENGTH = 8; ++ private static int HASH_BIT_SIZE = 256; ++ private static int HASH_ITERATIONS_COUNT = 10000; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { +@@ -250,7 +261,7 @@ public class JMap { + } else if (subopt.startsWith("RedactClassPath")) { + redactParams.setRedactClassPath(subopt.substring("RedactClassPath=".length())); + } else if (subopt.startsWith("RedactPassword")) { +- redactPassword = getRedactPassword(); ++ redactPassword = getRedactPassword(pid); + } else { + System.err.println("Fail: invalid option: '" + subopt + "'"); + usage(1); +@@ -282,7 +293,7 @@ public class JMap { + } + } + +- private static String getRedactPassword() { ++ private static String getRedactPassword(String pid) { + String redactPassword = ",RedactPassword="; + // heap dump may need a password + Console console = System.console(); +@@ -300,42 +311,85 @@ public class JMap { + } + + String digestStr = null; +- byte[] passwordBytes = null; + try { + CharBuffer cb = CharBuffer.wrap(passwords); + String passwordPattern = "^[0-9a-zA-Z!@#$]{1,9}$"; + if(!Pattern.matches(passwordPattern, cb)) { + return redactPassword; + } +- Charset cs = Charset.forName("UTF-8"); +- passwordBytes= cs.encode(cb).array(); +- +- StringBuilder digestStrBuilder = new StringBuilder(); +- MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); +- byte[] digestBytes = messageDigest.digest(passwordBytes); +- for(byte b : digestBytes) { +- String hex = Integer.toHexString(0xff & b); +- if(hex.length() == 1) { +- digestStrBuilder.append('0'); +- } +- digestStrBuilder.append(hex); ++ ++ String salt = getSalt(pid); ++ if(salt == null) { ++ return redactPassword; ++ } ++ byte[] saltBytes = salt.getBytes("UTF-8"); ++ if(saltBytes.length < SALT_MIN_LENGTH) { ++ return redactPassword; + } +- digestStr = digestStrBuilder.toString(); ++ digestStr = getEncryptValue(passwords, saltBytes); + } catch (Exception e) { + }finally { + // clear all password + if(passwords != null) { + Arrays.fill(passwords, '0'); + } +- if(passwordBytes != null) { +- Arrays.fill(passwordBytes, (byte) 0); +- } + } + + redactPassword += (digestStr == null ? "" : digestStr); + return redactPassword; + } + ++ private static String getSalt(String pid) throws Exception { ++ String salt = null; ++ StringBuilder redactAuth = new StringBuilder(); ++ ++ VirtualMachine vm = VirtualMachine.attach(pid); ++ HotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm; ++ String flag = "RedactPassword"; ++ try (InputStream in = hvm.printFlag(flag); BufferedInputStream bis = new BufferedInputStream(in); ++ InputStreamReader isr = new InputStreamReader(bis, "UTF-8")) { ++ char c[] = new char[256]; ++ int n; ++ do { ++ n = isr.read(c); ++ ++ if (n > 0) { ++ redactAuth.append(n == c.length ? c : Arrays.copyOf(c, n)); ++ } ++ } while (n > 0); ++ } ++ vm.detach(); ++ ++ if(redactAuth.length() > 0) { ++ String[] auths = redactAuth.toString().split(","); ++ if(auths.length != 2) { ++ return salt; ++ } ++ return auths[1].trim(); ++ } ++ ++ return salt; ++ } ++ ++ private static String getEncryptValue(char[] passwordValue, byte[] saltBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { ++ StringBuilder digestStrBuilder = new StringBuilder(); ++ ++ KeySpec spec = new PBEKeySpec(passwordValue, saltBytes, HASH_ITERATIONS_COUNT, HASH_BIT_SIZE); ++ SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); ++ SecretKey secretKey = secretKeyFactory.generateSecret(spec); ++ byte[] digestBytes = secretKey.getEncoded(); ++ for (byte b : digestBytes) { ++ String hex = Integer.toHexString(0xff & b); ++ if (hex.length() == 1) { ++ digestStrBuilder.append('0'); ++ } ++ digestStrBuilder.append(hex); ++ } ++ String digestStr = digestStrBuilder.toString(); ++ ++ return digestStr; ++ } ++ + private static void checkForUnsupportedOptions(String[] args) { + // Check arguments for -F, -m, and non-numeric value + // and warn the user that SA is not supported anymore +-- +2.22.0 + diff --git a/openjdk-17.spec b/openjdk-17.spec index cf06608..62da65e 100644 --- a/openjdk-17.spec +++ b/openjdk-17.spec @@ -903,7 +903,7 @@ Provides: java-src%{?1} = %{epoch}:%{version}-%{release} Name: java-%{javaver}-%{origin} Version: %{newjavaver}.%{buildver} -Release: 2 +Release: 3 # java-1.5.0-ibm from jpackage.org set Epoch to 1 for unknown reasons # and this change was brought into RHEL-4. java-1.5.0-ibm packages @@ -1040,6 +1040,15 @@ Patch66: Backport-of-8337712-Wrong-javadoc-in-java.util.Date-.patch #17.0.13 Patch67: Huawei-Fix-JBooster-file-issue-caused-by-os-write-change.patch Patch68: downgrade-fcntl64-to-fcntl-on-linux.patch +Patch69: Add-jbolt-feature.patch +Patch70: Enable-TLS-to-communciation-between-JBooster-Server-.patch +Patch71: SA-redact-support-password.patch +Patch72: Add-specialized-hashmap-version-of-the-long-type.patch +Patch73: Implement-JBooster-RPC-byte-alignment.patch +Patch74: Optimize-LazyAOT-klasses-sending-strategy.patch +Patch75: Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch +Patch76: Add-Fix-clear-mark-for-NativeGotJump.patch +Patch77: 8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch ############################################ # # LoongArch64 specific patches @@ -1323,6 +1332,15 @@ pushd %{top_level_dir_name} %patch66 -p1 %patch67 -p1 %patch68 -p1 +%patch69 -p1 +%patch70 -p1 +%patch71 -p1 +%patch72 -p1 +%patch73 -p1 +%patch74 -p1 +%patch75 -p1 +%patch76 -p1 +%patch77 -p1 popd # openjdk %endif @@ -1889,6 +1907,18 @@ cjc.mainProgram(args) -- the returns from copy_jdk_configs.lua should not affect %changelog + +* Fri Nov 29 2024 kuenking111 - 1:17.0.13.11-3 +- add Add-jbolt-feature.patch +- add Enable-TLS-to-communciation-between-JBooster-Server-.patch +- add SA-redact-support-password.patch +- add Add-specialized-hashmap-version-of-the-long-type.patch +- add Implement-JBooster-RPC-byte-alignment.patch +- add Optimize-LazyAOT-klasses-sending-strategy.patch +- add Add-KAE-zip-GzipKAEBenchmark-Benchmark.patch +- add Add-Fix-clear-mark-for-NativeGotJump.patch +- add 8323066-TestSkipRebuildRemsetPhase.java-fails-with-S.patch + * Wed Nov 6 2024 Pan Xuefeng - 1:17.0.13.11-2 - upgrade LoongArch64 port to 17.0.13 -- Gitee