--- make/autoconf/jvm-features.m4 | 41 +- make/autoconf/spec.gmk.in | 1 + make/common/Modules.gmk | 5 + make/conf/module-loader-map.conf | 1 + make/data/hotspot-symbols/symbols-unix | 6 + make/hotspot/lib/JvmFeatures.gmk | 5 + .../visualstudio/hotspot/CreateVSProject.gmk | 1 + make/modules/jdk.jbooster/Launcher.gmk | 34 + make/modules/jdk.jbooster/Lib.gmk | 43 ++ .../linux/jbooster/net/clientStream_linux.cpp | 144 +++++ .../net/communicationStream_linux.cpp | 37 ++ .../net/serverListeningThread_linux.cpp | 157 +++++ .../jbooster/utilities/fileUtils_linux.cpp | 57 ++ .../share/classfile/systemDictionary.cpp | 16 + src/hotspot/share/classfile/vmSymbols.hpp | 6 + src/hotspot/share/include/jvm.h | 30 + .../jbooster/client/clientDaemonThread.cpp | 102 +++ .../jbooster/client/clientDaemonThread.hpp | 54 ++ .../jbooster/client/clientDataManager.cpp | 274 ++++++++ .../jbooster/client/clientDataManager.hpp | 127 ++++ .../jbooster/client/clientMessageHandler.cpp | 348 ++++++++++ .../jbooster/client/clientMessageHandler.hpp | 99 +++ .../jbooster/client/clientStartupSignal.cpp | 214 ++++++ .../jbooster/client/clientStartupSignal.hpp | 64 ++ .../share/jbooster/dataTransmissionUtils.cpp | 432 +++++++++++++ .../share/jbooster/dataTransmissionUtils.hpp | 247 +++++++ .../share/jbooster/jBoosterManager.cpp | 154 +++++ .../share/jbooster/jBoosterManager.hpp | 69 ++ .../share/jbooster/jBoosterSymbols.hpp | 33 + .../share/jbooster/jClientArguments.cpp | 405 ++++++++++++ .../share/jbooster/jClientArguments.hpp | 115 ++++ src/hotspot/share/jbooster/jClientVMFlags.cpp | 177 +++++ src/hotspot/share/jbooster/jClientVMFlags.hpp | 96 +++ .../share/jbooster/jbooster_globals.hpp | 125 ++++ src/hotspot/share/jbooster/lazyAot.cpp | 537 +++++++++++++++ src/hotspot/share/jbooster/lazyAot.hpp | 111 ++++ .../share/jbooster/net/clientStream.cpp | 267 ++++++++ .../share/jbooster/net/clientStream.hpp | 69 ++ .../jbooster/net/communicationStream.cpp | 183 ++++++ .../jbooster/net/communicationStream.hpp | 152 +++++ .../net/communicationStream.inline.hpp | 125 ++++ src/hotspot/share/jbooster/net/errorCode.cpp | 50 ++ src/hotspot/share/jbooster/net/errorCode.hpp | 79 +++ src/hotspot/share/jbooster/net/message.hpp | 89 +++ .../share/jbooster/net/message.inline.hpp | 92 +++ .../share/jbooster/net/messageBuffer.cpp | 61 ++ .../share/jbooster/net/messageBuffer.hpp | 114 ++++ .../jbooster/net/messageBuffer.inline.hpp | 137 ++++ .../share/jbooster/net/messageType.cpp | 40 ++ .../share/jbooster/net/messageType.hpp | 81 +++ src/hotspot/share/jbooster/net/netCommon.hpp | 154 +++++ .../share/jbooster/net/rpcCompatibility.cpp | 73 +++ .../share/jbooster/net/rpcCompatibility.hpp | 50 ++ .../share/jbooster/net/serialization.cpp | 333 ++++++++++ .../share/jbooster/net/serialization.hpp | 187 ++++++ .../jbooster/net/serializationWrappers.cpp | 440 +++++++++++++ .../jbooster/net/serializationWrappers.hpp | 276 ++++++++ .../net/serializationWrappers.inline.hpp | 302 +++++++++ .../jbooster/net/serverListeningThread.cpp | 155 +++++ .../jbooster/net/serverListeningThread.hpp | 67 ++ .../share/jbooster/net/serverStream.cpp | 234 +++++++ .../share/jbooster/net/serverStream.hpp | 71 ++ .../jbooster/server/serverControlThread.cpp | 293 +++++++++ .../jbooster/server/serverControlThread.hpp | 102 +++ .../jbooster/server/serverDataManager.cpp | 610 ++++++++++++++++++ .../jbooster/server/serverDataManager.hpp | 428 ++++++++++++ .../jbooster/server/serverDataManagerLog.cpp | 145 +++++ .../jbooster/server/serverMessageHandler.cpp | 406 ++++++++++++ .../jbooster/server/serverMessageHandler.hpp | 75 +++ .../jbooster/utilities/concurrentHashMap.hpp | 164 +++++ .../utilities/concurrentHashMap.inline.hpp | 244 +++++++ .../share/jbooster/utilities/debugUtils.cpp | 41 ++ .../share/jbooster/utilities/debugUtils.hpp | 61 ++ .../jbooster/utilities/debugUtils.inline.hpp | 67 ++ .../share/jbooster/utilities/fileUtils.cpp | 178 +++++ .../share/jbooster/utilities/fileUtils.hpp | 76 +++ .../jbooster/utilities/scalarHashMap.hpp | 96 +++ .../utilities/scalarHashMap.inline.hpp | 72 +++ src/hotspot/share/logging/logTag.hpp | 4 + src/hotspot/share/memory/allocation.hpp | 1 + src/hotspot/share/oops/instanceKlass.cpp | 12 + src/hotspot/share/oops/instanceKlass.hpp | 10 + src/hotspot/share/oops/method.hpp | 15 + src/hotspot/share/oops/methodData.cpp | 155 ++++- src/hotspot/share/oops/methodData.hpp | 75 +++ src/hotspot/share/prims/jvm.cpp | 42 ++ src/hotspot/share/runtime/arguments.cpp | 53 ++ src/hotspot/share/runtime/arguments.hpp | 6 + src/hotspot/share/runtime/flags/allFlags.hpp | 15 +- src/hotspot/share/runtime/globals.hpp | 5 + src/hotspot/share/runtime/java.cpp | 13 +- src/hotspot/share/runtime/thread.cpp | 9 + .../share/utilities/concurrentHashTable.hpp | 4 + src/hotspot/share/utilities/hashtable.cpp | 3 + src/hotspot/share/utilities/hashtable.hpp | 2 +- src/hotspot/share/utilities/macros.hpp | 10 + src/hotspot/share/utilities/stringUtils.cpp | 63 ++ src/hotspot/share/utilities/stringUtils.hpp | 28 + src/java.base/share/classes/module-info.java | 1 + .../share/lib/security/default.policy | 9 + .../jbooster/JBoosterCompilationContext.java | 186 ++++++ .../jdk/vm/ci/jbooster/package-info.java | 27 + .../share/classes/module-info.java | 1 + .../share/classes/jdk/jbooster/Commands.java | 166 +++++ .../classes/jdk/jbooster/ConnectionPool.java | 73 +++ .../share/classes/jdk/jbooster/JBooster.java | 231 +++++++ .../jdk/jbooster/JBoosterClassLoader.java | 68 ++ .../JBoosterCompilationContextImpl.java | 168 +++++ .../share/classes/jdk/jbooster/Options.java | 282 ++++++++ .../jbooster/api/JBoosterStartupSignal.java | 163 +++++ .../share/classes/module-info.java | 36 ++ .../share/native/libjbooster/JBooster.c | 45 ++ .../libjbooster/JBoosterStartupSignal.c | 33 + test/hotspot/gtest/jbooster/test_net.cpp | 517 +++++++++++++++ test/hotspot/gtest/jbooster/test_util.cpp | 182 ++++++ test/jdk/tools/jbooster/JBoosterCmdTest.java | 133 ++++ test/jdk/tools/jbooster/JBoosterNetTest.java | 152 +++++ test/jdk/tools/jbooster/JBoosterTestBase.java | 201 ++++++ test/jdk/tools/jbooster/SimpleClient.java | 35 + test/jdk/tools/launcher/HelpFlagsTest.java | 1 + test/jdk/tools/launcher/VersionCheck.java | 2 + 121 files changed, 14530 insertions(+), 13 deletions(-) create mode 100644 make/modules/jdk.jbooster/Launcher.gmk create mode 100644 make/modules/jdk.jbooster/Lib.gmk create mode 100644 src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp create mode 100644 src/hotspot/os/linux/jbooster/net/communicationStream_linux.cpp create mode 100644 src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp create mode 100644 src/hotspot/os/linux/jbooster/utilities/fileUtils_linux.cpp create mode 100644 src/hotspot/share/jbooster/client/clientDaemonThread.cpp create mode 100644 src/hotspot/share/jbooster/client/clientDaemonThread.hpp create mode 100644 src/hotspot/share/jbooster/client/clientDataManager.cpp create mode 100644 src/hotspot/share/jbooster/client/clientDataManager.hpp create mode 100644 src/hotspot/share/jbooster/client/clientMessageHandler.cpp create mode 100644 src/hotspot/share/jbooster/client/clientMessageHandler.hpp create mode 100644 src/hotspot/share/jbooster/client/clientStartupSignal.cpp create mode 100644 src/hotspot/share/jbooster/client/clientStartupSignal.hpp create mode 100644 src/hotspot/share/jbooster/dataTransmissionUtils.cpp create mode 100644 src/hotspot/share/jbooster/dataTransmissionUtils.hpp create mode 100644 src/hotspot/share/jbooster/jBoosterManager.cpp create mode 100644 src/hotspot/share/jbooster/jBoosterManager.hpp create mode 100644 src/hotspot/share/jbooster/jBoosterSymbols.hpp create mode 100644 src/hotspot/share/jbooster/jClientArguments.cpp create mode 100644 src/hotspot/share/jbooster/jClientArguments.hpp create mode 100644 src/hotspot/share/jbooster/jClientVMFlags.cpp create mode 100644 src/hotspot/share/jbooster/jClientVMFlags.hpp create mode 100644 src/hotspot/share/jbooster/jbooster_globals.hpp create mode 100644 src/hotspot/share/jbooster/lazyAot.cpp create mode 100644 src/hotspot/share/jbooster/lazyAot.hpp create mode 100644 src/hotspot/share/jbooster/net/clientStream.cpp create mode 100644 src/hotspot/share/jbooster/net/clientStream.hpp create mode 100644 src/hotspot/share/jbooster/net/communicationStream.cpp create mode 100644 src/hotspot/share/jbooster/net/communicationStream.hpp create mode 100644 src/hotspot/share/jbooster/net/communicationStream.inline.hpp create mode 100644 src/hotspot/share/jbooster/net/errorCode.cpp create mode 100644 src/hotspot/share/jbooster/net/errorCode.hpp create mode 100644 src/hotspot/share/jbooster/net/message.hpp create mode 100644 src/hotspot/share/jbooster/net/message.inline.hpp create mode 100644 src/hotspot/share/jbooster/net/messageBuffer.cpp create mode 100644 src/hotspot/share/jbooster/net/messageBuffer.hpp create mode 100644 src/hotspot/share/jbooster/net/messageBuffer.inline.hpp create mode 100644 src/hotspot/share/jbooster/net/messageType.cpp create mode 100644 src/hotspot/share/jbooster/net/messageType.hpp create mode 100644 src/hotspot/share/jbooster/net/netCommon.hpp create mode 100644 src/hotspot/share/jbooster/net/rpcCompatibility.cpp create mode 100644 src/hotspot/share/jbooster/net/rpcCompatibility.hpp create mode 100644 src/hotspot/share/jbooster/net/serialization.cpp create mode 100644 src/hotspot/share/jbooster/net/serialization.hpp create mode 100644 src/hotspot/share/jbooster/net/serializationWrappers.cpp create mode 100644 src/hotspot/share/jbooster/net/serializationWrappers.hpp create mode 100644 src/hotspot/share/jbooster/net/serializationWrappers.inline.hpp create mode 100644 src/hotspot/share/jbooster/net/serverListeningThread.cpp create mode 100644 src/hotspot/share/jbooster/net/serverListeningThread.hpp create mode 100644 src/hotspot/share/jbooster/net/serverStream.cpp create mode 100644 src/hotspot/share/jbooster/net/serverStream.hpp create mode 100644 src/hotspot/share/jbooster/server/serverControlThread.cpp create mode 100644 src/hotspot/share/jbooster/server/serverControlThread.hpp create mode 100644 src/hotspot/share/jbooster/server/serverDataManager.cpp create mode 100644 src/hotspot/share/jbooster/server/serverDataManager.hpp create mode 100644 src/hotspot/share/jbooster/server/serverDataManagerLog.cpp create mode 100644 src/hotspot/share/jbooster/server/serverMessageHandler.cpp create mode 100644 src/hotspot/share/jbooster/server/serverMessageHandler.hpp create mode 100644 src/hotspot/share/jbooster/utilities/concurrentHashMap.hpp create mode 100644 src/hotspot/share/jbooster/utilities/concurrentHashMap.inline.hpp create mode 100644 src/hotspot/share/jbooster/utilities/debugUtils.cpp create mode 100644 src/hotspot/share/jbooster/utilities/debugUtils.hpp create mode 100644 src/hotspot/share/jbooster/utilities/debugUtils.inline.hpp create mode 100644 src/hotspot/share/jbooster/utilities/fileUtils.cpp create mode 100644 src/hotspot/share/jbooster/utilities/fileUtils.hpp create mode 100644 src/hotspot/share/jbooster/utilities/scalarHashMap.hpp create mode 100644 src/hotspot/share/jbooster/utilities/scalarHashMap.inline.hpp create mode 100644 src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java create mode 100644 src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/package-info.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/Commands.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/ConnectionPool.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterClassLoader.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/Options.java create mode 100644 src/jdk.jbooster/share/classes/jdk/jbooster/api/JBoosterStartupSignal.java create mode 100644 src/jdk.jbooster/share/classes/module-info.java create mode 100644 src/jdk.jbooster/share/native/libjbooster/JBooster.c create mode 100644 src/jdk.jbooster/share/native/libjbooster/JBoosterStartupSignal.c create mode 100644 test/hotspot/gtest/jbooster/test_net.cpp create mode 100644 test/hotspot/gtest/jbooster/test_util.cpp create mode 100644 test/jdk/tools/jbooster/JBoosterCmdTest.java create mode 100644 test/jdk/tools/jbooster/JBoosterNetTest.java create mode 100644 test/jdk/tools/jbooster/JBoosterTestBase.java create mode 100644 test/jdk/tools/jbooster/SimpleClient.java diff --git a/make/autoconf/jvm-features.m4 b/make/autoconf/jvm-features.m4 index aa99b037b..c6aab63f8 100644 --- a/make/autoconf/jvm-features.m4 +++ b/make/autoconf/jvm-features.m4 @@ -44,7 +44,7 @@ m4_define(jvm_features_valid, m4_normalize( \ ifdef([custom_jvm_features_valid], custom_jvm_features_valid) \ \ - cds compiler1 compiler2 dtrace epsilongc g1gc jfr jni-check \ + cds compiler1 compiler2 dtrace epsilongc g1gc jbooster jfr jni-check \ jvmci jvmti link-time-opt management minimal nmt opt-size parallelgc \ serialgc services shenandoahgc static-build vm-structs zero zgc \ )) @@ -61,6 +61,7 @@ m4_define(jvm_feature_desc_compiler2, [enable hotspot compiler C2]) m4_define(jvm_feature_desc_dtrace, [enable dtrace support]) m4_define(jvm_feature_desc_epsilongc, [include the epsilon (no-op) garbage collector]) m4_define(jvm_feature_desc_g1gc, [include the G1 garbage collector]) +m4_define(jvm_feature_desc_jbooster, [enable JBooster]) m4_define(jvm_feature_desc_jfr, [enable JDK Flight Recorder (JFR)]) m4_define(jvm_feature_desc_jni_check, [enable -Xcheck:jni support]) m4_define(jvm_feature_desc_jvmci, [enable JVM Compiler Interface (JVMCI)]) @@ -268,6 +269,22 @@ AC_DEFUN_ONCE([JVM_FEATURES_CHECK_DTRACE], ]) ]) +############################################################################### +# Check if the feature 'jbooster' is available on this platform. +# +AC_DEFUN_ONCE([JVM_FEATURES_CHECK_JBOOSTER], +[ + JVM_FEATURES_CHECK_AVAILABILITY(jbooster, [ + AC_MSG_CHECKING([if platform is supported by JBOOSTER]) + if test "x$OPENJDK_TARGET_CPU" = "xx86_64"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no, $OPENJDK_TARGET_CPU]) + AVAILABLE=false + fi + ]) +]) + ############################################################################### # Check if the feature 'jfr' is available on this platform. # @@ -405,6 +422,7 @@ AC_DEFUN_ONCE([JVM_FEATURES_PREPARE_PLATFORM], JVM_FEATURES_CHECK_CDS JVM_FEATURES_CHECK_DTRACE + JVM_FEATURES_CHECK_JBOOSTER JVM_FEATURES_CHECK_JFR JVM_FEATURES_CHECK_JVMCI JVM_FEATURES_CHECK_SHENANDOAHGC @@ -433,21 +451,21 @@ AC_DEFUN([JVM_FEATURES_PREPARE_VARIANT], # Check which features are unavailable for this JVM variant. # This means that is not possible to build these features for this variant. if test "x$variant" = "xminimal"; then - JVM_FEATURES_VARIANT_UNAVAILABLE="cds zero" + JVM_FEATURES_VARIANT_UNAVAILABLE="cds jbooster zero" elif test "x$variant" = "xcore"; then - JVM_FEATURES_VARIANT_UNAVAILABLE="cds minimal zero" + JVM_FEATURES_VARIANT_UNAVAILABLE="cds jbooster minimal zero" elif test "x$variant" = "xzero"; then JVM_FEATURES_VARIANT_UNAVAILABLE="cds compiler1 compiler2 \ - jvmci minimal zgc" + jbooster jvmci minimal zgc" else JVM_FEATURES_VARIANT_UNAVAILABLE="minimal zero" fi # Check which features should be off by default for this JVM variant. if test "x$variant" = "xclient"; then - JVM_FEATURES_VARIANT_FILTER="compiler2 jvmci link-time-opt opt-size" + JVM_FEATURES_VARIANT_FILTER="compiler2 jbooster jvmci link-time-opt opt-size" elif test "x$variant" = "xminimal"; then - JVM_FEATURES_VARIANT_FILTER="cds compiler2 dtrace epsilongc g1gc \ + JVM_FEATURES_VARIANT_FILTER="cds compiler2 dtrace epsilongc g1gc jbooster \ jfr jni-check jvmci jvmti management nmt parallelgc services \ shenandoahgc vm-structs zgc" if test "x$OPENJDK_TARGET_CPU" = xarm ; then @@ -458,12 +476,12 @@ AC_DEFUN([JVM_FEATURES_PREPARE_VARIANT], link-time-opt" fi elif test "x$variant" = "xcore"; then - JVM_FEATURES_VARIANT_FILTER="compiler1 compiler2 jvmci \ + JVM_FEATURES_VARIANT_FILTER="compiler1 compiler2 jbooster jvmci \ link-time-opt opt-size" elif test "x$variant" = "xzero"; then - JVM_FEATURES_VARIANT_FILTER="jfr link-time-opt opt-size" + JVM_FEATURES_VARIANT_FILTER="jbooster jfr link-time-opt opt-size" else - JVM_FEATURES_VARIANT_FILTER="link-time-opt opt-size" + JVM_FEATURES_VARIANT_FILTER="jbooster link-time-opt opt-size" fi ]) @@ -558,6 +576,9 @@ AC_DEFUN([JVM_FEATURES_VERIFY], if JVM_FEATURES_IS_ACTIVE(compiler2); then INCLUDE_COMPILER2="true" fi + if JVM_FEATURES_IS_ACTIVE(jbooster); then + INCLUDE_JBOOSTER="true" + fi # Verify that we have at least one gc selected (i.e., feature named "*gc"). if ! JVM_FEATURES_IS_ACTIVE(.*gc); then @@ -582,6 +603,7 @@ AC_DEFUN_ONCE([JVM_FEATURES_SETUP], ENABLE_CDS="true" INCLUDE_JVMCI="true" INCLUDE_COMPILER2="false" + INCLUDE_JBOOSTER="false" for variant in $JVM_VARIANTS; do # Figure out if any features are unavailable, or should be filtered out @@ -619,5 +641,6 @@ AC_DEFUN_ONCE([JVM_FEATURES_SETUP], AC_SUBST(INCLUDE_JVMCI) AC_SUBST(INCLUDE_COMPILER2) + AC_SUBST(INCLUDE_JBOOSTER) ]) diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in index 2fdda9655..b73bbfb2d 100644 --- a/make/autoconf/spec.gmk.in +++ b/make/autoconf/spec.gmk.in @@ -865,6 +865,7 @@ PNG_CFLAGS:=@PNG_CFLAGS@ INCLUDE_SA=@INCLUDE_SA@ INCLUDE_JVMCI=@INCLUDE_JVMCI@ INCLUDE_COMPILER2=@INCLUDE_COMPILER2@ +INCLUDE_JBOOSTER=@INCLUDE_JBOOSTER@ OS_VERSION_MAJOR:=@OS_VERSION_MAJOR@ OS_VERSION_MINOR:=@OS_VERSION_MINOR@ diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk index c3c6a1bf4..514f6a2a0 100644 --- a/make/common/Modules.gmk +++ b/make/common/Modules.gmk @@ -66,6 +66,11 @@ ifeq ($(INCLUDE_JVMCI), false) MODULES_FILTER += jdk.internal.vm.compiler.management endif +# Filter out jbooster specific modules if jbooster is disabled +ifeq ($(INCLUDE_JBOOSTER), false) + MODULES_FILTER += jdk.jbooster +endif + # jpackage is only on windows, macosx, and linux ifeq ($(call isTargetOs, windows macosx linux), false) MODULES_FILTER += jdk.jpackage diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf index f72d96123..051ca4dc8 100644 --- a/make/conf/module-loader-map.conf +++ b/make/conf/module-loader-map.conf @@ -82,6 +82,7 @@ PLATFORM_MODULES= \ jdk.crypto.ec \ jdk.dynalink \ jdk.httpserver \ + jdk.jbooster \ jdk.jsobject \ jdk.localedata \ jdk.naming.dns \ diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index b14c5338d..d731017ab 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -206,3 +206,9 @@ JVM_AddReadsModule JVM_DefineArchivedModules JVM_DefineModule JVM_SetBootLoaderUnnamedModule + +# JBooster related +JVM_JBoosterInitVM +JVM_JBoosterHandleConnection +JVM_JBoosterPrintStoredClientData +JVM_JBoosterStartupNativeCallback diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 1f16d0a91..0c83ec254 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -165,6 +165,11 @@ ifneq ($(call check-jvm-feature, jfr), true) JVM_EXCLUDE_FILES += compilerEvent.cpp endif +ifneq ($(call check-jvm-feature, jbooster), true) + JVM_CFLAGS_FEATURES += -DINCLUDE_JBOOSTER=0 + JVM_EXCLUDE_PATTERNS += jbooster +endif + ################################################################################ ifeq ($(call check-jvm-feature, link-time-opt), true) diff --git a/make/ide/visualstudio/hotspot/CreateVSProject.gmk b/make/ide/visualstudio/hotspot/CreateVSProject.gmk index 3db28db63..0ce9f412b 100644 --- a/make/ide/visualstudio/hotspot/CreateVSProject.gmk +++ b/make/ide/visualstudio/hotspot/CreateVSProject.gmk @@ -113,6 +113,7 @@ ifeq ($(call isTargetOs, windows), true) -hidePath .jcheck \ -hidePath jdk.hotspot.agent \ -hidePath jdk.internal.vm.ci \ + -hidePath jdk.jbooster \ -hidePath jdk.jfr \ -compiler VC10 \ -jdkTargetRoot $(call FixPath, $(JDK_OUTPUTDIR)) \ diff --git a/make/modules/jdk.jbooster/Launcher.gmk b/make/modules/jdk.jbooster/Launcher.gmk new file mode 100644 index 000000000..1b03ec55e --- /dev/null +++ b/make/modules/jdk.jbooster/Launcher.gmk @@ -0,0 +1,34 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +include LauncherCommon.gmk + +$(eval $(call SetupBuildLauncher, jbooster, \ + MAIN_CLASS := jdk.jbooster.Main, \ + JAVA_ARGS := \ + -XX:+UnlockExperimentalVMOptions \ + -XX:+AsJBooster \ + , \ +)) diff --git a/make/modules/jdk.jbooster/Lib.gmk b/make/modules/jdk.jbooster/Lib.gmk new file mode 100644 index 000000000..a57bff6d7 --- /dev/null +++ b/make/modules/jdk.jbooster/Lib.gmk @@ -0,0 +1,43 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +include LibCommon.gmk + +################################################################################ + +$(eval $(call SetupJdkLibrary, BUILD_LIBJBOOSTER, \ + NAME := jbooster, \ + OPTIMIZATION := LOW, \ + CFLAGS := $(CFLAGS_JDKLIB), \ + LDFLAGS := $(LDFLAGS_JDKLIB) \ + $(call SET_SHARED_LIBRARY_ORIGIN), \ + LIBS_unix := -ljvm, \ +)) + +$(BUILD_LIBJBOOSTER): $(call FindLib, java.base, java) + +TARGETS += $(BUILD_LIBJBOOSTER) + +################################################################################ diff --git a/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp b/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp new file mode 100644 index 000000000..6425ef0f2 --- /dev/null +++ b/src/hotspot/os/linux/jbooster/net/clientStream_linux.cpp @@ -0,0 +1,144 @@ +/* + * 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. + */ + +#include // for addrinfo +#include // for TCP_NODELAY +#include // for memset() +#include + +#include "jbooster/net/clientStream.hpp" +#include "jbooster/net/errorCode.hpp" +#include "logging/log.hpp" +#include "runtime/os.hpp" + +#define LOG_INNER(socket_fd, address, port, err_code, format, args...) \ + log_error(jbooster, rpc)(format ": socket=%d, address=\"%s:%s\", error=%s(\"%s\").", \ + ##args, socket_fd, address, port, \ + JBErr::err_name(err_code), JBErr::err_message(err_code)); \ + +static int resolve_address(addrinfo** res_addr, const char* address, const char* port) { + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = 0; + hints.ai_family = AF_INET; // IPv4 only + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; // any protocol + + int gai_err = getaddrinfo(address, port, &hints, res_addr); + if (gai_err != 0) { + if (gai_err == EAI_SYSTEM) { + LOG_INNER(-1, address, port, errno, "Failed to get addrinfo"); + } else { + log_error(jbooster, rpc)("Failed to get addrinfo: address=\"%s:%s\", gai_err=\"%s\".", + address, port, gai_strerror(gai_err)); + } + return -1; + } + return 0; +} + +/** + * Initializes the options of the client socket. + */ +static int init_socket_opts(int socket_fd, const char* address, const char* port, uint32_t timeout_ms) { +#define TRY_SETSOCKOPT(socket_fd, level, optname, optval) \ + if (setsockopt(socket_fd, level, optname, (void*)&optval, sizeof(optval)) < 0) { \ + LOG_INNER(socket_fd, address, port, errno, "Failed to set socket opt " #optname); \ + return -1; \ + } + + // enable keep alive + int val_keep_alive = 1; + TRY_SETSOCKOPT(socket_fd, SOL_SOCKET, SO_KEEPALIVE, val_keep_alive); + + // set linger after close() or shutdown() to 2 seconds + linger val_linger = { 1, 2 }; + TRY_SETSOCKOPT(socket_fd, SOL_SOCKET, SO_LINGER, val_linger); + + // set receiving and sending timeout to `timeout_ms` + timeval val_timeout = { timeout_ms / 1000 , (timeout_ms % 1000) * 1000 }; + TRY_SETSOCKOPT(socket_fd, SOL_SOCKET, SO_RCVTIMEO, val_timeout); + TRY_SETSOCKOPT(socket_fd, SOL_SOCKET, SO_SNDTIMEO, val_timeout); + + // disable Nagle to reduce latency + int val_no_delay = 1; + TRY_SETSOCKOPT(socket_fd, IPPROTO_TCP, TCP_NODELAY, val_no_delay); + + return 0; +#undef TRY_SETSOCKOPT +} + +int ClientStream::try_to_connect_once(int* res_fd, const char* address, const char* port, uint32_t timeout_ms) { + errno = 0; + + int res_err = 0; + int conn_fd = -1; + // addrinfo res of getaddrinfo() + addrinfo* res_addr = nullptr; + + do { + if (resolve_address(&res_addr, address, port) < 0) { + res_err = errno; + break; + } + + // open socket + addrinfo* addr_p; + for (addr_p = res_addr; addr_p; addr_p = addr_p->ai_next) { + conn_fd = socket(addr_p->ai_family, addr_p->ai_socktype, addr_p->ai_protocol); + if (conn_fd >= 0) break; + } + if (conn_fd < 0) { + res_err = errno; + LOG_INNER(conn_fd, address, port, res_err, "Failed to create the socket"); + break; + } + + // configure socket + if (init_socket_opts(conn_fd, address, port, timeout_ms) < 0) { + res_err = errno; + break; + } + + // connect socket + if (connect(conn_fd, addr_p->ai_addr, addr_p->ai_addrlen) < 0) { + res_err = errno; + LOG_INNER(conn_fd, address, port, res_err, "Failed to build the connection"); + break; + } + + // success + assert(errno == 0, "why errno=%s", JBErr::err_name(errno)); + freeaddrinfo(res_addr); + *res_fd = conn_fd; + return 0; + } while (false); + + // fail + errno = 0; + if (res_addr != nullptr) freeaddrinfo(res_addr); + if (conn_fd >= 0) close(conn_fd); + if (res_err == 0) res_err = JBErr::UNKNOWN; + *res_fd = -1; + return res_err; +} diff --git a/src/hotspot/os/linux/jbooster/net/communicationStream_linux.cpp b/src/hotspot/os/linux/jbooster/net/communicationStream_linux.cpp new file mode 100644 index 000000000..5ebe6c994 --- /dev/null +++ b/src/hotspot/os/linux/jbooster/net/communicationStream_linux.cpp @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include + +#include "jbooster/net/communicationStream.inline.hpp" + +bool CommunicationStream::set_read_write_timeout(uint32_t timeout_ms) { + timeval val_timeout = { timeout_ms / 1000 , (timeout_ms % 1000) * 1000 }; + if (setsockopt(_conn_fd, SOL_SOCKET, SO_RCVTIMEO, (void*) &val_timeout, sizeof(val_timeout)) < 0) { + return false; + } + if (setsockopt(_conn_fd, SOL_SOCKET, SO_SNDTIMEO, (void*) &val_timeout, sizeof(val_timeout)) < 0) { + 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 new file mode 100644 index 000000000..bc3eae5b0 --- /dev/null +++ b/src/hotspot/os/linux/jbooster/net/serverListeningThread_linux.cpp @@ -0,0 +1,157 @@ +/* + * 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. + */ + +#include // for addrinfo +#include +#include +#include + +#include "jbooster/net/errorCode.hpp" +#include "jbooster/net/serverListeningThread.hpp" +#include "logging/log.hpp" +#include "runtime/interfaceSupport.inline.hpp" + +static int init_server_socket_opts(int socket_fd) { + // enable reuse of address + int val_reuse_address = 1; + if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&val_reuse_address, sizeof(val_reuse_address)) < 0) { + log_error(jbooster, rpc)("Failed to set socket opt SO_REUSEADDR: %s.", strerror(errno)); + return -1; + } + + // enable keep alive + int val_keep_alive = 1; + if (setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&val_keep_alive, sizeof(val_keep_alive)) < 0) { + log_error(jbooster, rpc)("Failed to set socket opt SO_KEEPALIVE: %s.", strerror(errno)); + return -1; + } + + return 0; +} + +static int init_accepted_socket_opts(int conn_fd, uint32_t timeout_ms) { + // set receiving and sending timeout to `timeout_ms` + timeval val_timeout = { timeout_ms / 1000 , (timeout_ms % 1000) * 1000 }; + + if (setsockopt(conn_fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&val_timeout, sizeof(val_timeout)) < 0) { + log_error(jbooster, rpc)("Failed to set socket opt SO_RCVTIMEO: %s.", strerror(errno)); + return -1; + } + + if (setsockopt(conn_fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&val_timeout, sizeof(val_timeout)) < 0) { + log_error(jbooster, rpc)("Failed to set socket opt SO_SNDTIMEO: %s.", strerror(errno)); + return -1; + } + + return 0; +} + +/** + * Returns the socket file descriptor (or -1 if failed to open socket). + */ +static int bind_address(const char* address, uint16_t port) { + // open socket + int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // AF_INET for IPv4 only + if (socket_fd < 0) { + log_error(jbooster, rpc)("Failed to open a socket."); + return socket_fd; + } + + // configure socket + if (init_server_socket_opts(socket_fd) < 0) { + close(socket_fd); + return -1; + } + + // bind address and port + sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(port); + if (bind(socket_fd, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + log_error(jbooster, rpc)("Failed to bind the socket."); + close(socket_fd); + return -1; + } + + // listen + if (listen(socket_fd, SOMAXCONN) < 0) { + log_error(jbooster, rpc)("Failed to listen to the socket."); + close(socket_fd); + return -1; + } + + return socket_fd; +} + +/** + * Keep listening for client requests. + */ +int ServerListeningThread::run_listener(TRAPS) { + ThreadToNativeFromVM ttn(THREAD); + int server_fd = bind_address(_address, _port); + if (server_fd < 0) { + log_error(jbooster, rpc)("Failed to bind address '%s:%d'.", _address, (int) _port); + return EADDRINUSE; + } + + pollfd pfd = {0}; + pfd.fd = server_fd; + pfd.events = POLLIN; + + sockaddr_in acc_addr; + socklen_t acc_addrlen = sizeof(acc_addr); + + log_info(jbooster, rpc)("The JBooster server is serving on %s:%d.", _address, (int) _port); + while (!get_exit_flag()) { + int pcode = poll(&pfd, 1, 100); + if (pcode < 0) { + if (errno == EINTR) continue; + log_error(jbooster, rpc)("Failed to poll: %s.", strerror(errno)); + return errno; + } + else if (pcode == 0) continue; // timed out + else if (pfd.revents != POLLIN) { + log_error(jbooster, rpc)("Unexpected poll revent: %d.", pfd.revents); + return JBErr::UNKNOWN; + } + + 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)); + } + break; + } + if (init_accepted_socket_opts(conn_fd, _timeout_ms) < 0) break; + + handle_new_connection(conn_fd, THREAD); + } + } + + close(server_fd); + log_debug(jbooster, rpc)("The JBooster server listener thread stopped."); + return 0; +} diff --git a/src/hotspot/os/linux/jbooster/utilities/fileUtils_linux.cpp b/src/hotspot/os/linux/jbooster/utilities/fileUtils_linux.cpp new file mode 100644 index 000000000..0dd4ed523 --- /dev/null +++ b/src/hotspot/os/linux/jbooster/utilities/fileUtils_linux.cpp @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#include + +#include "jbooster/utilities/fileUtils.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/os.inline.hpp" + +FileUtils::ListDir::ListDir(const char* path): _path(path) { + _cur = nullptr; + _ds = os::opendir(path); +} + +FileUtils::ListDir::~ListDir() { + if (_ds != nullptr) os::closedir(_ds); +} + +bool FileUtils::ListDir::next() { + if (_ds == nullptr) return false; + return (_cur = os::readdir(_ds)) != nullptr; +} + +const char* FileUtils::ListDir::name() { + assert(_cur != nullptr, "sanity"); + return _cur->d_name; +} + +bool FileUtils::ListDir::is_file() { + assert(_cur != nullptr, "sanity"); + return _cur->d_type == DT_REG; +} + +bool FileUtils::ListDir::is_dir() { + assert(_cur != nullptr, "sanity"); + return _cur->d_type == DT_DIR; +} diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 96fbf375b..7cdc25406 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -86,6 +86,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_JBOOSTER +#include "jbooster/client/clientStartupSignal.hpp" +#endif // INCLUDE_JBOOSTER ResolutionErrorTable* SystemDictionary::_resolution_errors = NULL; SymbolPropertyTable* SystemDictionary::_invoke_method_table = NULL; @@ -900,6 +903,19 @@ InstanceKlass* SystemDictionary::resolve_class_from_stream( // throw potential ClassFormatErrors. InstanceKlass* k = NULL; +#if INCLUDE_JBOOSTER + if (UseJBooster && JBoosterStartupSignal != nullptr) { + if (ClientStartupSignal::is_target_klass(class_name)) { + unsigned char* ptr = const_cast(st->buffer()); + int len = st->length(); + bool success = ClientStartupSignal::try_inject_startup_callback(&ptr, &len, THREAD); + if (success) { + st = new ClassFileStream(ptr, len, st->source(), st->need_verify()); + } + } + } +#endif // INCLUDE_JBOOSTER + #if INCLUDE_CDS if (!DumpSharedSpaces) { k = SystemDictionaryShared::lookup_from_stream(class_name, diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 79a6991b1..6a1e5cf6c 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -31,6 +31,9 @@ #include "oops/symbol.hpp" #include "utilities/macros.hpp" #include "utilities/enumIterator.hpp" +#if INCLUDE_JBOOSTER +#include "jbooster/jBoosterSymbols.hpp" +#endif // INCLUDE_JBOOSTER // The class vmSymbols is a name space for fast lookup of // symbols commonly used in the VM. @@ -708,6 +711,9 @@ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_void_signature, "(Ljava/net/URL;)V") \ \ + /* JBooster */ \ + JBOOSTER_ONLY(JBOOSTER_TEMPLATES(template)) \ + \ /*end*/ // enum for figuring positions and size of Symbol::_vm_symbols[] diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index c38684cee..49c932aec 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1096,6 +1096,36 @@ JVM_GetTemporaryDirectory(JNIEnv *env); JNIEXPORT jobjectArray JNICALL JVM_GetEnclosingMethodInfo(JNIEnv* env, jclass ofClass); + +/************************************************************************* + JBooster Support + ************************************************************************/ + +/** + * 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); + +/** + * Handle a TCP connection. + */ +JNIEXPORT void JNICALL +JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd); + +/** + * Print data in ServerDataManager. + */ +JNIEXPORT void JNICALL +JVM_JBoosterPrintStoredClientData(JNIEnv *env, jboolean print_all); + +/** + * Callback of startup signal. + */ +JNIEXPORT void JNICALL +JVM_JBoosterStartupNativeCallback(JNIEnv *env); + + /* * This structure is used by the launcher to get the default thread * stack size from the VM using JNI_GetDefaultJavaVMInitArgs() with a diff --git a/src/hotspot/share/jbooster/client/clientDaemonThread.cpp b/src/hotspot/share/jbooster/client/clientDaemonThread.cpp new file mode 100644 index 000000000..e92205c1e --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientDaemonThread.cpp @@ -0,0 +1,102 @@ +/* + * 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. + */ + +#include "classfile/javaClasses.inline.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/client/clientDaemonThread.hpp" +#include "jbooster/client/clientMessageHandler.hpp" +#include "jbooster/net/clientStream.hpp" +#include "logging/log.hpp" +#include "runtime/globals.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/thread.inline.hpp" + +JavaThread* ClientDaemonThread::_the_java_thread = nullptr; + +void ClientDaemonThread::start_thread(TRAPS) { + JavaThread* new_thread = new JavaThread(&client_daemon_thread_entry); + JavaThread::vm_exit_on_osthread_failure(new_thread); + guarantee(_the_java_thread == nullptr, "sanity"); + _the_java_thread = new_thread; + + Handle string = java_lang_String::create_from_str("JBooster Client Daemon", 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); + + Klass* group = vmClasses::ThreadGroup_klass(); + JavaValue result(T_VOID); + JavaCalls::call_special(&result, + thread_group, + group, + vmSymbols::add_method_name(), + vmSymbols::thread_void_signature(), + thread_oop, + THREAD); + + JavaThread::start_internal_daemon(THREAD, new_thread, thread_oop, MinPriority); +} + +void ClientDaemonThread::daemon_run(TRAPS) { + ThreadToNativeFromVM ttn(THREAD); + + ClientStream cs(JBoosterAddress, JBoosterPort, JBoosterManager::heartbeat_timeout(), THREAD); + JB_TRY { + JB_THROW(cs.connect_and_init_stream()); + JB_THROW(cs.send_request(MessageType::ClientDaemonTask)); + + while (true) { + JB_THROW(daemon_loop(&cs, THREAD)); + } + } JB_TRY_END + JB_CATCH_REST() { + log_warning(jbooster)("Heartbeat timeout. The server seems to be dead."); + } JB_CATCH_END; +} + +int ClientDaemonThread::daemon_loop(ClientStream* client_stream, TRAPS) { + MessageType type; + JB_RETURN(client_stream->recv_request(type)); + switch (type) { + case MessageType::Heartbeat: + JB_RETURN(handle_heartbeat(client_stream)); + break; + default: + guarantee(false, "handle it"); + } + return 0; +} + +int ClientDaemonThread::handle_heartbeat(ClientStream* client_stream) { + int magic; + JB_RETURN(client_stream->parse_request(&magic)); + JB_RETURN(client_stream->send_response(&magic)); + return 0; +} diff --git a/src/hotspot/share/jbooster/client/clientDaemonThread.hpp b/src/hotspot/share/jbooster/client/clientDaemonThread.hpp new file mode 100644 index 000000000..f35f5017e --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientDaemonThread.hpp @@ -0,0 +1,54 @@ +/* + * 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 SHARE_JBOOSTER_CLIENT_CLIENTDAEMONTHREAD_HPP +#define SHARE_JBOOSTER_CLIENT_CLIENTDAEMONTHREAD_HPP + +#include "jbooster/jBoosterManager.hpp" +#include "runtime/thread.hpp" + +class ClientStream; + +/** + * Goals of this thread: + * - Send keep-alive packages to the server; + * - Send bytecode of the newly loaded klasses to the server; + * + * This thread is simple, so all static methods are used. + */ +class ClientDaemonThread: public AllStatic { +private: + static JavaThread* _the_java_thread; + + static void client_daemon_thread_entry(JavaThread* thread, TRAPS) { daemon_run(thread); } + + static void daemon_run(TRAPS); + static int daemon_loop(ClientStream* client_stream, TRAPS); + + static int handle_heartbeat(ClientStream* client_stream); + +public: + static void start_thread(TRAPS); +}; + +#endif // SHARE_JBOOSTER_CLIENT_CLIENTDAEMONTHREAD_HPP diff --git a/src/hotspot/share/jbooster/client/clientDataManager.cpp b/src/hotspot/share/jbooster/client/clientDataManager.cpp new file mode 100644 index 000000000..6326b6e8d --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientDataManager.cpp @@ -0,0 +1,274 @@ +/* + * 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. + */ + +#include "jbooster/client/clientDaemonThread.hpp" +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/client/clientStartupSignal.hpp" +#include "jbooster/net/clientStream.hpp" +#include "logging/log.hpp" +#include "runtime/arguments.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/java.hpp" +#include "runtime/timerTrace.hpp" +#include "utilities/formatBuffer.hpp" + +ClientDataManager* ClientDataManager::_singleton = nullptr; + +ClientDataManager::ClientDataManager() { + _random_id = JBoosterManager::calc_random_id(); + _startup_end = false; + _program_args = nullptr; + _program_str_id = nullptr; + _cache_dir_path = nullptr; + + _allow_aot = false; + _allow_cds = false; + _allow_clr = false; + + _using_aot = false; + _using_cds = false; + _using_clr = false; + + _cache_clr_path = nullptr; + _cache_cds_path = nullptr; + _cache_aot_path = nullptr; + + _session_id = 0; + _program_id = 0; + _server_random_id = 0; + _server_available = false; +} + +static bool is_option_on(const char* flag_name, const char* options, const char* option) { + const char* start = strstr(options, option); + if (start == nullptr) return false; + if (start == options) return true; + if (*(start - 1) == '+') return true; + if (*(start - 1) == '-') return false; + vm_exit_during_initialization(err_msg("Failed to parse \"%s\" in \"%s\" of %s.", option, options, flag_name)); + return false; +} + +void ClientDataManager::init_client_vm_options() { + // manage boost packages + if (!FLAG_IS_DEFAULT(BoostStopAtLevel) && !FLAG_IS_DEFAULT(UseBoostPackages)) { + vm_exit_during_initialization("Either BoostStopAtLevel or UseBoostPackages can be set."); + } + + if (!FLAG_IS_DEFAULT(UseBoostPackages)) { + if (strcmp(UseBoostPackages, "all") == 0) { + _allow_clr = _allow_cds = _allow_aot = _allow_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; + } + } else { + switch (BoostStopAtLevel) { + case 4: _allow_pgo = true; + case 3: _allow_aot = true; + case 2: _allow_cds = true; + case 1: _allow_clr = true; + case 0: break; + default: break; + } + } + + if (JBoosterStartupSignal != nullptr) { + ClientStartupSignal::init_phase1(); + } +} + +void ClientDataManager::init_const() { + _cache_dir_path = JBoosterCachePath; + _program_args = new JClientArguments(true); + _program_str_id = JBoosterManager::calc_program_string_id(_program_args->program_name(), + _program_args->program_entry(), + _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"); + _cache_aot_path = JBoosterManager::calc_cache_path(_cache_dir_path, _program_str_id, "aot.so"); +} + +void ClientDataManager::init_client_duty() { + // 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); +} + +void ClientDataManager::init_client_duty_under_local_mode() { + set_server_available(false); + _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; + return JNI_OK; +} + +jint ClientDataManager::init_cds_options() { + if (!is_cds_allowed()) return JNI_OK; + return JNI_OK; +} + +jint ClientDataManager::init_aot_options() { + if (!is_aot_allowed()) return JNI_OK; + return JNI_OK; +} + +jint ClientDataManager::init_pgo_options() { + if (!is_pgo_allowed()) return JNI_OK; + return JNI_OK; +} + +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())); +} + +static void check_jbooster_port() { + if (JBoosterPort == nullptr) { + vm_exit_during_initialization("JBoosterPort is not set!"); + } + + int len = strlen(JBoosterPort); + if (len > 1 && JBoosterPort[0] == '0') { + vm_exit_during_initialization( + err_msg("JBoosterPort \"%s\" should have no leading zero.", JBoosterPort) + ); + } + for (int i = 0; i < len; ++i) { + if (!isdigit(JBoosterPort[i])) { + vm_exit_during_initialization( + err_msg("JBoosterPort \"%s\" should contain only digits.", JBoosterPort) + ); + } + } + + julong v; + if (!Arguments::atojulong(JBoosterPort, &v)) { + vm_exit_during_initialization( + err_msg("JBoosterPort \"%s\" is not a unsigned integer.", JBoosterPort) + ); + } + uint uint_v = (uint) v; + if (uint_v < 1024u || uint_v > 65535u) { + vm_exit_during_initialization( + err_msg("JBoosterPort \"%s\" is outside the allowed range [1024, 65535].", JBoosterPort) + ); + } +} + +static void check_cache_path() { + if (JBoosterCachePath == nullptr) { + FLAG_SET_DEFAULT(JBoosterCachePath, JBoosterManager::calc_cache_dir_path(true)); + } + FileUtils::mkdirs(JBoosterCachePath); +} + +jint ClientDataManager::init_phase1() { + JBoosterManager::client_only(); + + if (Arguments::java_command() == nullptr) { + if (FLAG_SET_CMDLINE(UseJBooster, false) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } + return JNI_OK; + } + + if (JBoosterLocalMode) { + if (JBoosterPort != nullptr) { + vm_exit_during_initialization("Either JBoosterLocalMode or JBoosterPort can be set."); + } + + if (JBoosterStartupSignal != nullptr) { + log_warning(jbooster)("JBoosterStartupSignal will not be used under local mode."); + if (FLAG_SET_CMDLINE(JBoosterStartupSignal, nullptr) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } + } + } else { + check_jbooster_port(); + } + check_cache_path(); + + _singleton = new ClientDataManager(); + jint jni_err = JNI_OK; + + TraceTime tt("Init cache", TRACETIME_LOG(Info, jbooster)); + _singleton->init_client_vm_options(); + _singleton->init_const(); + if (!JBoosterLocalMode) { + _singleton->init_client_duty(); + } else { + _singleton->init_client_duty_under_local_mode(); + } + + jni_err = _singleton->init_clr_options(); + if (jni_err != JNI_OK) return jni_err; + jni_err = _singleton->init_cds_options(); + if (jni_err != JNI_OK) return jni_err; + jni_err = _singleton->init_aot_options(); + if (jni_err != JNI_OK) return jni_err; + jni_err = _singleton->init_pgo_options(); + if (jni_err != JNI_OK) return jni_err; + + _singleton->print_init_state(); + if (!_singleton->is_server_available()) return escape(); + return JNI_OK; +} + +void ClientDataManager::init_phase2(TRAPS) { + JBoosterManager::client_only(); + if (JBoosterStartupSignal != nullptr) { + ClientStartupSignal::init_phase2(); + } + ClientDaemonThread::start_thread(CHECK); +} + +jint ClientDataManager::escape() { + if (FLAG_SET_CMDLINE(UseJBooster, false) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } + + 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 new file mode 100644 index 000000000..0a778a07e --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientDataManager.hpp @@ -0,0 +1,127 @@ +/* + * 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 SHARE_JBOOSTER_CLIENT_CLIENTDATAMANAGER_HPP +#define SHARE_JBOOSTER_CLIENT_CLIENTDATAMANAGER_HPP + +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/jClientArguments.hpp" +#include "jbooster/utilities/fileUtils.hpp" + +class ClientDataManager: public CHeapObj { + friend class ClientStream; + + static ClientDataManager* _singleton; + + uint64_t _random_id; + bool _startup_end; + JClientArguments* _program_args; + const char* _program_str_id; + const char* _cache_dir_path; + + bool _allow_clr; + bool _allow_cds; + bool _allow_aot; + + bool _using_clr; + bool _using_cds; + bool _using_aot; + + const char* _cache_clr_path; + const char* _cache_cds_path; + const char* _cache_aot_path; + + uint32_t _session_id; + uint32_t _program_id; + uint64_t _server_random_id; + bool _server_available; + +private: + NONCOPYABLE(ClientDataManager); + + ClientDataManager(); + + void init_client_vm_options(); + void init_const(); + void init_client_duty(); + void init_client_duty_under_local_mode(); + jint init_clr_options(); + jint init_cds_options(); + jint init_aot_options(); + jint init_pgo_options(); + void print_init_state(); + +protected: + void set_session_id(uint32_t sid) { _session_id = sid; } + void set_program_id(uint32_t pid) { _program_id = pid; } + void set_server_random_id(uint64_t id) { _server_random_id = id; } + +public: + static ClientDataManager& get() { + JBoosterManager::client_only(); + assert(_singleton != nullptr, "sanity"); + return *_singleton; + } + + static jint init_phase1(); + static void init_phase2(TRAPS); + + // Escape to the original path without jbooster if the server cannot be connected. + static jint escape(); + + uint64_t random_id() { return _random_id; } + + bool is_startup_end() { return _startup_end; } + void set_startup_end() { _startup_end = true; } + + JClientArguments* program_args() { return _program_args; } + const char* program_str_id() { return _program_str_id; } + + // $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_clr_being_used() { return _using_clr; } + bool is_cds_being_used() { return _using_cds; } + bool is_aot_being_used() { return _using_aot; } + + // /client/cache--clr.log + const char* cache_clr_path() { return _cache_clr_path; } + // /client/cache--cds.jsa + const char* cache_cds_path() { return _cache_cds_path; } + // /client/cache--aot.so + const char* cache_aot_path() { return _cache_aot_path; } + + uint32_t session_id() { return _session_id; } + uint32_t program_id() { return _program_id; } + + uint64_t server_random_id() { return _server_random_id; } + + bool is_server_available() { return _server_available; } + void set_server_available(bool avl) { _server_available = avl; } +}; + +#endif // SHARE_JBOOSTER_CLIENT_CLIENTDATAMANAGER_HPP diff --git a/src/hotspot/share/jbooster/client/clientMessageHandler.cpp b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp new file mode 100644 index 000000000..0ebe350a0 --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientMessageHandler.cpp @@ -0,0 +1,348 @@ +/* + * 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. + */ + +#include "cds/dynamicArchive.hpp" +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/systemDictionary.hpp" +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/client/clientMessageHandler.hpp" +#include "jbooster/dataTransmissionUtils.hpp" +#include "jbooster/lazyAot.hpp" +#include "jbooster/net/clientStream.hpp" +#include "jbooster/net/serializationWrappers.inline.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "runtime/arguments.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/timerTrace.hpp" +#include "utilities/growableArray.hpp" + +ClientMessageHandler::ClientMessageHandler(ClientStream* client_stream): + _client_stream(client_stream), + _no_more_server_message(false) { + // Make sure all the members are trivial. + memset(&_clent_message_ctx, 0, sizeof(_clent_message_ctx)); +} + +int ClientMessageHandler::handle_a_message_from_server(MessageType msg_type) { + DebugUtils::assert_thread_in_native(); + + _no_more_server_message = false; + switch (msg_type) { +#define MESSAGE_TYPE_CASE(MT) \ + case MessageType::MT: \ + JB_RETURN(handle_##MT()); \ + break; \ + + MESSAGES_FOR_CLIENT_TO_HANDLE(MESSAGE_TYPE_CASE) +#undef MESSAGE_TYPE_CASE + default: + JB_RETURN(JBErr::BAD_MSG_TYPE); + break; + } + return 0; +} + +int ClientMessageHandler::handle_messages_from_server() { + DebugUtils::assert_thread_in_native(); + + MessageType msg_type; + do { + JB_RETURN(cs().recv_request(msg_type)); + JB_RETURN(handle_a_message_from_server(msg_type)); + } while (!_no_more_server_message); + return 0; +} + +void ClientMessageHandler::trigger_cache_generation_tasks(TriggerTaskPhase phase, TRAPS) { + DebugUtils::assert_thread_in_native(); + + ClientStream cs(JBoosterAddress, JBoosterPort, JBoosterTimeout, THREAD); + ClientStream* client_stream = &cs; + + JB_TRY { + JB_THROW(client_stream->connect_and_init_stream()); + ClientMessageHandler msg_handler(client_stream); + + bool cache_file_task = false; + if (ClientDataManager::get().is_clr_allowed() || ClientDataManager::get().is_cds_allowed()) { + cache_file_task = (phase == ON_SHUTDOWN); + } + if (cache_file_task) { + JB_THROW(msg_handler.send_cache_file_sync_task()); + } + + bool lazy_aot_task = false; + if (ClientDataManager::get().is_aot_allowed()) { + lazy_aot_task = ((phase == ON_STARTUP) || ((phase == ON_SHUTDOWN) && !ClientDataManager::get().is_startup_end())); + } + if (lazy_aot_task) { + JB_THROW(msg_handler.send_lazy_aot_compilation_task()); + } + } JB_TRY_END + JB_CATCH_REST() { + client_stream->LOG_OR_CRASH(); + } JB_CATCH_END; + + if (HAS_PENDING_EXCEPTION) { + LogTarget(Warning, jbooster) lt; + if (lt.is_enabled()) { + lt.print("Unhandled exception at ClientMessageHandler::trigger_cache_generation_tasks()!"); + } + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } +} + +// ------------------------------- Message Handlers -------------------------------- + +int ClientMessageHandler::handle_EndOfCurrentPhase() { + _no_more_server_message = true; + return 0; +} + +static void dump_cds() { + JavaThread* THREAD = JavaThread::current(); + TraceTime tt("Duration CDS", TRACETIME_LOG(Info, jbooster)); + ThreadInVMfromNative tivm(THREAD); + ExceptionMark em(THREAD); + + const char* cds_path = ClientDataManager::get().cache_cds_path(); + // SharedDynamicArchivePath is ".tmp" here. + const char* tmp_cds_path = Arguments::GetSharedDynamicArchivePath(); + + // Try to create the tmp file (get the file lock) before DynamicArchive::dump(). + // Do not try to create the tmp file in FileMapInfo::open_for_write() because if + // the file fails to be created, the whole process will exit (see + // FileMapInfo::fail_stop()). + int fd = JBoosterManager::create_and_open_tmp_cache_file(tmp_cds_path); + if (fd < 0) { + log_error(jbooster, cds)("Failed to open the tmp cds dump file \"%s\". Skip dump. " + "Why is some other process dumping?", + tmp_cds_path); + return; + } + os::close(fd); + fd = -1; + + bool rename_successful = false; + // Skip dump if some other process already dumped. + if (!FileUtils::is_file(cds_path)) { + DynamicArchive::dump(); + chmod(tmp_cds_path, S_IREAD); + rename_successful = FileUtils::rename(tmp_cds_path, cds_path); + } else { + log_warning(jbooster, cds)("The cds jsa file \"%s\" already exists. Skip dump.", cds_path); + } + + if (!rename_successful) { + FileUtils::remove(tmp_cds_path); + } + + if (HAS_PENDING_EXCEPTION) { + LogTarget(Error, jbooster, cds) lt; + if (lt.is_enabled()) { + lt.print("ArchiveClassesAtExit has failed:"); + } + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } +} + +int ClientMessageHandler::handle_CacheAggressiveCDS() { + if (DynamicDumpSharedSpaces && ClientDataManager::get().is_cds_allowed()) { + dump_cds(); + } + FileWrapper file(ClientDataManager::get().cache_cds_path(), + SerializationMode::SERIALIZE); + JB_RETURN(file.send_file(&cs())); + return 0; +} + +int ClientMessageHandler::handle_CacheClassLoaderResource() { + FileWrapper file(ClientDataManager::get().cache_clr_path(), + SerializationMode::SERIALIZE); + JB_RETURN(file.send_file(&cs())); + return 0; +} + +int ClientMessageHandler::handle_ClassLoaderLocators() { + GrowableArray clls; + GrowableArray* all_loaders = ctx().all_class_loaders; + for (GrowableArrayIterator iter = all_loaders->begin(); + iter != all_loaders->end(); + ++iter) { + clls.append(ClassLoaderLocator(*iter)); + } + ArrayWrapper cll_aw(&clls); + JB_RETURN(cs().send_response(&cll_aw)); + return 0; +} + +int ClientMessageHandler::handle_DataOfClassLoaders() { + ArrayWrapper cll_aw(false); + JB_RETURN(cs().parse_request(&cll_aw)); + ClassLoaderLocator* loader_locators = cll_aw.get_array(); + GrowableArray chains; + for (int i = 0; i < cll_aw.size(); ++i) { + ClassLoaderData* data = loader_locators[i].class_loader_data(); + chains.append(ClassLoaderChain(data)); + } + ArrayWrapper aw(&chains); + JB_RETURN(cs().send_response(&aw)); + return 0; +} + +int ClientMessageHandler::handle_KlassLocators() { + GrowableArray kls; + GrowableArray* klasses = ctx().all_sorted_klasses; + for (GrowableArrayIterator iter = klasses->begin(); + iter != klasses->end(); + ++iter) { + kls.append(KlassLocator(*iter)); + } + ArrayWrapper kl_aw(&kls); + JB_RETURN(cs().send_response(&kl_aw)); + return 0; +} + +int ClientMessageHandler::handle_DataOfKlasses() { + Thread* thread = Thread::current(); + ArrayWrapper klasses_aw(false); + JB_RETURN(cs().parse_request(&klasses_aw)); + KlassLocator* klass_locators = klasses_aw.get_array(); + ResourceMark rm; + InstanceKlass** klasses = NEW_RESOURCE_ARRAY(InstanceKlass*, klasses_aw.size()); + for (int i = 0; i < klasses_aw.size(); ++i) { + KlassLocator& w = klass_locators[i]; + Handle loader(thread, w.class_loader_locator().class_loader_data()->class_loader()); + Klass* k = SystemDictionary::find_instance_klass(w.class_name(), loader, Handle()); + if (k == nullptr) { + klasses[i] = nullptr; + ResourceMark rm; + log_warning(jbooster, compilation)("Unresolved class \"%s\".", + w.class_name()->as_C_string()); + } else { + klasses[i] = InstanceKlass::cast(k); + } + } + ArrayWrapper klass_array(klasses, klasses_aw.size()); + JB_RETURN(cs().send_response(&klass_array)); + return 0; +} + +int ClientMessageHandler::handle_MethodLocators() { + GrowableArray mls; + GrowableArray* methods; + bool to_compile; + JB_RETURN(cs().parse_request(&to_compile)); + if (to_compile) { + methods = ctx().methods_to_compile; + } else { + methods = ctx().methods_not_compile; + } + for (GrowableArrayIterator iter = methods->begin(); + iter != methods->end(); + ++iter) { + mls.append(MethodLocator(*iter)); + } + ArrayWrapper ml_aw(&mls); + JB_RETURN(cs().send_response(&ml_aw)); + return 0; +} + +int ClientMessageHandler::handle_Profilinginfo() { + ResourceMark rm; + ArrayWrapper aw(false); + JB_RETURN(cs().parse_request(&aw)); + uintptr_t* klass_array = aw.get_array(); + ProfileDataCollector data_collector(aw.size(), (InstanceKlass**) klass_array); + JB_RETURN(cs().send_response(&data_collector)); + return 0; +} + +int ClientMessageHandler::handle_ArrayKlasses() { + ResourceMark rm; + GrowableArray* array_klasses = ctx().array_klasses; + ArrayWrapper klass_array(array_klasses); + JB_RETURN(cs().send_response(&klass_array)); + return 0; +} + +// ---------------------------------- Some Tasks ----------------------------------- + +int ClientMessageHandler::send_cache_file_sync_task() { + DebugUtils::assert_thread_nonjava_or_in_native(); + JB_RETURN(cs().send_request(MessageType::CacheFilesSyncTask)); + JB_RETURN(handle_messages_from_server()); + return 0; +} + +int ClientMessageHandler::send_lazy_aot_compilation_task() { + DebugUtils::assert_thread_nonjava_or_in_native(); + JavaThread* THREAD = JavaThread::current(); + HandleMark hm(THREAD); + ResourceMark rm(THREAD); + + JB_RETURN(cs().send_request(MessageType::LazyAOTCompilationTask)); + bool should_send_klasses; + JB_RETURN(cs().recv_response(&should_send_klasses)); + if (!should_send_klasses) { + log_info(jbooster, compilation)("The server doesn't need klass data now."); + return 0; + } + + // Keep the class loaders alive until the end of this function. + ClassLoaderKeepAliveMark clka; + + GrowableArray all_loaders; + GrowableArray klasses_to_compile; + GrowableArray methods_to_compile; + GrowableArray methods_not_compile; + GrowableArray all_sorted_klasses; + GrowableArray array_klasses; + + { + TraceTime tt("Duration AOT", TRACETIME_LOG(Info, jbooster)); + LazyAOT::collect_all_klasses_to_compile(clka, + &all_loaders, + &klasses_to_compile, + &methods_to_compile, + &methods_not_compile, + &all_sorted_klasses, + &array_klasses, + CHECK_(JBErr::THREAD_EXCEPTION)); + } + + ctx().all_class_loaders = &all_loaders; + ctx().klasses_to_compile = &klasses_to_compile; + ctx().methods_to_compile = &methods_to_compile; + ctx().methods_not_compile = &methods_not_compile; + ctx().all_sorted_klasses = &all_sorted_klasses; + ctx().array_klasses = &array_klasses; + JB_RETURN(handle_messages_from_server()); + + log_info(jbooster, compilation)("All %d klasses have been sent to the server.", + all_sorted_klasses.length()); + return 0; +} diff --git a/src/hotspot/share/jbooster/client/clientMessageHandler.hpp b/src/hotspot/share/jbooster/client/clientMessageHandler.hpp new file mode 100644 index 000000000..66844247c --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientMessageHandler.hpp @@ -0,0 +1,99 @@ +/* + * 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 SHARE_JBOOSTER_CLIENT_CLIENTMESSAGEHANDLER_HPP +#define SHARE_JBOOSTER_CLIENT_CLIENTMESSAGEHANDLER_HPP + +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/messageType.hpp" + +#define MESSAGES_FOR_CLIENT_TO_HANDLE(F) \ + F(EndOfCurrentPhase ) \ + F(CacheAggressiveCDS ) \ + F(CacheClassLoaderResource) \ + F(ClassLoaderLocators ) \ + F(DataOfClassLoaders ) \ + F(KlassLocators ) \ + F(DataOfKlasses ) \ + F(MethodLocators ) \ + F(Profilinginfo ) \ + F(ArrayKlasses ) \ + +class ArrayKlass; +class ClassLoaderData; +class ClientStream; +template class GrowableArray; +class InstanceKlass; +class JavaThread; +class Method; + +/** + * Try not to use TRAPS here because these functions may be called on any thread. + */ +class ClientMessageHandler: public StackObj { +public: + enum TriggerTaskPhase { + ON_STARTUP, + ON_SHUTDOWN + }; + + /** + * All the members should be trivial. + */ + struct ClientMessageContext { + GrowableArray* all_class_loaders; + GrowableArray* klasses_to_compile; + GrowableArray* methods_to_compile; + GrowableArray* methods_not_compile; + GrowableArray* all_sorted_klasses; + GrowableArray* array_klasses; + }; + +private: + ClientStream* _client_stream; + ClientMessageContext _clent_message_ctx; + bool _no_more_server_message; + +private: + NONCOPYABLE(ClientMessageHandler); + +#define DECLARE_HANDLER(MT) int handle_##MT(); + MESSAGES_FOR_CLIENT_TO_HANDLE(DECLARE_HANDLER) +#undef DECLARE_HANDLER + +public: + ClientMessageHandler(ClientStream* client_stream); + + ClientStream& cs() { return *_client_stream; } + ClientMessageContext& ctx() { return _clent_message_ctx; } + + static void trigger_cache_generation_tasks(TriggerTaskPhase phase, TRAPS); + + int handle_a_message_from_server(MessageType msg_type); + int handle_messages_from_server(); + + int send_cache_file_sync_task(); + int send_lazy_aot_compilation_task(); +}; + +#endif // SHARE_JBOOSTER_CLIENT_CLIENTMESSAGEHANDLER_HPP diff --git a/src/hotspot/share/jbooster/client/clientStartupSignal.cpp b/src/hotspot/share/jbooster/client/clientStartupSignal.cpp new file mode 100644 index 000000000..65c3233f7 --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientStartupSignal.cpp @@ -0,0 +1,214 @@ +/* + * 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. + */ + +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "jbooster/client/clientStartupSignal.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "memory/oopFactory.hpp" +#include "oops/typeArrayOop.inline.hpp" +#include "runtime/arguments.hpp" +#include "runtime/atomic.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/java.hpp" +#include "runtime/javaCalls.hpp" +#include "utilities/stringUtils.hpp" + +volatile bool ClientStartupSignal::_startup_class_found = false; + +Symbol* ClientStartupSignal::_klass_name_sym = nullptr; + +const char* ClientStartupSignal::_klass_name = nullptr; +const char* ClientStartupSignal::_method_name = nullptr; +const char* ClientStartupSignal::_method_signature = nullptr; + +static void guarantee_good_format(bool good) { + if (good) return; + vm_exit_during_initialization(err_msg( + "Invalid format: JBoosterStartupSignal=\"%s\".", JBoosterStartupSignal)); +} + +/** + * JBoosterStartupSignal follow the format of Method::name_and_sig_as_C_string(): + * "java.lang.String.matches(Ljava/lang/String;)Z" + * This is also ok (but make sure that the method is not overloaded): + * "java.lang.String.matches" + */ +void ClientStartupSignal::init_x_flag() { + char* s = StringUtils::copy_to_heap(JBoosterStartupSignal, mtJBooster); + int len = strlen(s); + int ke = 0; // klass end + int lb = 0, rb = 0; + guarantee_good_format(len >= 5 && s[0] != '.' && s[0] != '/' && s[0] != '('); + for (int i = 0; i < len; ++i) { + switch (s[i]) { + case '.': s[i] = '/'; // no break here + case '/': if (lb == 0) ke = i; break; + case '(': + guarantee_good_format(lb == 0); + lb = i; + break; + case ')': + guarantee_good_format(lb != 0 && rb == 0); + rb = i; + break; + default: break; + } + } + + if (lb == 0) { + _method_signature = nullptr; + } else { + _method_signature = StringUtils::copy_to_heap(s + lb, mtJBooster); + s[lb] = '\0'; + } + _method_name = StringUtils::copy_to_heap(s + ke + 1, mtJBooster); + s[ke] = '\0'; + _klass_name = StringUtils::copy_to_heap(s, mtJBooster); + + StringUtils::free_from_heap(s); + + if (log_is_enabled(Trace, jbooster)) { + log_trace(jbooster)("startup_method_klass=%s", _klass_name); + log_trace(jbooster)("startup_method_method=%s", _method_name); + log_trace(jbooster)("startup_method_signature=%s", _method_signature); + } +} + +void ClientStartupSignal::init_d_flag() { + jint res = Arguments::init_jbooster_startup_signal_properties(_klass_name, + _method_name, + _method_signature); + if (res != JNI_OK) { + vm_exit_during_initialization("Failed to set -D flags for JBoosterStartupSignal"); + } +} + +void ClientStartupSignal::init_phase1() { + JBoosterManager::client_only(); + assert(JBoosterStartupSignal != nullptr, "sanity"); + init_x_flag(); + init_d_flag(); +} + +void ClientStartupSignal::init_phase2() { + JBoosterManager::client_only(); + assert(JBoosterStartupSignal != nullptr, "sanity"); + _klass_name_sym = SymbolTable::new_symbol(_klass_name); +} + +void ClientStartupSignal::free() { + StringUtils::free_from_heap(_klass_name); + StringUtils::free_from_heap(_method_name); + StringUtils::free_from_heap(_method_signature); + + if (_klass_name_sym != nullptr) { + _klass_name_sym->decrement_refcount(); + _klass_name_sym = nullptr; + } +} + +bool ClientStartupSignal::inject_startup_callback(unsigned char** data_ptr, int* data_len, TRAPS) { + int len = *data_len; + + // arg1 + typeArrayOop bytes_oop = oopFactory::new_byteArray(len, CHECK_false); + typeArrayHandle bytes_h(THREAD, bytes_oop); + const jbyte* src = reinterpret_cast(*data_ptr); + ArrayAccess<>::arraycopy_from_native(src, bytes_h(), typeArrayOopDesc::element_offset(0), len); + + // arg 2 + Handle method_name_h = java_lang_String::create_from_str(_method_name, CHECK_false); + + // arg 3 + Handle method_signature_h = _method_signature == nullptr + ? Handle() + : java_lang_String::create_from_str(_method_signature, CHECK_false); + + // wrap args + JavaValue result(T_ARRAY); + JavaCallArguments java_args; + java_args.push_oop(bytes_h); + java_args.push_oop(method_name_h); + java_args.push_oop(method_signature_h); + + // callee klass + TempNewSymbol ss_name = SymbolTable::new_symbol("jdk/jbooster/api/JBoosterStartupSignal"); + Klass* ss_k = SystemDictionary::resolve_or_null( + ss_name, + Handle(THREAD, SystemDictionary::java_platform_loader()), + Handle(), CHECK_false); + guarantee(ss_k != nullptr && ss_k->is_instance_klass(), "sanity"); + InstanceKlass* ss_ik = InstanceKlass::cast(ss_k); + + // call it + TempNewSymbol inject_method_name = SymbolTable::new_symbol("injectStartupCallback"); + TempNewSymbol inject_method_signature = SymbolTable::new_symbol("([BLjava/lang/String;Ljava/lang/String;)[B"); + JavaCalls::call_static(&result, + ss_ik, + inject_method_name, + inject_method_signature, + &java_args, CHECK_false); + typeArrayHandle res_bytes_h(THREAD, (typeArrayOop) result.get_oop()); + if (res_bytes_h.is_null()) return false; + + // copy java byte array to cpp + unsigned char* res_bytes_base = reinterpret_cast(res_bytes_h->byte_at_addr(0)); + int res_len = res_bytes_h->length(); + + unsigned char* res_bytes = NEW_RESOURCE_ARRAY(unsigned char, res_len); + memcpy(res_bytes, res_bytes_base, res_len); + + *data_ptr = res_bytes; + *data_len = res_len; + return true; +} + +bool ClientStartupSignal::try_inject_startup_callback(unsigned char** data_ptr, int* data_len, TRAPS) { + bool success = false; + if (Atomic::cmpxchg(&_startup_class_found, false, true) == /* prior value */ false) { + success = inject_startup_callback(data_ptr, data_len, THREAD); + if (HAS_PENDING_EXCEPTION) { + success = false; + } + } + + if (success) { + log_info(jbooster, startup)("Successfully injected startup callback to: klass=%s, method=%s%s.", + ClientStartupSignal::klass_name(), + ClientStartupSignal::method_name(), + ClientStartupSignal::method_signature()); + } else { + log_warning(jbooster, startup)("Failed to inject startup callback to: klass=%s, method=%s%s.", + ClientStartupSignal::klass_name(), + ClientStartupSignal::method_name(), + ClientStartupSignal::method_signature()); + if (HAS_PENDING_EXCEPTION) { + LogTarget(Warning, jbooster, startup) lt; + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } + } + + return success; +} diff --git a/src/hotspot/share/jbooster/client/clientStartupSignal.hpp b/src/hotspot/share/jbooster/client/clientStartupSignal.hpp new file mode 100644 index 000000000..7641edc41 --- /dev/null +++ b/src/hotspot/share/jbooster/client/clientStartupSignal.hpp @@ -0,0 +1,64 @@ +/* + * 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 SHARE_JBOOSTER_CLIENT_CLIENTSTARTUPSIGNAL_HPP +#define SHARE_JBOOSTER_CLIENT_CLIENTSTARTUPSIGNAL_HPP + +#include "jbooster/jBoosterManager.hpp" + +class InstanceKlass; +class Method; +class Symbol; + +/** + * This class is used to determine whether the current program is + * at the end of the startup phase. + */ +class ClientStartupSignal: public AllStatic { + static volatile bool _startup_class_found; + + static Symbol* _klass_name_sym; + + static const char* _klass_name; + static const char* _method_name; + static const char* _method_signature; + +private: + static void init_x_flag(); + static void init_d_flag(); + static bool inject_startup_callback(unsigned char** data_ptr, int* data_len, TRAPS); + +public: + static void init_phase1(); + static void init_phase2(); + void free(); + + static const char* klass_name() { return _klass_name; } + static const char* method_name() { return _method_name; } + static const char* method_signature() { return _method_signature; } + + static bool is_target_klass(Symbol* klass_name) { return _klass_name_sym == klass_name; } + static bool try_inject_startup_callback(unsigned char** data_ptr, int* data_len, TRAPS); +}; + +#endif // SHARE_JBOOSTER_CLIENT_CLIENTSTARTUPSIGNAL_HPP diff --git a/src/hotspot/share/jbooster/dataTransmissionUtils.cpp b/src/hotspot/share/jbooster/dataTransmissionUtils.cpp new file mode 100644 index 000000000..5d2235dca --- /dev/null +++ b/src/hotspot/share/jbooster/dataTransmissionUtils.cpp @@ -0,0 +1,432 @@ +/* + * 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. + */ + +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/dataTransmissionUtils.hpp" +#include "jbooster/net/serializationWrappers.inline.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "oops/methodData.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" + +// -------------------------------- ClassLoaderKey --------------------------------- + +ClassLoaderKey::ClassLoaderKey(const ClassLoaderKey& key): + _loader_class_name(key._loader_class_name), + _loader_name(key._loader_name), + _first_loaded_class_name(key._first_loaded_class_name) { + // change ref only for the server + if (AsJBooster) { + if (_loader_class_name != nullptr) { + _loader_class_name->increment_refcount(); + } + if (_loader_name != nullptr) { + _loader_name->increment_refcount(); + } + if (_first_loaded_class_name != nullptr) { + _first_loaded_class_name->increment_refcount(); + } + } +} + +ClassLoaderKey::~ClassLoaderKey() { + if (AsJBooster) { + if (_loader_class_name != nullptr) { + _loader_class_name->decrement_refcount(); + } + if (_loader_name != nullptr) { + _loader_name->decrement_refcount(); + } + if (_first_loaded_class_name != nullptr) { + _first_loaded_class_name->decrement_refcount(); + } + } +} + +ClassLoaderKey& ClassLoaderKey::operator = (const ClassLoaderKey& other) { + if (AsJBooster) { + if (_loader_class_name != nullptr) { + _loader_class_name->decrement_refcount(); + } + if (_loader_name != nullptr) { + _loader_name->decrement_refcount(); + } + if (_first_loaded_class_name != nullptr) { + _first_loaded_class_name->decrement_refcount(); + } + } + + _loader_class_name = other._loader_class_name; + _loader_name = other._loader_name; + _first_loaded_class_name = other._first_loaded_class_name; + + if (AsJBooster) { + if (_loader_class_name != nullptr) { + _loader_class_name->increment_refcount(); + } + if (_loader_name != nullptr) { + _loader_name->increment_refcount(); + } + if (_first_loaded_class_name != nullptr) { + _first_loaded_class_name->increment_refcount(); + } + } + return *this; +} + +ClassLoaderKey ClassLoaderKey::boot_loader_key() { + return ClassLoaderKey(nullptr, nullptr, nullptr); +} +bool ClassLoaderKey::is_boot_loader() const { + return _loader_class_name == nullptr + && _loader_name == nullptr + && _first_loaded_class_name == nullptr; +} +ClassLoaderKey ClassLoaderKey::platform_loader_key() { + return ClassLoaderKey(vmSymbols::jdk_internal_loader_ClassLoaders_PlatformClassLoader(), nullptr, nullptr); +} +bool ClassLoaderKey::is_platform_loader() const { + return _loader_class_name == vmSymbols::jdk_internal_loader_ClassLoaders_PlatformClassLoader() + && _loader_name == nullptr + && _first_loaded_class_name == nullptr; +} + +ClassLoaderKey ClassLoaderKey::key_of_class_loader_data(ClassLoaderData* cld) { + if (cld->is_boot_class_loader_data()) { + return boot_loader_key(); + } else if (cld->is_platform_class_loader_data()) { + return platform_loader_key(); + } + + Klass* loader_class = cld->class_loader_klass(); + Symbol* loader_class_name = loader_class == nullptr ? nullptr : loader_class->name(); + Symbol* loader_name = cld->name(); + Klass* first_klass = cld->klasses(); + if (first_klass == nullptr) { + return ClassLoaderKey(loader_class_name, loader_name, nullptr); + } + + while (first_klass->next_link() != nullptr) { + first_klass = first_klass->next_link(); + } + Symbol* first_loaded_class_name = first_klass->name(); + return ClassLoaderKey(loader_class_name, loader_name, first_loaded_class_name); +} + +// ------------------------------- ClassLoaderChain -------------------------------- + +void ClassLoaderChain::parse_cld() const { + if (!_chain.is_empty()) return; // already parsed + + JavaThread* THREAD = JavaThread::current(); + ClassLoaderData* cld = _cld; + do { + assert(cld != nullptr, "sanity"); + ClassLoaderKey key = ClassLoaderKey::key_of_class_loader_data(cld); + _chain.append(ClassLoaderChain::Node(key, (uintptr_t) cld)); + if (cld->is_boot_class_loader_data()) break; + Handle cur_h(THREAD, cld->class_loader()); + Handle parent_h(THREAD, java_lang_ClassLoader::parent(cur_h())); + cld = ClassLoaderData::class_loader_data_or_null(parent_h()); + if (cld == nullptr) { + // [JBOOSTER TODO] this is a temp solution + cld = ClassLoaderData::the_null_class_loader_data(); + } + } while (true); +} + +int ClassLoaderChain::serialize(MessageBuffer& buf) const { + parse_cld(); + JB_RETURN(buf.serialize_no_meta(_chain.length())); + for (GrowableArrayIterator it = _chain.begin(); it != _chain.end(); ++it) { + uintptr_t addr = (*it).client_cld_addr; + ClassLoaderKey key = (*it).key; + JB_RETURN(buf.serialize_no_meta(addr)); + JB_RETURN(buf.serialize_with_meta(key.loader_class_name())); + JB_RETURN(buf.serialize_with_meta(key.loader_name())); + JB_RETURN(buf.serialize_with_meta(key.first_loaded_class_name())); + } + return 0; +} + +int ClassLoaderChain::deserialize(MessageBuffer& buf) { + assert(_chain.is_empty(), "sanity"); + int size; + JB_RETURN(buf.deserialize_ref_no_meta(size)); + for (int i = 0; i < size; ++i) { + uintptr_t addr; + Symbol* loader_class_name = nullptr; + Symbol* loader_name = nullptr; + Symbol* first_loaded_class_name = nullptr; + JB_RETURN(buf.deserialize_ref_no_meta(addr)); + JB_RETURN(buf.deserialize_with_meta(loader_class_name)); + JB_RETURN(buf.deserialize_with_meta(loader_name)); + JB_RETURN(buf.deserialize_with_meta(first_loaded_class_name)); + _chain.append(ClassLoaderChain::Node(ClassLoaderKey(loader_class_name, loader_name, first_loaded_class_name), addr)); + } + return 0; +} + +// ------------------------------ ClassLoaderLocator ------------------------------- + +static ClassLoaderData* cld_of_cl_oop(oop class_loader_oop) { + return ClassLoaderData::class_loader_data_or_null(class_loader_oop); +} + +ClassLoaderLocator::ClassLoaderLocator(Handle class_loader_h): + ClassLoaderLocator(cld_of_cl_oop(class_loader_h())) {} + +void ClassLoaderLocator::set_class_loader_data(Handle class_loader_h) { + _class_loader_data = cld_of_cl_oop(class_loader_h()); +} + +int ClassLoaderLocator::serialize(MessageBuffer& buf) const { + if (UseJBooster) { + JB_RETURN(buf.serialize_no_meta((uintptr_t) _class_loader_data)); + } else if (AsJBooster) { + if (_class_loader_data != nullptr) { + Thread* THREAD = Thread::current(); + address client_addr = ((ServerStream*) buf.stream())->session_data() + ->client_cld_address(_class_loader_data, THREAD); + _client_cld_address = (uintptr_t) client_addr; + guarantee(_client_cld_address != 0, "client CLD address not in server"); + } else { + guarantee(_client_cld_address != 0, "client CLD address is not specified"); + } + JB_RETURN(buf.serialize_no_meta(_client_cld_address)); + } else fatal("Not jbooster environment?"); + return 0; +} + +int ClassLoaderLocator::deserialize(MessageBuffer& buf) { + uintptr_t client_addr; + JB_RETURN(buf.deserialize_ref_no_meta(client_addr)); + if (UseJBooster) { + _class_loader_data = (ClassLoaderData*) client_addr; + } else if (AsJBooster) { + _client_cld_address = client_addr; + recheck_class_loader_data(buf); + } else fatal("Not jbooster environment?"); + return 0; +} + +ClassLoaderData* ClassLoaderLocator::recheck_class_loader_data(MessageBuffer& buf) { + Thread* THREAD = Thread::current(); + // _class_loader_data can be null if the mapping is not found. + _class_loader_data = ((ServerStream*) buf.stream())->session_data() + ->server_cld_address((address) _client_cld_address, THREAD); + return _class_loader_data; +} + +// --------------------------------- KlassLocator ---------------------------------- + +KlassLocator::KlassLocator(InstanceKlass* klass): + _class_name(klass->name()), + _client_klass((uintptr_t) klass), + _fingerprint(klass->get_stored_fingerprint()), + _class_loader_locator(klass->class_loader_data()) { +} + +int KlassLocator::serialize(MessageBuffer& buf) const { + JB_RETURN(buf.serialize_with_meta(_class_name)); + JB_RETURN(buf.serialize_no_meta(_client_klass)); + JB_RETURN(buf.serialize_no_meta(_fingerprint)); + JB_RETURN(buf.serialize_with_meta(&_class_loader_locator)); + return 0; +} + +int KlassLocator::deserialize(MessageBuffer& buf) { + JB_RETURN(buf.deserialize_with_meta(_class_name)); + JB_RETURN(buf.deserialize_ref_no_meta(_client_klass)); + JB_RETURN(buf.deserialize_ref_no_meta(_fingerprint)); + JB_RETURN(buf.deserialize_with_meta(&_class_loader_locator)); + return 0; +} + +InstanceKlass* KlassLocator::try_to_get_ik(TRAPS) { + ClassLoaderData* cld = _class_loader_locator.class_loader_data(); + if (cld == nullptr) { + return nullptr; + } + Handle loader(THREAD, cld->class_loader()); + Klass* k = SystemDictionary::resolve_or_null(_class_name, loader, Handle(), THREAD); + if (k == nullptr || HAS_PENDING_EXCEPTION) { + if (cld->is_boot_class_loader_data() || cld->is_platform_class_loader_data()) { + log_warning(jbooster, serialization)("The %s class \"%s\" not found.", + cld->is_boot_class_loader_data() ? "boot" : "platform", + _class_name->as_C_string()); + if (HAS_PENDING_EXCEPTION && !PENDING_EXCEPTION->is_a(vmClasses::ClassNotFoundException_klass())) { + LogTarget(Warning, jbooster, serialization) lt; + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } + } + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + return nullptr; + } + return InstanceKlass::cast(k); +} + +// --------------------------------- MethodLocator --------------------------------- + +int MethodLocator::serialize(MessageBuffer& buf) const { + JB_RETURN(buf.serialize_no_meta((uintptr_t) _method->method_holder())); + JB_RETURN(buf.serialize_with_meta(_method->name())); + JB_RETURN(buf.serialize_with_meta(_method->signature())); +#ifdef ASSERT + JB_RETURN(buf.serialize_with_meta(_method->method_holder()->name())); +#endif + return 0; +} + +int MethodLocator::deserialize(MessageBuffer& buf) { + Thread* THREAD = Thread::current(); + + uintptr_t holder_ptr; + JB_RETURN(buf.deserialize_ref_no_meta(holder_ptr)); + InstanceKlass* holder = (InstanceKlass*) ((ServerStream*) buf.stream()) + ->session_data() + ->server_klass_address((address) holder_ptr, THREAD); + + Symbol* client_name = nullptr; + Symbol* client_signature = nullptr; + JB_RETURN(buf.deserialize_with_meta(client_name)); + JB_RETURN(buf.deserialize_with_meta(client_signature)); + TempNewSymbol client_name_wrapper = client_name; + TempNewSymbol client_signature_wrapper = client_signature; + +#ifdef ASSERT + Symbol* klass_name = nullptr; + JB_RETURN(buf.deserialize_with_meta(klass_name)); + TempNewSymbol klass_name_wrapper = klass_name; +#endif // ASSERT + + if (holder != nullptr) { + assert(klass_name == holder->name(), "sanity"); + _method = holder->find_method(client_name, client_signature); + if (_method == nullptr) { + ResourceMark rm(THREAD); + log_debug(jbooster, serialization)("Method \"%s%s\" not found in \"%s\".", + client_name->as_C_string(), + client_signature->as_C_string(), + holder->internal_name()); + } + } else { + ResourceMark rm(THREAD); +#ifdef ASSERT + log_debug(jbooster, serialization)("The holder of \"%s%s\" is null (should be \"%s\").", + client_name->as_C_string(), + client_signature->as_C_string(), + klass_name->as_C_string()); +#else + log_debug(jbooster, serialization)("The holder of \"%s%s\" is null.", + client_name->as_C_string(), client_signature->as_C_string()); +#endif // ASSERT + _method = nullptr; + } + return 0; +} + +// ----------------------------- ProfileDataCollector ------------------------------ + +ProfileDataCollector::ProfileDataCollector(uint32_t size, InstanceKlass** klass_array_base): + _klass_size(size), + _klass_array_base(klass_array_base) {} + +int ProfileDataCollector::serialize(MessageBuffer& buf) const { + for (uint32_t i = 0; i < klass_size(); i++) { + Array* methods = klass_at(i)->methods(); + uint32_t cnt = 0; + for (int j= 0; j < methods->length(); j++) { + MethodData* method_data = methods->at(j)->method_data(); + if (method_data != nullptr && method_data->is_mature()) { + ++cnt; + } + } + JB_RETURN(buf.serialize_no_meta(cnt)); + for (int j= 0; j < methods->length(); j++) { + Method* method = methods->at(j); + MethodData* method_data = method->method_data(); + if (method_data != nullptr && method_data->is_mature()) { + Symbol* name = method->name(); + Symbol* signature = method->signature(); + JB_RETURN(buf.serialize_with_meta(name)); + JB_RETURN(buf.serialize_with_meta(signature)); + MemoryWrapper md_mem(method_data, method_data->size_in_bytes()); + JB_RETURN(buf.serialize_no_meta(md_mem)); + } + } + } + return 0; +} + +int ProfileDataCollector::deserialize(MessageBuffer& buf) { + JavaThread* THREAD = JavaThread::current(); + ResourceMark rm(THREAD); + JClientSessionData* session_data = ((ServerStream*) buf.stream())->session_data(); + + for (uint32_t i = 0; i < klass_size(); i++) { + ThreadInVMfromNative tiv(THREAD); + InstanceKlass* klass = klass_at(i); + + int cnt; + JB_RETURN(buf.deserialize_ref_no_meta(cnt)); + for (int j = 0; j < cnt; ++j) { + Symbol* name = nullptr; + Symbol* signature = nullptr; + JB_RETURN(buf.deserialize_with_meta(name)); + JB_RETURN(buf.deserialize_with_meta(signature)); + TempNewSymbol name_wrapper = name; + TempNewSymbol signature_wrapper = signature; + MemoryWrapper md_mem; + JB_RETURN(buf.deserialize_ref_no_meta(md_mem)); + guarantee(name != nullptr, "sanity"); + guarantee(signature != nullptr, "sanity"); + guarantee(!md_mem.is_null(), "sanity"); + + Method* method = klass->find_method(name, signature); + if (method == nullptr) { + log_warning(jbooster, serialization)("Method not found in klass: method=%s%s, klass=%s", + name->as_C_string(), + signature->as_C_string(), + klass->internal_name()); + continue; + } + MethodData* method_data = MethodData::create_instance_for_jbooster(method, + (int) md_mem.size(), (char*) md_mem.get_memory(), CATCH); + session_data->add_method_data((address) method, (address) method_data, THREAD); + } + } + return 0; +} + +InstanceKlass* ProfileDataCollector::klass_at(uint32_t index) const { + guarantee(index < _klass_size,"sanity"); + return _klass_array_base[index]; +} diff --git a/src/hotspot/share/jbooster/dataTransmissionUtils.hpp b/src/hotspot/share/jbooster/dataTransmissionUtils.hpp new file mode 100644 index 000000000..b224af873 --- /dev/null +++ b/src/hotspot/share/jbooster/dataTransmissionUtils.hpp @@ -0,0 +1,247 @@ +/* + * 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 SHARE_JBOOSTER_DATATRANSMISSIONUTILS_HPP +#define SHARE_JBOOSTER_DATATRANSMISSIONUTILS_HPP + +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "utilities/growableArray.hpp" + +class Symbol; +class InstanceKlass; +class MethodData; + +/** + * To uniquely identify a class loader. + */ +class ClassLoaderKey final: public StackObj { + // Fields that uniquely identify a class loader. + Symbol* _loader_class_name; + Symbol* _loader_name; + Symbol* _first_loaded_class_name; + +public: + ClassLoaderKey(Symbol* loader_class_name, Symbol* loader_name, Symbol* first_loaded_class_name): + _loader_class_name(loader_class_name), + _loader_name(loader_name), + _first_loaded_class_name(first_loaded_class_name) { /* no need to increment_refcount() */ } + ClassLoaderKey(const ClassLoaderKey& key); + + ~ClassLoaderKey(); + + Symbol* loader_class_name() const { return _loader_class_name; } + Symbol* loader_name() const { return _loader_name; } + Symbol* first_loaded_class_name() const { return _first_loaded_class_name; } + + uintx hash() const { + int v = 0; + v = v * 31 + primitive_hash(_loader_class_name); + v = v * 31 + primitive_hash(_loader_name); + v = v * 31 + primitive_hash(_first_loaded_class_name); + return v; + } + bool equals(const ClassLoaderKey& other) const { + return _loader_class_name == other._loader_class_name + && _loader_name == other._loader_name + && _first_loaded_class_name == other._first_loaded_class_name; + } + ClassLoaderKey& operator = (const ClassLoaderKey& other); + + static ClassLoaderKey boot_loader_key(); + bool is_boot_loader() const; + static ClassLoaderKey platform_loader_key(); + bool is_platform_loader() const; + + static ClassLoaderKey key_of_class_loader_data(ClassLoaderData* cld); +}; + +/** + * The parent chain of the target class loader. + * With the chain we can rebuild the dependencies for all class loaders on the + * server. Notice that we assume that all class loaders follow the parents + * delegation model (for now). + */ +class ClassLoaderChain: public StackObj { +public: + struct Node: public StackObj { + ClassLoaderKey key; + uintptr_t client_cld_addr; + + Node(): key(nullptr, nullptr, nullptr), client_cld_addr(0) {} + Node(const ClassLoaderKey& key, uintptr_t adr): key(key), client_cld_addr(adr) {} + Node(const Node& node): key(node.key), client_cld_addr(node.client_cld_addr) {} + + Node& operator = (const Node& node) { + key = node.key; + client_cld_addr = node.client_cld_addr; + return *this; + } + }; + +private: + ClassLoaderData* _cld; + mutable GrowableArray _chain; + +private: + void parse_cld() const; + +public: + ClassLoaderChain(): _cld(nullptr), _chain() {} + ClassLoaderChain(ClassLoaderData* cld): _cld(cld), _chain() { + assert(_cld != nullptr, "sanity"); + } + ClassLoaderChain(const ClassLoaderChain& clc): _cld(clc._cld), _chain() { + assert(_cld != nullptr, "sanity"); + } + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + ClassLoaderChain& operator = (const ClassLoaderChain& clc) { + _cld = clc._cld; + assert(_cld != nullptr, "sanity"); + assert(_chain.is_empty(), "sanity"); + return *this; + } + + GrowableArray* chain() { return &_chain; } +}; + +DECLARE_SERIALIZER_INTRUSIVE(ClassLoaderChain); + +/** + * To locate the class loader oop or its ClassLoaderData. + * + * Class loaders are frequently transmitted between the client and server. + * Therefore, only the ID (for now we use the pointer) of the loader are + * transferred to reduce the data to be transmitted. + * + * * To transmit complete class loader data to the server, please use the + * ClassLoaderData class itself. + */ +class ClassLoaderLocator: public StackObj { + ClassLoaderData* _class_loader_data; + mutable uintptr_t _client_cld_address; // only used by the server + +public: + ClassLoaderLocator(Handle class_loader_h); + ClassLoaderLocator(ClassLoaderData* data): _class_loader_data(data), + _client_cld_address(0) {} + ClassLoaderLocator(): _class_loader_data(nullptr), + _client_cld_address(0) {} + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + void set_class_loader_data(Handle class_loader_h); + void set_class_loader_data(ClassLoaderData* cld) { _class_loader_data = cld; } + ClassLoaderData* class_loader_data() const { return _class_loader_data; } + + // only used by the server + ClassLoaderData* recheck_class_loader_data(MessageBuffer& buf); + uintptr_t client_cld_address() { return _client_cld_address; } +}; + +DECLARE_SERIALIZER_INTRUSIVE(ClassLoaderLocator); + +/** + * To locate a klass. + */ +class KlassLocator: public StackObj { + Symbol* _class_name; + uintptr_t _client_klass; + uint64_t _fingerprint; + ClassLoaderLocator _class_loader_locator; + +public: + KlassLocator(InstanceKlass* klass); + KlassLocator(): _class_name(nullptr), + _client_klass(0u), + _fingerprint(0u), + _class_loader_locator((ClassLoaderData*)nullptr) {} + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + void set_class_name(Symbol* class_name) { _class_name = class_name; } + Symbol* class_name() const { return _class_name; } + + uintptr_t client_klass() { return _client_klass; } + + void set_fingerprint(uint64_t fingerprint) { _fingerprint = fingerprint; } + uint64_t fingerprint() { return _fingerprint; } + + void set_class_loader(Handle class_loader_h) { + _class_loader_locator.set_class_loader_data(class_loader_h); + } + void set_class_loader(ClassLoaderData* class_loader_data) { + _class_loader_locator.set_class_loader_data(class_loader_data); + } + + ClassLoaderLocator& class_loader_locator() { return _class_loader_locator; } + + InstanceKlass* try_to_get_ik(TRAPS); +}; + +DECLARE_SERIALIZER_INTRUSIVE(KlassLocator); + +/** + * To locate a method. + */ +class MethodLocator: public StackObj { + Method* _method; + +public: + MethodLocator(Method* method): _method(method) {} + MethodLocator(): _method(nullptr) {} + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + Method* get_method() { return _method; } +}; + +DECLARE_SERIALIZER_INTRUSIVE(MethodLocator); + +/** + * To collect method data. + */ +class ProfileDataCollector: public StackObj { +private: + uint32_t _klass_size; + InstanceKlass** _klass_array_base; + +public: + ProfileDataCollector(uint32_t size, InstanceKlass** klass_array_base); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + uint32_t klass_size() const { return _klass_size; } + InstanceKlass* klass_at(uint32_t index) const; +}; + +DECLARE_SERIALIZER_INTRUSIVE(ProfileDataCollector); + +#endif // SHARE_JBOOSTER_DATATRANSMISSIONUTILS_HPP diff --git a/src/hotspot/share/jbooster/jBoosterManager.cpp b/src/hotspot/share/jbooster/jBoosterManager.cpp new file mode 100644 index 000000000..9a8959589 --- /dev/null +++ b/src/hotspot/share/jbooster/jBoosterManager.cpp @@ -0,0 +1,154 @@ +/* + * 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. + */ + +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "logging/log.hpp" +#include "runtime/arguments.hpp" +#include "runtime/java.hpp" +#include "runtime/os.hpp" +#include "utilities/formatBuffer.hpp" + +#ifdef ASSERT +void JBoosterManager::client_only() { + assert(UseJBooster, "client only"); +} + +void JBoosterManager::server_only() { + assert(AsJBooster, "server only"); +} +#endif // ASSERT + +const char* JBoosterManager::calc_cache_dir_path(bool is_client) { + const char* fs = FileUtils::separator(); + const char* user_home = FileUtils::home_path(); + int buf_len = strlen(user_home) + strlen(fs) * 2 + 9 /* ".jbooster" */ + 6 /* "client" or "server" */ + 1; + char* buf = NEW_C_HEAP_ARRAY(char, buf_len, mtJBooster); + int cnt = snprintf(buf, buf_len, "%s%s.jbooster%s%s", user_home, fs, fs, is_client ? "client" : "server"); + guarantee(buf_len == cnt + 1, "sanity"); + return buf; +} + +const char* JBoosterManager::calc_program_string_id(const char* program_name, + const char* program_entry, + bool is_jar, + uint32_t program_hash) { + // use simple class name (if it is a class name) + const char* tmp = is_jar ? nullptr : strrchr(program_entry, '.'); + const char* short_entry = (tmp == nullptr ? program_entry : tmp + 1); + + int max_res_len = -1; + char* res = nullptr; + int snp_len = 0; + if (program_name == nullptr) { + // short_entry + '-' + program_hash + '\0' + max_res_len = strlen(short_entry) + 1 + 8 + 1; + res = NEW_C_HEAP_ARRAY(char, max_res_len, mtJBooster); + snp_len = snprintf(res, max_res_len, "%s-%x", short_entry, program_hash); + } else { + // program_name + '-' + short_entry + '-' + program_hash + '\0' + max_res_len = strlen(program_name) + 1 + strlen(short_entry) + 1 + 8 + 1; + res = NEW_C_HEAP_ARRAY(char, max_res_len, mtJBooster); + snp_len = snprintf(res, max_res_len, "%s-%s-%x", program_name, short_entry, program_hash); + } + guarantee(snp_len <= max_res_len, "sanity"); + log_info(jbooster)("Human-readable program string id: \"%s\".", res); + return res; +} + +const char* JBoosterManager::calc_cache_path(const char* cache_dir, + const char* program_str_id, + const char* cache_type) { + const char* fs = FileUtils::separator(); + int buf_len = strlen(cache_dir) + strlen(fs) + 6 + strlen(program_str_id) + 1 + strlen(cache_type) + 1; + char* buf = NEW_C_HEAP_ARRAY(char, buf_len, mtJBooster); + int cnt = snprintf(buf, buf_len, "%s%scache-%s-%s", cache_dir, fs, program_str_id, cache_type); + guarantee(buf_len == cnt + 1, "sanity"); + return buf; +} + +/** + * Returns ".tmp". + * The returned tmp path string is allocated on the heap! + */ +const char* JBoosterManager::calc_tmp_cache_path(const char* cache_path) { + int cache_path_len = strlen(cache_path); + const int tmp_cache_path_len = cache_path_len + 5; // ".tmp" + char* tmp_cache_path = NEW_C_HEAP_ARRAY(char, tmp_cache_path_len, mtJBooster); + snprintf(tmp_cache_path, tmp_cache_path_len, "%s.tmp", cache_path); + return tmp_cache_path; +} + +uint64_t JBoosterManager::calc_random_id() { + // nano_time + random_int, to make it as unique as possible. + return (((uint64_t) os::javaTimeNanos()) >> 4) ^ (((uint64_t) os::random()) << (64 - 8 - 4)); +} + +/** + * Try to create and open the tmp file atomically (we treat the tmp file + * as a lock). The file will not be opened if it already exists. + * Returns the file fd. + */ +int JBoosterManager::create_and_open_tmp_cache_file(const char* tmp_cache_path) { + // Use O_CREAT & O_EXCL to make sure the opened file is created by the + // current thread. + return os::open(tmp_cache_path, O_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0600); +} + +jint JBoosterManager::init_common() { + if (FLAG_SET_CMDLINE(CalculateClassFingerprint, true) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } + + return JNI_OK; +} + +jint JBoosterManager::init_phase1() { + // NOTICE: In this phase Thread::current() is not initialized yet. + assert(UseJBooster || AsJBooster, "sanity"); + if (UseJBooster && AsJBooster) { + vm_exit_during_initialization("Both UseJBooster and AsJBooster are set?!"); + } + + jint jni_err = JNI_OK; + jni_err = init_common(); + if (jni_err != JNI_OK) return jni_err; + + if (UseJBooster) { + return ClientDataManager::init_phase1(); + } else if (AsJBooster) { + return ServerDataManager::init_phase1(); + } + return JNI_OK; +} + +void JBoosterManager::init_phase2(TRAPS) { + assert(UseJBooster == !AsJBooster, "sanity"); + + if (UseJBooster) { + ClientDataManager::init_phase2(CHECK); + } else if (AsJBooster) { + ServerDataManager::init_phase2(CHECK); + } +} diff --git a/src/hotspot/share/jbooster/jBoosterManager.hpp b/src/hotspot/share/jbooster/jBoosterManager.hpp new file mode 100644 index 000000000..eb42f869a --- /dev/null +++ b/src/hotspot/share/jbooster/jBoosterManager.hpp @@ -0,0 +1,69 @@ +/* + * 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 SHARE_JBOOSTER_JBOOSTERMANAGER_HPP +#define SHARE_JBOOSTER_JBOOSTERMANAGER_HPP + +#include "jbooster/jbooster_globals.hpp" +#include "memory/allocation.hpp" +#include "runtime/globals.hpp" +#include "utilities/exceptions.hpp" + +/** + * Manage some common parts about JBooster. + * @see ClientDataManager + * @see ServerDataManager + */ +class JBoosterManager: public AllStatic { +public: + static const uint32_t _heartbeat_timeout = 4 * 60 * 1000; // 4 minutes + +private: + static jint init_common(); + +public: + // Invoked in Threads::create_vm(). + static jint init_phase1(); + static void init_phase2(TRAPS); + + static void client_only() NOT_DEBUG_RETURN; + static void server_only() NOT_DEBUG_RETURN; + + static uint64_t calc_random_id(); + + static const char* calc_cache_dir_path(bool is_client); + static const char* calc_program_string_id(const char* program_name, + const char* program_entry, + bool is_jar, + uint32_t program_hash); + static const char* calc_cache_path(const char* cache_dir, + const char* program_str_id, + const char* cache_type); + static const char* calc_tmp_cache_path(const char* cache_path); + + static int create_and_open_tmp_cache_file(const char* tmp_cache_path); + + static uint32_t heartbeat_timeout() { return _heartbeat_timeout; } +}; + +#endif // SHARE_JBOOSTER_JBOOSTERMANAGER_HPP diff --git a/src/hotspot/share/jbooster/jBoosterSymbols.hpp b/src/hotspot/share/jbooster/jBoosterSymbols.hpp new file mode 100644 index 000000000..720e17d58 --- /dev/null +++ b/src/hotspot/share/jbooster/jBoosterSymbols.hpp @@ -0,0 +1,33 @@ +/* + * 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 SHARE_JBOOSTER_JBOOSTERSYMBOLS_HPP +#define SHARE_JBOOSTER_JBOOSTERSYMBOLS_HPP + +#define JBOOSTER_TEMPLATES(template) \ + template(receiveConnection_name, "receiveConnection") \ + template(codesource_signature, "Ljava/security/CodeSource;") \ + template(getProtectionDomainByURLString_name, "getProtectionDomainByURLString") \ + template(getProtectionDomainByURLString_signature, "(Ljava/lang/String;)Ljava/security/ProtectionDomain;") \ + +#endif // SHARE_JBOOSTER_JBOOSTERSYMBOLS_HPP diff --git a/src/hotspot/share/jbooster/jClientArguments.cpp b/src/hotspot/share/jbooster/jClientArguments.cpp new file mode 100644 index 000000000..2c32b6482 --- /dev/null +++ b/src/hotspot/share/jbooster/jClientArguments.cpp @@ -0,0 +1,405 @@ +/* + * 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. + */ + +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/jClientArguments.hpp" +#include "jbooster/net/communicationStream.hpp" +#include "jbooster/net/messageBuffer.inline.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "logging/log.hpp" +#include "logging/logStream.hpp" +#include "runtime/abstract_vm_version.hpp" +#include "runtime/arguments.hpp" +#include "utilities/stringUtils.hpp" + +static JClientArguments::CpuArch calc_cpu() { +#ifdef X86 + return JClientArguments::CpuArch::CPU_X86; +#endif +#ifdef AARCH64 + return JClientArguments::CpuArch::CPU_AARCH64; +#endif +#ifdef ARM + return JClientArguments::CpuArch::CPU_ARM; +#endif + return JClientArguments::CpuArch::CPU_UNKOWN; +} + +static uint32_t calc_new_hash(uint32_t old_hash, uint32_t ele_hash) { + return 31 * old_hash + ele_hash; +} + +/** + * Returns the jar file name as the program name. + * > This function is only for the client. + * > A new C-heap string will be returned. + */ +const char* calc_program_entry_by_jar(const char* app_cp, int app_cp_len) { + assert(strlen(FileUtils::separator()) == 1, "sanity"); + const char* file_start = strrchr(app_cp, FileUtils::separator()[0]); + if (file_start == nullptr) file_start = app_cp; + else ++file_start; + int len = app_cp + app_cp_len - file_start; + if (strncmp(file_start + len - 4, ".jar", 4) == 0) len -= 4; + char* res = NEW_C_HEAP_ARRAY(char, len + 1, mtJBooster); + memcpy(res, file_start, len); + res[len] = '\0'; + return res; +} + +/** + * Return the main class full name. + * > This function is only for the client. + * > A new C-heap string will be returned. + */ +const char* calc_program_entry_by_class(const char* full_cmd, int full_cmd_len) { + int len = full_cmd_len; + const char* clazz_end = strchr(full_cmd, ' '); + if (clazz_end != nullptr) len = clazz_end - full_cmd; + char* res = NEW_C_HEAP_ARRAY(char, len + 1, mtJBooster); + memcpy(res, full_cmd, len); + res[len] = '\0'; + return res; +} + +/** + * Returns the hash of the classpath names. + * > This function is only for the client. + */ +static uint32_t calc_classpath_name_hash(const char *app_cp, int app_cp_strlen) { + return StringUtils::hash_code(app_cp, app_cp_strlen + 1); +} + +/** + * Returns the hash of the classpath timestamps. + * > This function is only for the client. + */ +static uint32_t calc_classpath_timestamp_hash(const char *app_cp, int app_cp_strlen) { + const int path_len = strlen(os::path_separator()); + uint32_t h = 0; + char* tmp = StringUtils::copy_to_heap(app_cp, app_cp_strlen + 1, mtJBooster); + char* p = tmp; + while (p != nullptr) { + char* nxt = strstr(p, os::path_separator()); + if (nxt != nullptr) { + *nxt = '\0'; + nxt += path_len; + } + if (nxt - p != path_len) { // not empty path + h = calc_new_hash(h, primitive_hash(FileUtils::modify_time(p))); + } + p = nxt; + } + StringUtils::free_from_heap(tmp); + return h; +} + +static uint32_t calc_agent_name_hash() { + uint32_t h = 0; + if (Arguments::init_agents_at_startup()) { + for (AgentLibrary* agent = Arguments::agents(); agent != nullptr; agent = agent->next()) { + h = calc_new_hash(h, StringUtils::hash_code(agent->name())); + } + } + return h; +} + +/** + * Returns the java commands in the cmd line. Different from + * Arguments::java_command(), the return value excludes the file path. + * > This function is only for the client. + * > A new C-heap string will be returned if it is not null. + */ +static const char* calc_java_commands_by_jar(const char* full_cmd, int app_cp_len) { + if (full_cmd[app_cp_len] == '\0') return nullptr; + return StringUtils::copy_to_heap(full_cmd + app_cp_len + 1, mtJBooster); +} + +/** + * Return the java commands in the cmd line. Different from + * Arguments::java_command(), the return value excludes the class name. + * > This function is only for the client. + * > A new C-heap string will be returned if it is not null. + */ +const char* calc_java_commands_by_class(const char* full_cmd, int full_cmd_len) { + const char* start = strchr(full_cmd, ' '); + if (start == nullptr) start = full_cmd + full_cmd_len; + else ++start; + return StringUtils::copy_to_heap(start, mtJBooster); +} + +JClientArguments::JClientArguments(bool is_client) { + if (is_client) { + init_for_client(); + _state = INITIALIZED_FOR_CLIENT; + } else { + _state = NOT_INITIALIZED_FOR_SEREVR; + } +} + +JClientArguments::~JClientArguments() { + if (_state == OBJECT_MOVED) return; // let the new object to free the members + + if (_state == INITIALIZED_FOR_CLIENT) { + // [FOR EACH ARG] + // _internal_jvm_info is shared. Do not free it. + // _program_name is shared. Do not free it. + // _program_entry is shared. Do not free it. + // _java_commands is shared. Do not free it. + delete _related_flags; + } else if (_state == INITIALIZED_FOR_SEREVR) { + // [FOR EACH ARG] + StringUtils::free_from_heap(_internal_jvm_info); + StringUtils::free_from_heap(_program_name); + StringUtils::free_from_heap(_program_entry); + StringUtils::free_from_heap(_java_commands); + delete _related_flags; + } else { + guarantee(_state == NOT_INITIALIZED_FOR_SEREVR, "sanity"); + // All the members are not initialized (may be random value). + } +} + +void JClientArguments::init_for_client() { + const char* full_cmd = Arguments::java_command(); + const char* app_cp = Arguments::get_appclasspath(); + int full_cmd_len = strlen(full_cmd); + int app_cp_len = strlen(app_cp); + + // The classpath is ignored when using "-jar" and in this case the + // app classpath is the jar file path. + bool is_jar = strncmp(full_cmd, app_cp, app_cp_len) == 0; + + // [FOR EACH ARG] + _cpu_arch = calc_cpu(); + _jvm_version = Abstract_VM_Version::jvm_version(); + _internal_jvm_info = Abstract_VM_Version::internal_vm_info_string(); + _program_name = StringUtils::copy_to_heap(JBoosterProgramName, mtJBooster); + _program_entry = is_jar ? calc_program_entry_by_jar(app_cp, app_cp_len) + : calc_program_entry_by_class(full_cmd, full_cmd_len); + _is_jar = is_jar; + _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(); + 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); + } 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(); + _related_flags = new JClientVMFlags(true); + + _hash = calc_hash(); +} + +uint32_t JClientArguments::calc_hash() { + uint32_t result = 1; + + // [FOR EACH ARG] + result = calc_new_hash(result, primitive_hash(_cpu_arch)); + result = calc_new_hash(result, primitive_hash(_jvm_version)); + result = calc_new_hash(result, StringUtils::hash_code(_internal_jvm_info)); + result = calc_new_hash(result, StringUtils::hash_code(_program_name)); + result = calc_new_hash(result, StringUtils::hash_code(_program_entry)); + result = calc_new_hash(result, _is_jar); + 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, 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, _related_flags->hash(_jbooster_allow_clr, _jbooster_allow_cds, _jbooster_allow_aot)); + + return result; +} + +bool JClientArguments::equals(const JClientArguments* that) const { + assert(that != nullptr, "sanity"); + + // [FOR EACH ARG] + if (this->_cpu_arch != that->_cpu_arch ) return false; + if (this->_jvm_version != that->_jvm_version ) return false; + if (StringUtils::compare(this->_internal_jvm_info, that->_internal_jvm_info) != 0) return false; + if (StringUtils::compare(this->_program_name, that->_program_name) != 0) return false; + if (StringUtils::compare(this->_program_entry, that->_program_entry) != 0) return false; + if (this->_is_jar != that->_is_jar) return false; + 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 (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; + return true; +} + +void JClientArguments::print_args(outputStream* st) const { + // [FOR EACH ARG] + st->print_cr(" hash: %x", _hash); + st->print_cr(" cpu_arch: %s", cpu_arch_str(_cpu_arch)); + st->print_cr(" jvm_version: %u", _jvm_version); + st->print_cr(" jvm_info: \"%s\"", _internal_jvm_info); + st->print_cr(" program_name: %s", _program_name); + st->print_cr(" program_entry: %s", _program_entry); + st->print_cr(" is_jar: %s", BOOL_TO_STR(_is_jar)); + 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(" 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)); + _related_flags->print_flags(st); +} + +JClientArguments* JClientArguments::move() { + JClientArguments* new_obj = new JClientArguments(*this); // shallow copy! + _state = OBJECT_MOVED; + return new_obj; +} + +int JClientArguments::serialize(MessageBuffer& buf) const { + // [FOR EACH ARG] + JB_RETURN(buf.serialize_no_meta(_cpu_arch)); + JB_RETURN(buf.serialize_no_meta(_jvm_version)); + JB_RETURN(buf.serialize_with_meta(&_internal_jvm_info)); + JB_RETURN(buf.serialize_with_meta(&_program_name)); + JB_RETURN(buf.serialize_with_meta(&_program_entry)); + JB_RETURN(buf.serialize_no_meta(_is_jar)); + 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_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_with_meta(_related_flags)); + + JB_RETURN(buf.serialize_no_meta(_hash)); + + return 0; +} + +int JClientArguments::deserialize(MessageBuffer& buf) { + guarantee(_state == NOT_INITIALIZED_FOR_SEREVR, "init twice?"); + + // [FOR EACH ARG] + + JB_RETURN(buf.deserialize_ref_no_meta(_cpu_arch)); + JB_RETURN(buf.deserialize_ref_no_meta(_jvm_version)); + + StringWrapper sw_internal_jvm_info; + JB_RETURN(buf.deserialize_with_meta(&sw_internal_jvm_info)); + _internal_jvm_info = sw_internal_jvm_info.export_string(); + + StringWrapper sw_program_name; + JB_RETURN(buf.deserialize_with_meta(&sw_program_name)); + _program_name = sw_program_name.export_string(); + + StringWrapper sw_program_entry; + JB_RETURN(buf.deserialize_with_meta(&sw_program_entry)); + _program_entry = sw_program_entry.export_string(); + + JB_RETURN(buf.deserialize_ref_no_meta(_is_jar)); + + JB_RETURN(buf.deserialize_ref_no_meta(_classpath_name_hash)); + + JB_RETURN(buf.deserialize_ref_no_meta(_classpath_timestamp_hash)); + + JB_RETURN(buf.deserialize_ref_no_meta(_agent_name_hash)); + + StringWrapper sw_java_commands; + 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)); + + _related_flags = new JClientVMFlags(false); + JB_RETURN(buf.deserialize_with_meta(_related_flags)); + + // end of for each arg + + JB_RETURN(buf.deserialize_ref_no_meta(_hash)); + guarantee(_hash == calc_hash(), "sanity"); + _state = INITIALIZED_FOR_SEREVR; + LogTarget(Info, jbooster) lt; + if (lt.is_enabled()) { + ResourceMark rm; + LogStream ls(lt); + ls.print_cr("Received JClientArguments, stream_id=%u:", buf.stream()->stream_id()); + print_args(&ls); + } + return 0; +} + +/** + * The arg "reason" will only be set when the return value of this function is false. + * The value of "reason" should be literal string so you don't have to free it. + */ +bool JClientArguments::check_compatibility_with_server(const char** reason) { + const char* different_item = nullptr; + if (_cpu_arch != calc_cpu()) { + different_item = "CPU arch"; + } else if (_jvm_version != Abstract_VM_Version::jvm_version()) { + different_item = "JVM version"; + } else if (StringUtils::compare(_internal_jvm_info, Abstract_VM_Version::internal_vm_info_string()) != 0) { + different_item = "JVM info"; + } else if (_related_flags->get_UseG1GC() != true) { + different_item = "G1 GC"; + } else { + return true; + } + + assert(different_item != nullptr, "sanity"); + *reason = different_item; + return false; +} + +const char* JClientArguments::cpu_arch_str(CpuArch cpu_arch) { + switch (cpu_arch) { + case CpuArch::CPU_UNKOWN: return "unknown"; + case CpuArch::CPU_X86: return "x86"; + case CpuArch::CPU_ARM: return "arm"; + case CpuArch::CPU_AARCH64: return "aarch64"; + default: break; + } + return "error"; +} diff --git a/src/hotspot/share/jbooster/jClientArguments.hpp b/src/hotspot/share/jbooster/jClientArguments.hpp new file mode 100644 index 000000000..e3574db02 --- /dev/null +++ b/src/hotspot/share/jbooster/jClientArguments.hpp @@ -0,0 +1,115 @@ +/* + * 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 SHARE_JBOOSTER_JCLIENTARGUMENTS_HPP +#define SHARE_JBOOSTER_JCLIENTARGUMENTS_HPP + +#include "jbooster/jClientVMFlags.hpp" +#include "jbooster/net/serialization.hpp" + +/** + * Arguments that identify a program. + */ +class JClientArguments final: public CHeapObj { + enum InitState { + NOT_INITIALIZED_FOR_SEREVR, + INITIALIZED_FOR_SEREVR, + INITIALIZED_FOR_CLIENT, + OBJECT_MOVED // all members are shallowly copied to another object + }; + +public: + enum class CpuArch { + CPU_UNKOWN, + CPU_X86, + CPU_ARM, + CPU_AARCH64 + }; + +private: + InitState _state; + uint32_t _hash; + + // ======== parameters used to identify a program ======== + // All places with the comment "[FOR EACH ARG]" need to + // be updated if you want to add a new argument. + // [FOR EACH ARG] + CpuArch _cpu_arch; + uint32_t _jvm_version; + const char* _internal_jvm_info; + const char* _program_name; + const char* _program_entry; + bool _is_jar; + uint32_t _classpath_name_hash; + 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; + JClientVMFlags* _related_flags; + // ========================= end ========================= + +private: + void init_for_client(); + uint32_t calc_hash(); + +public: + JClientArguments(bool is_client); + ~JClientArguments(); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + // [FOR EACH ARG] + CpuArch cpu_arch() const { return _cpu_arch; } + uint32_t jvm_version() const { return _jvm_version; } + const char* internal_jvm_info() const { return _internal_jvm_info; } + const char* program_name() const { return _program_name; } + const char* program_entry() const { return _program_entry; } + bool is_jar() const { return _is_jar; } + uint32_t classpath_name_hash() const { return _classpath_name_hash; } + 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; + uint32_t hash() const { return _hash; } + void print_args(outputStream* st) const; + + JClientArguments* move(); + + // This method is only used on the server. + bool check_compatibility_with_server(const char** reason); + + static const char* cpu_arch_str(CpuArch cpu_arch); +}; + +DECLARE_SERIALIZER_INTRUSIVE(JClientArguments); + +#endif // SHARE_JBOOSTER_JCLIENTARGUMENTS_HPP diff --git a/src/hotspot/share/jbooster/jClientVMFlags.cpp b/src/hotspot/share/jbooster/jClientVMFlags.cpp new file mode 100644 index 000000000..6e46aeb48 --- /dev/null +++ b/src/hotspot/share/jbooster/jClientVMFlags.cpp @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#include "classfile/javaClasses.hpp" +#include "jbooster/jClientVMFlags.hpp" +#include "jbooster/net/messageBuffer.inline.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "logging/log.hpp" +#include "utilities/stringUtils.hpp" + +template +class FlagTypeHandler: public AllStatic { +public: + static T constructor(T input) { return input; } + static void destructor(T flag) { /* do nothing */ } + + static bool equals(T a, T b) { return a == b; } + static uint32_t hash(T flag) { return primitive_hash(flag); } + + static int serialize(MessageBuffer& buf, T flag) { + return buf.serialize_no_meta(flag); + } + static int deserialize(MessageBuffer& buf, T& flag) { + return buf.deserialize_ref_no_meta(flag); + } + + static void print(outputStream* st, const char* name, T value) { fatal("please specialize it"); } +}; + +// ======================= template specialization of ccstr ======================== + +template <> +ccstr FlagTypeHandler::constructor(ccstr input) { + return StringUtils::copy_to_heap(input, mtJBooster); +} + +template <> +void FlagTypeHandler::destructor(ccstr flag) { + StringUtils::free_from_heap(flag); +} + +template <> +bool FlagTypeHandler::equals(ccstr a, ccstr b) { + return StringUtils::compare(a, b) == 0; +} + +template <> +uint32_t FlagTypeHandler::hash(ccstr flag) { + return StringUtils::hash_code(flag); +} + +template <> +int FlagTypeHandler::serialize(MessageBuffer& buf, ccstr flag) { + return buf.serialize_with_meta(&flag); +} + +template <> +int FlagTypeHandler::deserialize(MessageBuffer& buf, ccstr& flag) { + StringWrapper sw; + JB_RETURN(buf.deserialize_with_meta(&sw)); + flag = sw.export_string(); + return 0; +} + +template <> +void FlagTypeHandler::print(outputStream* st, const char* name, ccstr value) { + st->print_cr(" %s: %s", name, value); +} + +// ==================== template specialization of other types ===================== + +template <> +void FlagTypeHandler::print(outputStream* st, const char* name, bool value) { + st->print_cr(" %s: %s", name, BOOL_TO_STR(value)); +} + +template <> +void FlagTypeHandler::print(outputStream* st, const char* name, intx value) { + st->print_cr(" %s: " INTX_FORMAT, name, value); +} + +template <> +void FlagTypeHandler::print(outputStream* st, const char* name, size_t value) { + st->print_cr(" %s: " SIZE_FORMAT, name, value); +} + +// ==================================== macros ===================================== + +JClientVMFlags::JClientVMFlags(bool is_client) { + if (is_client) { +#define INIT_FLAG(type, flag) v_##flag = FlagTypeHandler::constructor(flag); + JCLIENT_VM_FLAGS(INIT_FLAG) +#undef INIT_FLAG + + _state = INITIALIZED_FOR_CLIENT; + } else { + _state = NOT_INITIALIZED_FOR_SEREVR; + } +} + +JClientVMFlags::~JClientVMFlags() { + guarantee(_state != NOT_INITIALIZED_FOR_SEREVR, "sanity"); +#define FREE_FLAG(type, flag) FlagTypeHandler::destructor(v_##flag); + JCLIENT_VM_FLAGS(FREE_FLAG) +#undef FREE_FLAG +} + +bool JClientVMFlags::equals(JClientVMFlags* that, bool allow_clr, bool allow_cds, bool allow_aot) { +#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) + } +#undef CMP_FLAG + + return true; +} + +uint32_t JClientVMFlags::hash(bool allow_clr, bool allow_cds, bool allow_aot) { + 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) + } +#undef CALC_FLAG_HASH + return result; +} + +int JClientVMFlags::serialize(MessageBuffer& buf) const { +#define SERIALIZE_FLAG(type, flag) JB_RETURN(FlagTypeHandler::serialize(buf, v_##flag)); + JCLIENT_VM_FLAGS(SERIALIZE_FLAG) +#undef SERIALIZE_FLAG + return 0; +} + +int JClientVMFlags::deserialize(MessageBuffer& buf) { + guarantee(_state == NOT_INITIALIZED_FOR_SEREVR, "init twice?"); + +#define DESERIALIZE_FLAG(type, flag) JB_RETURN(FlagTypeHandler::deserialize(buf, v_##flag)); + JCLIENT_VM_FLAGS(DESERIALIZE_FLAG) +#undef DESERIALIZE_FLAG + + _state = INITIALIZED_FOR_SEREVR; + return 0; +} + +void JClientVMFlags::print_flags(outputStream* st) { + if (!log_is_enabled(Trace, jbooster)) return; +#define PRINT_FLAG(type, flag) FlagTypeHandler::print(st, #flag, v_##flag); + JCLIENT_VM_FLAGS(PRINT_FLAG) +#undef PRINT_FLAG +} diff --git a/src/hotspot/share/jbooster/jClientVMFlags.hpp b/src/hotspot/share/jbooster/jClientVMFlags.hpp new file mode 100644 index 000000000..3cdbc59cf --- /dev/null +++ b/src/hotspot/share/jbooster/jClientVMFlags.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 SHARE_JBOOSTER_JCLIENTVMFLAGS_HPP +#define SHARE_JBOOSTER_JCLIENTVMFLAGS_HPP + +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/serialization.hpp" +#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 ) \ + f(intx, ObjectAlignmentInBytes ) \ + +#define JCLIENT_AOT_VM_FLAGS(f) \ + f(intx, CodeEntryAlignment ) \ + f(bool, EnableContended ) \ + f(bool, RestrictContended ) \ + f(intx, ContendedPaddingWidth ) \ + f(bool, VerifyOops ) \ + f(bool, DontCompileHugeMethods ) \ + f(intx, HugeMethodLimit ) \ + f(bool, Inline ) \ + f(bool, ForceUnreachable ) \ + f(bool, FoldStableValues ) \ + f(intx, MaxVectorSize ) \ + f(bool, UseTLAB ) \ + f(bool, UseG1GC ) \ + +#define JCLIENT_VM_FLAGS(f) \ + JCLIENT_CDS_VM_FLAGS(f) \ + JCLIENT_AOT_VM_FLAGS(f) \ + +class JClientVMFlags final: public CHeapObj { + enum InitState { + NOT_INITIALIZED_FOR_SEREVR, + INITIALIZED_FOR_SEREVR, + INITIALIZED_FOR_CLIENT + }; + + InitState _state; + +#define DEF_VARIABLES(type, flag) type v_##flag; + JCLIENT_VM_FLAGS(DEF_VARIABLES) +#undef DEF_VARIABLES + +public: + JClientVMFlags(bool is_client); + ~JClientVMFlags(); + +#define DEF_GETTERS(type, flag) type get_##flag() const { return v_##flag; } + JCLIENT_VM_FLAGS(DEF_GETTERS) +#undef DEF_GETTERS + + 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); + + void print_flags(outputStream* st); +}; + +DECLARE_SERIALIZER_INTRUSIVE(JClientVMFlags); + +#endif // SHARE_JBOOSTER_JCLIENTVMFLAGS_HPP diff --git a/src/hotspot/share/jbooster/jbooster_globals.hpp b/src/hotspot/share/jbooster/jbooster_globals.hpp new file mode 100644 index 000000000..45aa00db5 --- /dev/null +++ b/src/hotspot/share/jbooster/jbooster_globals.hpp @@ -0,0 +1,125 @@ +/* + * 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 SHARE_JBOOSTER_JBOOSTER_GLOBALS_HPP +#define SHARE_JBOOSTER_JBOOSTER_GLOBALS_HPP + +#include "runtime/globals_shared.hpp" + +#define JBOOSTER_FLAGS(develop, \ + develop_pd, \ + product, \ + product_pd, \ + notproduct, \ + range, \ + constraint) \ + \ + product(bool, UseJBooster, false, EXPERIMENTAL, \ + "Use and connect to a JBooster server.") \ + \ + product(bool, AsJBooster, false, EXPERIMENTAL, \ + "Play the role of the JBooster server. " \ + "This flag is automatically set in VM.") \ + \ + product(ccstr, JBoosterAddress, "127.0.0.1", \ + "Address of the JBooster server. Default: '127.0.0.1'.") \ + \ + product(ccstr, JBoosterPort, NULL, \ + "Port of the JBooster server.") \ + \ + product(uint, JBoosterTimeout, 4'000, \ + "Timeout of the JBooster connection. Default: 4,000 ms.") \ + \ + product(bool, JBoosterExitIfUnsupported, true, \ + "Exit the VM if the client uses features " \ + "that are not supported by the server.") \ + \ + product(bool, JBoosterCrashIfNoServer, false, DIAGNOSTIC, \ + "Exit the VM if the server is not available.") \ + \ + product(ccstr, JBoosterProgramName, NULL, \ + "Unique name of current app.") \ + \ + product(ccstr, JBoosterCachePath, NULL, \ + "The directory path for JBooster caches " \ + "(default: $HOME/.jbooster/client).") \ + \ + product(bool, JBoosterLocalMode, false, \ + "No connection to the server and uses only the local cache.") \ + \ + product(ccstr, JBoosterStartupSignal, NULL, \ + "The first invocation of the signal method means the end of " \ + "the client start-up phase. " \ + "The relevant logic is executed at exit if it's not set.") \ + \ + product(int, JBoosterStartupMaxTime, 600, \ + "Max seconds required for the start-up phase (0 means off). " \ + "A plan B when JBoosterStartupSignal fails.") \ + range(0, max_jint) \ + \ + product(int, BoostStopAtLevel, 3, \ + "0 for no optimization; 1 with class loader resource cache; " \ + "2 with aggressive CDS; 3 with lazy AOT; 4 with PGO.") \ + range(0, 4) \ + \ + product(ccstr, UseBoostPackages, "all", DIAGNOSTIC, \ + "\"all\" means \"aot+cds+clr\".") \ + \ + product(bool, JBoosterClientStrictMatch, false, DIAGNOSTIC, \ + "Be strict when matching the client data.") \ + \ + product(bool, PrintAllClassInfo, false, DIAGNOSTIC, \ + "Print info of all class loaders and all classes " \ + "at startup or at exit.") \ + \ + product(bool, UseAggressiveCDS, false, EXPERIMENTAL, \ + "An aggressive stratage to improve start-up " \ + "because we avoid decoding the classfile.") \ + \ + product(bool, CheckClassFileTimeStamp, true, EXPERIMENTAL, \ + "Check whether the modification time of the" \ + "class file is changed during UseAggressiveCDS.") \ + \ + product(bool, UseClassLoaderResourceCache, false, EXPERIMENTAL, \ + "Cache and share the name-url pairs in " \ + "java.net.URLClassLoader#findResource.") \ + \ + product(ccstr, DumpClassLoaderResourceCacheFile, NULL, \ + "The file path to dump class loader resource cache.") \ + \ + product(ccstr, LoadClassLoaderResourceCacheFile, NULL, \ + "The file path to laod class loader resource cache.") \ + \ + product(uint, ClassLoaderResourceCacheSizeEachLoader, 2000, \ + "Max number of entries that can be cached in each " \ + "class loader (delete old values based on LRU).") \ + \ + product(bool, ClassLoaderResourceCacheVerboseMode, false, DIAGNOSTIC, \ + "Dump/load more data for verification and debugging.") \ + + +// end of JBOOSTER_FLAGS + +DECLARE_FLAGS(JBOOSTER_FLAGS) + +#endif // SHARE_JBOOSTER_JBOOSTER_GLOBALS_HPP diff --git a/src/hotspot/share/jbooster/lazyAot.cpp b/src/hotspot/share/jbooster/lazyAot.cpp new file mode 100644 index 000000000..ca79f1816 --- /dev/null +++ b/src/hotspot/share/jbooster/lazyAot.cpp @@ -0,0 +1,537 @@ +/* + * 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. + */ + +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/classLoaderDataGraph.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/lazyAot.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/debugUtils.hpp" +#include "jbooster/utilities/ptrHashSet.inline.hpp" +#include "memory/resourceArea.inline.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/method.inline.hpp" +#include "oops/methodData.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/timerTrace.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" + +enum { + ALL_KLASSES, + RESOLVED_KLASSES, + INITIALIZED_KLASSES +}; + +InstanceKlass* LazyAOT::get_klass_from_class_tag(const constantPoolHandle& cp, + int index, + Handle loader, + int which_klasses, + TRAPS) { + Klass* klass = nullptr; + if (which_klasses == ALL_KLASSES) { + // resolve class if not resolved + klass = cp->klass_at(index, THREAD); + if (HAS_PENDING_EXCEPTION) { + ResourceMark rm; + CLEAR_PENDING_EXCEPTION; + CPKlassSlot kslot = cp->klass_slot_at(index); + int name_index = kslot.name_index(); + Symbol* name = cp->symbol_at(name_index); + klass = SystemDictionary::resolve_or_fail(name, loader, Handle(), true, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_trace(jbooster, compilation)("Class in constant pool not found: " + "class=\"%s\", loader=\"%s\", holder=\"%s\"", + name->as_C_string(), + ClassLoaderData::class_loader_data_or_null(loader()) + ->loader_name(), + cp->pool_holder()->internal_name()); + CLEAR_PENDING_EXCEPTION; + return nullptr; + } + } + if (klass == nullptr || !klass->is_instance_klass()) return nullptr; + return InstanceKlass::cast(klass); + } else { // RESOLVED_KLASSES or INITIALIZED_KLASSES + klass = ConstantPool::klass_at_if_loaded(cp, index); + if (klass == nullptr || !klass->is_instance_klass()) return nullptr; + InstanceKlass* ik = InstanceKlass::cast(klass); + if (which_klasses == INITIALIZED_KLASSES && !ik->is_initialized()) { + return nullptr; + } + return ik; + } +} + +void LazyAOT::get_klasses_from_name_and_type_tag(const constantPoolHandle& cp, + int index, + Handle loader, + GrowableArray &res, + int which_klasses, + TRAPS) { + int sym_index = cp->signature_ref_index_at(index); + Symbol* sym = cp->symbol_at(sym_index); + // The sym could be like "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + // or "Ljava/lang/Object;". + for (SignatureStream ss(sym, Signature::is_method(sym)); !ss.is_done(); ss.next()) { + if (ss.is_array()) ss.skip_array_prefix(); + if (ss.is_reference()) { + assert(!ss.is_array(), "sanity"); + SignatureStream::FailureMode mode = which_klasses == ALL_KLASSES + ? SignatureStream::ReturnNull + : SignatureStream::CachedOrNull; + Klass* klass = ss.as_klass(loader, Handle(), mode, THREAD); + if (HAS_PENDING_EXCEPTION) { + ResourceMark rm; + log_trace(jbooster, compilation)("Class in constant pool not found: " + "method=\"%s\", loader=\"%s\", holder=\"%s\"", + sym->as_C_string(), + ClassLoaderData::class_loader_data_or_null(loader()) + ->loader_name(), + cp->pool_holder()->internal_name()); + CLEAR_PENDING_EXCEPTION; + continue; + } + if (klass == nullptr || !klass->is_instance_klass()) continue; + InstanceKlass* ik = InstanceKlass::cast(klass); + if (which_klasses == INITIALIZED_KLASSES && !ik->is_initialized()) { + continue; + } + res.append(ik); + } + } +} + +void LazyAOT::collect_klasses_by_inheritance(GrowableArray* dst, + PtrHashSet* vis, + InstanceKlass* ik, + TRAPS) { + if (!vis->add(ik)) return; + InstanceKlass* super = ik->java_super(); + if (super != nullptr) { + collect_klasses_by_inheritance(dst, vis, super, THREAD); + } + Array* interfaces = ik->local_interfaces(); + for (int i = 0; i < interfaces->length(); ++i) { + InstanceKlass* interface = interfaces->at(i); + collect_klasses_by_inheritance(dst, vis, interface, THREAD); + } + dst->append(ik); +} + +void LazyAOT::collect_klasses_in_constant_pool(GrowableArray* dst, + PtrHashSet* vis, + InstanceKlass* ik, + int which_klasses, + TRAPS) { + constantPoolHandle cp(THREAD, ik->constants()); + Handle class_loader(THREAD, ik->class_loader()); + for (int i = 0; i < cp->length(); ++i) { + constantTag tag = cp->tag_at(i); + if (tag.is_unresolved_klass() || tag.is_klass()) { + InstanceKlass* next = get_klass_from_class_tag(cp, i, class_loader, which_klasses, THREAD); + if (next != nullptr) { // next is null when it is not a InstanceKlass + collect_klasses_by_inheritance(dst, vis, next, THREAD); + } + } else if (tag.is_name_and_type()) { + GrowableArray next_list; + get_klasses_from_name_and_type_tag(cp, i, class_loader, next_list, which_klasses, THREAD); + for (GrowableArrayIterator iter = next_list.begin(); + iter != next_list.end(); + ++iter) { + collect_klasses_by_inheritance(dst, vis, *iter, THREAD); + } + } + } +} + +void LazyAOT::collect_klasses_in_constant_pool(GrowableArray* dst, + PtrHashSet* vis, + TRAPS) { + int last_len = 0; + for (int lvl = _compilation_related_klasses_layers; lvl > 0; --lvl) { + 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, THREAD); + } + last_len = len; + } +} + +void LazyAOT::collect_klasses_in_method_data(GrowableArray* dst_ik, + GrowableArray* dst_ak, + PtrHashSet* ik_vis, + TRAPS) { + int last_len = 0; + PtrHashSet ak_vis; + for (int lvl = _compilation_related_klasses_layers; lvl > 0; --lvl) { + int len = dst_ik->length(); + for (int i = last_len; i < len; ++i) { + Array* methods = dst_ik->at(i)->methods(); + for (int j = 0; j < methods->length(); j++) { + MethodData* method_data = methods->at(j)->method_data(); + if (method_data != nullptr) { + collect_klasses_in_method_data(dst_ik, dst_ak, ik_vis, &ak_vis, + method_data, ALL_KLASSES, THREAD); + } + } + } + last_len = len; + } +} + +void LazyAOT::collect_klasses_in_method_data(GrowableArray* dst_ik, + GrowableArray* dst_ak, + PtrHashSet* ik_vis, + PtrHashSet* ak_vis, + MethodData* method_data, + int which_klasses, + TRAPS) { + int position = 0; + while (position < method_data->data_size()) { + ProfileData* profile_data = method_data->data_at(position); + GrowableArray ik_array; + GrowableArray ak_array; + profile_data->collect_klass(&ik_array, &ak_array); + for (GrowableArrayIterator iter = ik_array.begin(); + iter != ik_array.end(); + ++iter) { + if (which_klasses == INITIALIZED_KLASSES && !(*iter)->is_initialized()) { + continue; + } + collect_klasses_by_inheritance(dst_ik, ik_vis, *iter, THREAD); + } + for (GrowableArrayIterator iter = ak_array.begin(); + iter != ak_array.end(); + ++iter) { + if (!ak_vis->add(*iter)) continue; + dst_ak->append(*iter); + } + position += profile_data->size_in_bytes(); + } +} + +bool LazyAOT::sort_klasses_by_inheritance(GrowableArray* dst_ik, + GrowableArray* dst_ak, + GrowableArray* src, + bool reverse_scan_src, + TRAPS) { + PtrHashSet visited; + if (reverse_scan_src) { + for (int i = src->length() - 1; i >= 0; --i) { + collect_klasses_by_inheritance(dst_ik, &visited, src->at(i), THREAD); + } + } else { + for (GrowableArrayIterator iter = src->begin(); + iter != src->end(); + ++iter) { + collect_klasses_by_inheritance(dst_ik, &visited, *iter, THREAD); + } + } + collect_klasses_in_constant_pool(dst_ik, &visited, THREAD); + collect_klasses_in_method_data(dst_ik, dst_ak, &visited, THREAD); + return true; +} + +bool LazyAOT::can_be_compiled(const methodHandle& mh) { + ResourceMark rm; + InstanceKlass* ik = mh->method_holder(); + return can_be_compiled(ik); +} + +bool LazyAOT::can_be_compiled(InstanceKlass* ik, bool check_cld) { + if (check_cld && !can_be_compiled(ik->class_loader_data())) { + return false; + } + + // AOT does not support it + if (ik->is_hidden()) return false; + + // Error occurs when proxy classes are transferred to the server. + if (ik->is_dynamic_proxy()) return false; + + return true; +} + +bool LazyAOT::can_be_compiled(ClassLoaderData* cld) { + oop loader_oop = cld->class_loader(); + + // Skip the bootstrap loaders used by LambdaForm. + // Each LambdaForm has its own bootstrap class loader. + // @see ClassLoaderData::is_boot_class_loader_data() + if (cld->is_the_null_class_loader_data()) return true; + if (loader_oop == nullptr) return false; + + // Skip klasses like GeneratedMethodAccessor, GeneratedConstructorAccessor + // and GeneratedSerializationConstructorAccessor. + if (loader_oop->is_a(vmClasses::reflect_DelegatingClassLoader_klass())) { + return false; + } + + return true; +} + +class KlassGetAllInstanceKlassesClosure: public KlassClosure { + GrowableArray* _klasses; + GrowableArray* _methods_to_compile; + GrowableArray* _methods_not_compile; + +public: + KlassGetAllInstanceKlassesClosure(GrowableArray* klasses, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile): + _klasses(klasses), + _methods_to_compile(methods_to_compile), + _methods_not_compile(methods_not_compile) {} + + void do_klass(Klass* k) override { + if (!k->is_instance_klass()) return; + InstanceKlass* ik = InstanceKlass::cast(k); + if (!ik->is_loaded()) return; + + if (PrintAllClassInfo) { + ResourceMark rm; + Symbol* class_name = ik->name(); + const char* class_name_c = class_name == nullptr ? nullptr + : class_name->as_C_string(); + tty->print_cr(" %s:%p", class_name_c, ik); + } + + // Maybe we should add "if (!ik->is_initialized()) return;". + if (!LazyAOT::can_be_compiled(ik, /* check_cld */ false)) return; + + bool should_append_klass = false; + Array* methods = ik->methods(); + int len = methods->length(); + for (int i = 0; i < len; i++) { + Method* m = methods->at(i); + + bool should_compile = m->has_compiled_code(); + + if (should_compile) { + _methods_to_compile->append(m); + should_append_klass = true; + } + + if (m->is_rewrite_invokehandle()) { + _methods_not_compile->append(m); + } + } + + if (should_append_klass) { + _klasses->append(ik); + } + } +}; + +class CLDGetAllInstanceKlassesClosure: public CLDClosure { + GrowableArray* _loaders; + GrowableArray* _klasses; + GrowableArray* _methods_to_compile; + GrowableArray* _methods_not_compile; + +private: + void for_each(ClassLoaderData* cld) { + if (PrintAllClassInfo) { + ResourceMark rm; + Klass* loader_class = cld->class_loader_klass(); + Symbol* loader_class_name = loader_class == nullptr ? nullptr : loader_class->name(); + Symbol* loader_name = cld->name(); + const char* loader_class_name_c = loader_class_name == nullptr ? nullptr + : loader_class_name->as_C_string(); + const char* loader_name_c = loader_name == nullptr ? nullptr + : loader_name->as_C_string(); + tty->print_cr("Class loader: \"%s:%s:%p\", can_be_compiled=%s.", + loader_class_name_c, loader_name_c, cld, + BOOL_TO_STR(LazyAOT::can_be_compiled(cld))); + tty->print_cr(" -"); + } + if (LazyAOT::can_be_compiled(cld)) { + if (_loaders != nullptr) _loaders->append(cld); + KlassGetAllInstanceKlassesClosure cl(_klasses, _methods_to_compile, _methods_not_compile); + cld->classes_do(&cl); + } + } + +public: + CLDGetAllInstanceKlassesClosure(GrowableArray* all_loaders, + GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + bool no_boot_platform = false): + _loaders(all_loaders), + _klasses(klasses_to_compile), + _methods_to_compile(methods_to_compile), + _methods_not_compile(methods_not_compile) {} + + void do_cld(ClassLoaderData* cld) override { for_each(cld); } +}; + +void LazyAOT::collect_all_klasses_to_compile(GrowableArray* all_loaders, + GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + GrowableArray* all_sorted_klasses, + GrowableArray* array_klasses, + TRAPS) { + { + { + TraceTime tt("Collect all classes", TRACETIME_LOG(Info, jbooster, aot)); + CLDGetAllInstanceKlassesClosure cl(all_loaders, klasses_to_compile, methods_to_compile, methods_not_compile); + ThreadInVMfromNative tivm(THREAD); + MutexLocker ml(THREAD, ClassLoaderDataGraph_lock); + ClassLoaderDataGraph::cld_do(&cl); + } + TraceTime tt("Sort klasses", TRACETIME_LOG(Info, jbooster, aot)); + sort_klasses_by_inheritance(all_sorted_klasses, array_klasses, klasses_to_compile, + /* reverse_scan_src */ true, CATCH); + } + log_info(jbooster, compilation)("Klasses to send: %d", all_sorted_klasses->length()); + log_info(jbooster, compilation)("Klasses to compile: %d", klasses_to_compile->length()); +} + +static Handle create_hash_set_instance(InstanceKlass** ik, TRAPS) { + TempNewSymbol hash_set_name = SymbolTable::new_symbol("java/util/HashSet"); + Klass* hash_set_k = SystemDictionary::resolve_or_fail(hash_set_name, Handle(), Handle(), true, CHECK_NH); + assert(hash_set_k != nullptr && hash_set_k->is_instance_klass(), "sanity"); + InstanceKlass* hash_set_ik = InstanceKlass::cast(hash_set_k); + *ik = hash_set_ik; + Handle hash_set_h = JavaCalls::construct_new_instance(hash_set_ik, + vmSymbols::void_method_signature(), + CHECK_NH); + return hash_set_h; +} + +static Handle add_klasses_to_java_hash_set(GrowableArray* klasses, TRAPS) { + DebugUtils::assert_thread_in_vm(); + + // create a HashSet object + InstanceKlass* hash_set_ik = nullptr; + Handle hash_set_h = create_hash_set_instance(&hash_set_ik, CHECK_NH); + + // add all klasses to the hash set + for (GrowableArrayIterator iter = klasses->begin(); iter != klasses->end(); ++iter) { + JavaValue result(T_BOOLEAN); + JavaCalls::call_virtual(&result, hash_set_h, hash_set_ik, + vmSymbols::add_method_name(), + vmSymbols::object_boolean_signature(), + Handle(THREAD, (*iter)->java_mirror()), + CHECK_NH); + guarantee((bool)result.get_jboolean() == true, "sanity"); + } + return hash_set_h; +} + +static Handle add_methods_names_to_java_hash_set(GrowableArray* methods, TRAPS) { + DebugUtils::assert_thread_in_vm(); + + // create a HashSet object + InstanceKlass* hash_set_ik = nullptr; + Handle hash_set_h = create_hash_set_instance(&hash_set_ik, CHECK_NH); + + // add all names of methods to the hash set + for (GrowableArrayIterator iter = methods->begin(); iter != methods->end(); ++iter) { + ResourceMark rm(THREAD); + Method* m = *iter; + const char* s = m->name_and_sig_as_C_string(); + Handle s_h = java_lang_String::create_from_str(s, CHECK_NH); + + JavaValue result(T_BOOLEAN); + JavaCalls::call_virtual(&result, hash_set_h, hash_set_ik, + vmSymbols::add_method_name(), + vmSymbols::object_boolean_signature(), + s_h, CHECK_NH); + guarantee((bool)result.get_jboolean() == true, "sanity"); + } + return hash_set_h; +} + +bool LazyAOT::compile_classes_by_graal(int session_id, + const char* file_path, + GrowableArray* klasses, + TRAPS) { + DebugUtils::assert_thread_in_vm(); + + // arg2 + Handle file_path_h = java_lang_String::create_from_str(file_path, CHECK_false); + + // arg3 + Handle hash_set_h = add_klasses_to_java_hash_set(klasses, CHECK_false); + + JavaValue result(T_BOOLEAN); + JavaCallArguments java_args; + java_args.push_int(session_id); + java_args.push_oop(file_path_h); + java_args.push_oop(hash_set_h); + + TempNewSymbol compile_classes_name = SymbolTable::new_symbol("compileClasses"); + TempNewSymbol compile_classes_signature = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/util/Set;)Z"); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + compile_classes_name, + compile_classes_signature, + &java_args, CHECK_false); + return (bool) result.get_jboolean(); +} + +bool LazyAOT::compile_methods_by_graal(int session_id, + const char* file_path, + GrowableArray* klasses, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + TRAPS) { + DebugUtils::assert_thread_in_vm(); + + // arg2 + Handle file_path_h = java_lang_String::create_from_str(file_path, CHECK_false); + + // arg3 + Handle klass_set_h = add_klasses_to_java_hash_set(klasses, CHECK_false); + + // arg4 + Handle method_name_set_h = add_methods_names_to_java_hash_set(methods_to_compile, CHECK_false); + + // arg5 + Handle not_method_name_set_h = add_methods_names_to_java_hash_set(methods_not_compile, CHECK_false); + + JavaValue result(T_BOOLEAN); + JavaCallArguments java_args; + java_args.push_int(session_id); + java_args.push_oop(file_path_h); + java_args.push_oop(klass_set_h); + java_args.push_oop(method_name_set_h); + java_args.push_oop(not_method_name_set_h); + + 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"); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + compile_methods_name, + compile_methods_signature, + &java_args, CHECK_false); + return (bool) result.get_jboolean(); +} diff --git a/src/hotspot/share/jbooster/lazyAot.hpp b/src/hotspot/share/jbooster/lazyAot.hpp new file mode 100644 index 000000000..f9adc6596 --- /dev/null +++ b/src/hotspot/share/jbooster/lazyAot.hpp @@ -0,0 +1,111 @@ +/* + * 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 SHARE_JBOOSTER_LAZYAOT_HPP +#define SHARE_JBOOSTER_LAZYAOT_HPP + +#include "jbooster/utilities/ptrHashSet.hpp" +#include "memory/allocation.hpp" +#include "runtime/handles.hpp" +#include "runtime/thread.hpp" + +class AOTCodeHeap; +class ArrayKlass; +class ClassLoaderData; +template class GrowableArray; +class InstanceKlass; +class Method; +class MethodData; + +class LazyAOT: public AllStatic { +private: + static const int _compilation_related_klasses_layers = 2; + +private: + static InstanceKlass* get_klass_from_class_tag(const constantPoolHandle& cp, + int index, + Handle loader, + int which_klasses, + TRAPS); + static void get_klasses_from_name_and_type_tag(const constantPoolHandle& cp, + int index, + Handle loader, + GrowableArray &res, + int which_klasses, + TRAPS); + + static void collect_klasses_by_inheritance(GrowableArray* dst, + PtrHashSet* vis, + InstanceKlass* ik, + TRAPS); + static void collect_klasses_in_constant_pool(GrowableArray* dst, + PtrHashSet* vis, + InstanceKlass* ik, + int which_klasses, + TRAPS); + static void collect_klasses_in_constant_pool(GrowableArray* dst, + PtrHashSet* vis, + TRAPS); + static void collect_klasses_in_method_data(GrowableArray* dst_ik, + GrowableArray* dst_ak, + PtrHashSet* ik_vis, + TRAPS); + static void collect_klasses_in_method_data(GrowableArray* dst_ik, + GrowableArray* dst_ak, + PtrHashSet* ik_vis, + PtrHashSet* ak_vis, + MethodData* method_data, + int which_klasses, + TRAPS); + static bool sort_klasses_by_inheritance(GrowableArray* dst_ik, + GrowableArray* dst_ak, + GrowableArray* src, + bool reverse_scan_src, + TRAPS); + +public: + static bool can_be_compiled(const methodHandle& mh); + static bool can_be_compiled(InstanceKlass* ik, bool check_cld = true); + static bool can_be_compiled(ClassLoaderData* cld); + + static void collect_all_klasses_to_compile(GrowableArray* all_loaders, + GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + GrowableArray* all_sorted_klasses, + GrowableArray* array_klasses, + TRAPS); + + static bool compile_classes_by_graal(int session_id, + const char* file_path, + GrowableArray* klasses, + TRAPS); + static bool compile_methods_by_graal(int session_id, + const char* file_path, + GrowableArray* klasses, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + TRAPS); +}; + +#endif // SHARE_JBOOSTER_LAZYAOT_HPP diff --git a/src/hotspot/share/jbooster/net/clientStream.cpp b/src/hotspot/share/jbooster/net/clientStream.cpp new file mode 100644 index 000000000..d72b796d5 --- /dev/null +++ b/src/hotspot/share/jbooster/net/clientStream.cpp @@ -0,0 +1,267 @@ +/* + * 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. + */ + +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/net/clientStream.hpp" +#include "jbooster/net/rpcCompatibility.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "runtime/java.hpp" +#include "runtime/thread.hpp" + +ClientStream::ClientStream(const char* address, const char* port, uint32_t timeout_ms): + CommunicationStream(Thread::current_or_null()), + _server_address(address), + _server_port(port), + _timeout_ms(timeout_ms), + _inform_before_close(true) {} + +ClientStream::ClientStream(const char* address, const char* port, uint32_t timeout_ms, Thread* thread): + CommunicationStream(thread), + _server_address(address), + _server_port(port), + _timeout_ms(timeout_ms), + _inform_before_close(true) {} + +ClientStream::~ClientStream() { + if (!can_close_safely() && _inform_before_close) { + send_no_more_compilation_tasks(); + } +} + +int ClientStream::connect_to_server() { + close_stream(); + const int retries = 3; + int last_err = 0; + for (int i = 0; i < retries; ++i) { + JB_TRY_BREAKABLE { + 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); + return 0; + } JB_TRY_BREAKABLE_END + JB_CATCH(ECONNREFUSED, EBADF) { + last_err = JB_ERR; + } JB_CATCH_REST() { + last_err = JB_ERR; + break; + } JB_CATCH_END; + } + + if (last_err == ECONNREFUSED || last_err == EBADF) { + constexpr const char* fmt = "Failed to connect to the JBooster server! Retried %d times."; + if (JBoosterCrashIfNoServer) { + fatal(fmt, retries); + } else { + log_error(jbooster, rpc)(fmt, retries); + } + } else { + constexpr const char* fmt = "Unexpected exception when connecting to the JBooster server: error=%s(\"%s\")."; + if (JBoosterCrashIfNoServer) { + fatal(fmt, JBErr::err_name(last_err), JBErr::err_message(last_err)); + } else { + log_error(jbooster, rpc)(fmt, JBErr::err_name(last_err), JBErr::err_message(last_err)); + } + } + + return last_err; +} + +int ClientStream::request_cache_file(bool* use_it, + bool allowed_to_use, + bool local_cache_exists, + bool remote_cache_exists, + const char* file_path, + MessageType msg_type) { + if (!allowed_to_use) { + *use_it = false; + } else if (local_cache_exists) { + *use_it = true; + } else if (remote_cache_exists) { + FileWrapper file(file_path, SerializationMode::DESERIALIZE); + if (file.is_tmp_file_already_exists()) { + *use_it = file.wait_for_file_deserialization(); + } else { + JB_RETURN(send_request(msg_type)); + JB_RETURN(file.recv_file(this)); + *use_it = !file.is_null(); + } + } else { + *use_it = false; + } + return 0; +} + +int ClientStream::sync_session_meta__client(bool* has_remote_clr, bool* has_remote_cds, bool* has_remote_aot) { + ClientDataManager& cdm = ClientDataManager::get(); + + 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)); + + uint64_t server_random_id; + uint32_t session_id, program_id; + + JB_TRY { + JB_THROW(recv_response(stream_id_addr(), &server_random_id, &session_id, &program_id, + has_remote_clr, has_remote_cds, has_remote_aot)); + } JB_TRY_END + JB_CATCH(JBErr::BAD_MSG_TYPE) { + if (recv_msg_type() == MessageType::UnsupportedClient) { + char unsupport_reason[64]; + parse_request(&unsupport_reason); + log_error(jbooster, rpc)("The server does not support this client because the \"%s\" of the two are different!", + unsupport_reason); + } + return JB_ERR; + } JB_CATCH_REST() { + return JB_ERR; + } JB_CATCH_END; + + cdm.set_server_random_id(server_random_id); + cdm.set_session_id(session_id); + cdm.set_program_id(program_id); + log_info(jbooster, rpc)("Client meta: session_id=%u, program_id=%u, server_random_id=" UINT64_FORMAT_X ".", + session_id, program_id, server_random_id); + return 0; +} + +int ClientStream::sync_stream_meta__client() { + ClientDataManager& cdm = ClientDataManager::get(); + + uint32_t session_id = cdm.session_id(); + uint64_t client_random_id = cdm.random_id(); + uint64_t server_random_id = cdm.server_random_id(); + JB_RETURN(send_request(MessageType::ClientStreamMeta, &session_id, &client_random_id, &server_random_id)); + JB_RETURN(recv_response(stream_id_addr())); + log_trace(jbooster, rpc)("New ClientStream: session_id=%u, stream_id=%u.", session_id, stream_id()); + return 0; +} + +int ClientStream::resync_session_and_stream_meta__client() { + bool has_remote_clr, has_remote_cds, has_remote_aot; // unused here + JB_RETURN(sync_session_meta__client(&has_remote_clr, &has_remote_cds, &has_remote_aot)); + return 0; +} + +int ClientStream::connect_and_init_session(bool* use_clr, bool* use_cds, bool* use_aot) { + _inform_before_close = false; + ClientDataManager& cdm = ClientDataManager::get(); + + JB_TRY { + JB_THROW(connect_to_server()); + bool has_remote_clr, has_remote_cds, has_remote_aot; + 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(), + 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(), + 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(), + FileUtils::is_file(cdm.cache_aot_path()), + has_remote_aot, + cdm.cache_aot_path(), + MessageType::GetLazyAOTCache)); + + JB_THROW(send_request(MessageType::EndOfCurrentPhase)); + } JB_TRY_END + JB_CATCH_REST() { + if (JBoosterExitIfUnsupported && JB_ERR == JBErr::BAD_MSG_TYPE + && recv_msg_type() == MessageType::UnsupportedClient) { + vm_exit_during_initialization("The JBooster server does not support this client."); + } + LOG_OR_CRASH(); + + if (!*use_clr) *use_clr = FileUtils::is_file(cdm.cache_clr_path()); + if (!*use_cds) *use_cds = FileUtils::is_file(cdm.cache_cds_path()); + if (!*use_aot) *use_aot = FileUtils::is_file(cdm.cache_aot_path()); + + return JB_ERR; + } JB_CATCH_END; + return 0; +} + +int ClientStream::connect_and_init_stream() { + JB_TRY { + JB_THROW(connect_to_server()); + JB_THROW(sync_stream_meta__client()); + } JB_TRY_END + JB_CATCH(JBErr::BAD_MSG_TYPE) { + if (msg_recv().msg_type() == MessageType::ClientSessionMetaAgain) { + JB_TRY { + JB_THROW(resync_session_and_stream_meta__client()); + JB_THROW(sync_stream_meta__client()); + } JB_TRY_END + JB_CATCH_REST() { + LOG_OR_CRASH(); + return JB_ERR; + } JB_CATCH_END; + return 0; + } + LOG_OR_CRASH(); + return JB_ERR; + } JB_CATCH_REST() { + LOG_OR_CRASH(); + return JB_ERR; + } JB_CATCH_END; + return 0; +} + +void ClientStream::send_no_more_compilation_tasks() { + if (is_stream_closed()) return; + JB_TRY { + bool no_more = true; + JB_THROW(send_request(MessageType::NoMoreRequests, &no_more)); + set_can_close_safely(); + } JB_TRY_END + JB_CATCH_REST() { + log_warning(jbooster, rpc)("Exception [%s] at ClientStream::send_no_more_compilation_tasks(). stream_id=%u.", + JBErr::err_name(JB_ERR), stream_id()); + } JB_CATCH_END; +} + +void ClientStream::log_or_crash(const char* file, int line, int err_code) { + constexpr const char* fmt = "Unhandled exception found at %s:%d: error=%s(\"%s\"), stream_id=%u."; + if (JBoosterCrashIfNoServer) { + fatal(fmt, file, line, + JBErr::err_name(err_code), JBErr::err_message(err_code), + stream_id()); + } else { + log_error(jbooster, rpc)(fmt, file, line, + JBErr::err_name(err_code), JBErr::err_message(err_code), + stream_id()); + } +} diff --git a/src/hotspot/share/jbooster/net/clientStream.hpp b/src/hotspot/share/jbooster/net/clientStream.hpp new file mode 100644 index 000000000..1f185644f --- /dev/null +++ b/src/hotspot/share/jbooster/net/clientStream.hpp @@ -0,0 +1,69 @@ +/* + * 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 SHARE_JBOOSTER_NET_CLIENTSTREAM_HPP +#define SHARE_JBOOSTER_NET_CLIENTSTREAM_HPP + +#include "jbooster/net/communicationStream.inline.hpp" + +class ClientStream: public CommunicationStream { +private: + const char* const _server_address; + const char* const _server_port; + const uint32_t _timeout_ms; + + bool _inform_before_close; + +private: + static int try_to_connect_once(int* res_fd, const char* address, const char* port, uint32_t timeout_ms); + + int request_cache_file(bool* use_it, + bool allowed_to_use, + bool local_cache_exists, + bool remote_cache_exists, + const char* file_path, + MessageType msg_type); + + int connect_to_server(); + + int sync_session_meta__client(bool* use_clr, bool* use_cds, bool* use_aot); + int sync_stream_meta__client(); + int resync_session_and_stream_meta__client(); + +public: + ClientStream(const char* address, const char* port, uint32_t timeout_ms); + ClientStream(const char* address, const char* port, uint32_t timeout_ms, Thread* thread); + ~ClientStream(); + + 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); + int connect_and_init_stream(); + void send_no_more_compilation_tasks(); + + void log_or_crash(const char* file, int line, int err_code); +}; + +#define LOG_OR_CRASH() log_or_crash(__FILE__, __LINE__, JB_ERR) + +#endif // SHARE_JBOOSTER_NET_CLIENTSTREAM_HPP diff --git a/src/hotspot/share/jbooster/net/communicationStream.cpp b/src/hotspot/share/jbooster/net/communicationStream.cpp new file mode 100644 index 000000000..f0898170c --- /dev/null +++ b/src/hotspot/share/jbooster/net/communicationStream.cpp @@ -0,0 +1,183 @@ +/* + * 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. + */ + +#include "jbooster/net/communicationStream.inline.hpp" +#include "runtime/os.inline.hpp" +#ifdef ASSERT +#include "runtime/thread.inline.hpp" +#endif // ASSERT + +void CommunicationStream::handle_net_err(int comm_size, bool is_recv) { + if (comm_size > 0) return; + const char* rw = is_recv ? "recv" : "send"; + if (comm_size == 0) { + set_errno(JBErr::CONN_CLOSED_BY_PEER); + log_debug(jbooster, rpc)("Failed to %s as the connection is closed by peer. stream_id=%u.", + rw, stream_id()); + close_stream(); + return; + } + // comm_size < 0 + if (is_stream_closed()) { + set_errno(JBErr::CONN_CLOSED); + log_debug(jbooster, rpc)("Failed to %s as the connection has been closed. stream_id=%u.", + rw, stream_id()); + return; + } + int err = errno; + errno = 0; + set_errno(err); + log_debug(jbooster, rpc)("Failed to %s: error=%s(\"%s\"), stream_id=%u.", + rw, JBErr::err_name(err), JBErr::err_message(err), stream_id()); + // We don't give special treatment to EAGAIN (yet). + close_stream(); + return; +} + +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; +} + +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); + if (read_size <= 0) { + handle_net_err(read_size, true); + break; + } + total_read_size += read_size; + } + return total_read_size; +} + +uint32_t CommunicationStream::write_once_to_stream(char* buf, uint32_t size) { + int written_size = os::send(_conn_fd, buf, size, 0); + if (written_size <= 0) { + handle_net_err(written_size, false); + return 0; + } + return (uint32_t) written_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); + if (written_size <= 0) { + handle_net_err(written_size, false); + break; + } + total_written_size += written_size; + } + return total_written_size; +} + +void CommunicationStream::close_stream() { + if (_conn_fd >= 0) { + log_trace(jbooster, rpc)("Connection closed. stream_id=%u.", stream_id()); + os::close(_conn_fd); + _conn_fd = -1; + } +} + +#ifdef ASSERT +void CommunicationStream::assert_current_thread() { + assert(Thread::current_or_null() == _cur_thread, "cur_thread=%p, stream_thread=%p", + Thread::current_or_null(), _cur_thread); +} + +void CommunicationStream::assert_in_native() { + if (_cur_thread != nullptr && _cur_thread->is_Java_thread()) { + assert(_cur_thread->as_Java_thread()->thread_state() == _thread_in_native, "may affect safepoint"); + } +} +#endif // ASSERT + +int CommunicationStream::recv_message() { + assert_current_thread(); + assert_in_native(); + + Message& msg = _msg_recv; + // read once (or memmove from the overflowed buffer) to get message size + uint32_t read_size, msg_size; + if (msg.has_overflow()) { + read_size = msg.move_overflow(); + if (read_size < sizeof(msg_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 (!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()); + } + return return_errno_or_flag(JBErr::BAD_MSG_SIZE); + } + + msg_size = msg.deserialize_size_only(); + 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 + uint32_t msg_left_size = msg_size - read_size; + msg.expand_buf_if_needed(msg_size, read_size); + uint32_t sz = read_all_from_stream(msg.buf_beginning() + read_size, msg_left_size); + if (sz < msg_left_size) { + log_warning(jbooster, rpc)("Failed to read the rest message: read_size=%d, msg_left_size=%d. stream_id=%u.", + sz, msg_left_size, stream_id()); + return return_errno_or_flag(JBErr::BAD_MSG_SIZE); + } + } + + msg.deserialize_meta(); + log_debug(jbooster, rpc)("Recv %s, size=%u, thread=%p, stream_id=%u.", + msg_type_name(msg.msg_type()), msg.msg_size(), + Thread::current_or_null(), stream_id()); + return 0; +} + +int CommunicationStream::send_message() { + assert_current_thread(); + assert_in_native(); + + Message& msg = _msg_send; + msg.serialize_meta(); + uint32_t sz = write_all_to_stream(msg.buf_beginning(), msg.msg_size()); + if (sz != msg.msg_size()) { + log_warning(jbooster, rpc)("Failed to send the full message: write_size=%d, msg_size=%d. stream_id=%u.", + sz, msg.msg_size(), stream_id()); + return return_errno_or_flag(JBErr::BAD_MSG_SIZE); + } + log_debug(jbooster, rpc)("Send %s, size=%u, thread=%p, stream_id=%u.", + msg_type_name(msg.msg_type()), msg.msg_size(), + Thread::current_or_null(), stream_id()); + return 0; +} diff --git a/src/hotspot/share/jbooster/net/communicationStream.hpp b/src/hotspot/share/jbooster/net/communicationStream.hpp new file mode 100644 index 000000000..7b2cfd9db --- /dev/null +++ b/src/hotspot/share/jbooster/net/communicationStream.hpp @@ -0,0 +1,152 @@ +/* + * 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 SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_HPP +#define SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_HPP + +#include "jbooster/net/message.hpp" +#include "jbooster/net/netCommon.hpp" +#include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" + +class Thread; + +/** + * Base class of ServerStream and ClientStream. + * We never use CommunicationStream directly. Instead, we specify ServerStream or ClientStream + * each time. So there's no need to define any method as vitrual. + */ +class CommunicationStream: public CHeapObj { +private: + int _conn_fd; // generated by the OS + uint32_t _stream_id; // generated by the server + int _errno; + + Message _msg_recv, _msg_send; + + bool _can_close_safely; + + // only to ensure that it's used by only one thread + Thread* _cur_thread; + +private: + void handle_net_err(int comm_size, bool is_read); + + void set_errno(int eno) { _errno = eno; } + 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; } + + uint32_t read_once_from_stream(char* buf, uint32_t size); + uint32_t read_all_from_stream(char* buf, uint32_t size); + uint32_t write_once_to_stream(char* buf, uint32_t size); + uint32_t write_all_to_stream(char* buf, uint32_t size); + + bool check_received_message_type(MessageType expected); + bool check_received_message_size(); + + void assert_current_thread() NOT_DEBUG_RETURN; + void assert_in_native() NOT_DEBUG_RETURN; + +protected: + CommunicationStream(Thread* thread): + _conn_fd(-1), + _stream_id(0), + _errno(0), + _msg_recv(SerializationMode::DESERIALIZE, this), + _msg_send(SerializationMode::SERIALIZE, this), + _can_close_safely(false), + _cur_thread(thread) {} + + virtual ~CommunicationStream() { close_stream(); } + + void init_stream(int conn_fd) { _conn_fd = conn_fd; } + void close_stream(); + + int recv_message(); + int send_message(); + + uint32_t* stream_id_addr() { return &_stream_id; } + void set_stream_id(uint32_t stream_id) { _stream_id = stream_id; } + + void set_can_close_safely() { _can_close_safely = true; } + +public: + bool is_stream_closed() { return _conn_fd < 0; } + bool can_close_safely() { return _can_close_safely; } + + int conn_fd() { return _conn_fd; } + uint32_t stream_id() { return _stream_id; } + + const Message& msg_recv() const { return _msg_recv; } + const Message& msg_send() const { return _msg_send; } + + // Usage of the following APIs: + // (Both the client and server can act as the requester and responder.) + // + // Requester: send_request() recv_response() + // | A + // V | + // Responder: recv_request() -> parse_request() -> send_response() + // + // Or, of course, more generally: + // + // Side A: send_request() recv_request() -> parse_request() + // | A + // V | + // Side B: recv_request() -> parse_request() -> send_request() + // + // @return: 0: fine; other values: error code + // + // **Note**: These operations (except parse_request()) may block the thread until + // JBoosterTimeout. So it's better not to invoke them in _thread_in_vm + // state of JavaThread, or the time to reach safepoints may be severely + // affected. + // @see ThreadToNativeFromVM, ThreadBlockInVM + + template + int send_request(MessageType type, const Args* const... args); + + int recv_request(MessageType& type); + + template + int recv_request(MessageType type, Args* const&... args); + + template + int parse_request(Args* const&... args); + + template + int send_response(const Args* const... args); + + template + int recv_response(Args* const&... args); + + MessageType recv_msg_type() { return _msg_recv.msg_type(); } + + Thread* current_thread() { return _cur_thread; } + void set_current_thread(Thread* thread) { _cur_thread = thread; } + + bool set_read_write_timeout(uint32_t timeout_ms); +}; + +#endif // SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_HPP diff --git a/src/hotspot/share/jbooster/net/communicationStream.inline.hpp b/src/hotspot/share/jbooster/net/communicationStream.inline.hpp new file mode 100644 index 000000000..45311c2be --- /dev/null +++ b/src/hotspot/share/jbooster/net/communicationStream.inline.hpp @@ -0,0 +1,125 @@ +/* + * 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 SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_INLINE_HPP +#define SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_INLINE_HPP + +#include "jbooster/net/communicationStream.hpp" +#include "jbooster/net/message.inline.hpp" +#include "logging/log.hpp" + +inline bool CommunicationStream::check_received_message_type(MessageType expected) { + if (expected != _msg_recv.msg_type()) { + log_warning(jbooster, rpc)("Failed to receive the message as wrong message type: " + "expected=%s, received=%s. stream_id=%u.", + msg_type_name(expected), + msg_type_name(_msg_recv.msg_type()), + stream_id()); + return false; + } + return true; +} + +inline bool CommunicationStream::check_received_message_size() { + if (_msg_recv.msg_size() != _msg_recv.cur_buf_offset()) { + log_warning(jbooster, rpc)("Failed to parse the message as the msg_size mismatch: " + "msg_size=%u, parsed_size=%u. stream_id=%u.", + _msg_recv.msg_size(), + _msg_recv.cur_buf_offset(), + stream_id()); + return false; + } + return true; +} + +/** + * Send the mesaage with the message type and all the arguments. + */ +template +inline int CommunicationStream::send_request(MessageType type, const Args* const... args) { + _msg_send.set_msg_type(type); + _msg_send.set_cur_buf_offset_after_meta(); + JB_RETURN(_msg_send.serialize(args...)); + _msg_send.set_msg_size_based_on_cur_buf_offset(); + return send_message(); +} + +/** + * Receive a message. + * The received message type is set to `type`. + */ +inline int CommunicationStream::recv_request(MessageType& type) { + int err_code = recv_message(); + type = _msg_recv.msg_type(); + return err_code; +} + +/** + * Receive and parse a message. + * The received message type must be the same as `type`. + */ +template +inline int CommunicationStream::recv_request(MessageType type, Args* const&... args) { + JB_RETURN(recv_message()); + if (!check_received_message_type(type)) { + return JBErr::BAD_MSG_TYPE; + } + return parse_request(args...); +} + +/** + * Parse the received message. + * Invoke recv_request() before parse_request() to identify the message type. + */ +template +inline int CommunicationStream::parse_request(Args* const&... args) { + assert(_msg_recv.cur_buf_offset() == Message::meta_size, "address mismatch"); + JB_RETURN(_msg_recv.deserialize(args...)); + if (!check_received_message_size()) { + return JBErr::BAD_MSG_DATA; + } + return 0; +} + +/** + * Send the response using the message type of the last received message. + */ +template +inline int CommunicationStream::send_response(const Args* const... args) { + return send_request(_msg_recv.msg_type(), args...); +} + + +/** + * Receive the response using the message type of the last sent message. + */ +template +inline int CommunicationStream::recv_response(Args* const&... args) { + JB_RETURN(recv_message()); + if (!check_received_message_type(_msg_send.msg_type())) { + return JBErr::BAD_MSG_TYPE; + } + return parse_request(args...); +} + +#endif // SHARE_JBOOSTER_NET_COMMUNICATIONSTREAM_INLINE_HPP diff --git a/src/hotspot/share/jbooster/net/errorCode.cpp b/src/hotspot/share/jbooster/net/errorCode.cpp new file mode 100644 index 000000000..aa46e5b88 --- /dev/null +++ b/src/hotspot/share/jbooster/net/errorCode.cpp @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include "jbooster/net/errorCode.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" + +#define REGISTER_JB_ERROR_CODE_NAMES_AND_MESSAGES(err_name, human_readable) #err_name, human_readable, + +static const char* jb_err_code_name_str_arr[] = { + "GOOD", "Nothing is wrong", + JB_ERROR_CODES(REGISTER_JB_ERROR_CODE_NAMES_AND_MESSAGES) + "END_OF_JB_ERROR_CODE", "Should not reach here!" +}; + +#undef REGISTER_JB_ERROR_CODE_NAMES_AND_MESSAGES + +const char* JBErr::err_name(int err_code) { + if (err_code > 0) return os::errno_name(err_code); + guarantee(-err_code <= err_cnt(), "unknown error code: %d", err_code); + int index = -err_code * 2; + return jb_err_code_name_str_arr[index]; +} + +const char* JBErr::err_message(int err_code) { + if (err_code > 0) return os::strerror(err_code); + guarantee(-err_code <= err_cnt(), "unknown error code: %d", err_code); + int index = -err_code * 2 + 1; + return jb_err_code_name_str_arr[index]; +} diff --git a/src/hotspot/share/jbooster/net/errorCode.hpp b/src/hotspot/share/jbooster/net/errorCode.hpp new file mode 100644 index 000000000..8dddd31f0 --- /dev/null +++ b/src/hotspot/share/jbooster/net/errorCode.hpp @@ -0,0 +1,79 @@ +/* + * 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 SHARE_JBOOSTER_NET_ERRORCODE_HPP +#define SHARE_JBOOSTER_NET_ERRORCODE_HPP + +#include + +#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_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" ) \ + f(BAD_ARG_SIZE, "Unexpected size of the argument" ) \ + f(BAD_ARG_DATA, "Unexpected payload data of the argument" ) \ + f(INCOMPATIBLE_RPC, "Incompatible RPC version" ) \ + f(DESER_TERMINATION, "Deserialization terminated early (not an error)" ) \ + f(ABORT_CUR_PHRASE, "Abort current communication phrase (not an error)" ) \ + f(THREAD_EXCEPTION, "Java exception from Thread::current()" ) \ + f(UNKNOWN, "Unknown error" ) \ + + +#define REGISTER_JB_ERROR_CODE_OPPOSITE_IDS(err_name, human_readable) err_name, +#define REGISTER_JB_ERROR_CODES(err_name, human_readable) err_name = -(int)ErrCodeOpposite::err_name, + +/** + * Defines all the possible error codes for the jbooster network communication. + * We define all the jbooster error codes as negative integers as C standard + * already defines some positive errnos. + * + * @see static const char* errno_to_string (int e, bool short_text) + */ +class JBErr { +private: + enum class ErrCodeOpposite: int { + PLACE_HOLDER_OF_ZERO, + JB_ERROR_CODES(REGISTER_JB_ERROR_CODE_OPPOSITE_IDS) + PLACE_HOLDER_END + }; + + static const int _err_cnt = (int) ErrCodeOpposite::PLACE_HOLDER_END - 1; + +public: + enum: int { + JB_ERROR_CODES(REGISTER_JB_ERROR_CODES) + PLACE_HOLDER_END + }; + + static int err_cnt() { return _err_cnt; } + + static const char* err_name(int err_code); + static const char* err_message(int err_code); +}; + +#undef REGISTER_JB_ERROR_CODE_OPPOSITE_IDS +#undef REGISTER_JB_ERROR_CODES + +#endif // SHARE_JBOOSTER_NET_ERRORCODE_HPP diff --git a/src/hotspot/share/jbooster/net/message.hpp b/src/hotspot/share/jbooster/net/message.hpp new file mode 100644 index 000000000..47d2634e2 --- /dev/null +++ b/src/hotspot/share/jbooster/net/message.hpp @@ -0,0 +1,89 @@ +/* + * 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 SHARE_JBOOSTER_NET_MESSAGE_HPP +#define SHARE_JBOOSTER_NET_MESSAGE_HPP + +#include "jbooster/net/messageBuffer.hpp" +#include "jbooster/net/messageType.hpp" +#include "jbooster/net/netCommon.hpp" + +class Message: public MessageConst { + // see MessageConst for the message format +private: + struct { + uint32_t msg_size; + MessageType msg_type; + } _meta; + + MessageBuffer _buf; + + uint32_t _overflow_offset; + uint32_t _overflow_size; + +private: + int serialize_inner(); + int deserialize_inner(); + template + int serialize_inner(const Arg* const arg, const Args* const... args); + template + int deserialize_inner(Arg* const& arg, Args* const&... args); + +public: + Message(SerializationMode smode, CommunicationStream* stream = nullptr): + _buf(smode, stream), + _overflow_offset(0), + _overflow_size(0) {} + + 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()); } + MessageType msg_type() const { return _meta.msg_type; } + void set_msg_type(MessageType type) { _meta.msg_type = type; } + + char* buf_beginning() const { return _buf.buf(); } + uint32_t buf_size() const { return _buf.buf_size(); } + + uint32_t cur_buf_offset() { return _buf.cur_offset(); } + void set_cur_buf_offset_after_meta() { _buf.set_cur_offset(meta_size); } + + bool has_overflow() { return _overflow_size > 0; } + void set_overflow(uint32_t offset, uint32_t size); + uint32_t move_overflow(); + + void expand_buf_if_needed(uint32_t required_size, uint32_t copy_size) { + _buf.expand_if_needed(required_size, copy_size); + } + + uint32_t deserialize_size_only() { return *((uint32_t*)_buf.buf()); } + + template + int serialize(const Args* const... args); + template + int deserialize(Args* const&... args); + + void serialize_meta(); + void deserialize_meta(); +}; + +#endif // SHARE_JBOOSTER_NET_MESSAGE_HPP diff --git a/src/hotspot/share/jbooster/net/message.inline.hpp b/src/hotspot/share/jbooster/net/message.inline.hpp new file mode 100644 index 000000000..5b5add47f --- /dev/null +++ b/src/hotspot/share/jbooster/net/message.inline.hpp @@ -0,0 +1,92 @@ +/* + * 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 SHARE_JBOOSTER_NET_MESSAGE_INLINE_HPP +#define SHARE_JBOOSTER_NET_MESSAGE_INLINE_HPP + +#include + +#include "jbooster/net/message.hpp" +#include "jbooster/net/messageBuffer.inline.hpp" +#include "jbooster/net/serialization.hpp" + +inline void Message::set_overflow(uint32_t offset, uint32_t size) { + guarantee(!has_overflow(), "handle the existing overflow first"); + assert(size > 0 && offset + size <= _buf.buf_size(), "sanity"); + _overflow_offset = offset; + _overflow_size = size; +} + +inline uint32_t Message::move_overflow() { + assert(has_overflow(), "sanity"); + uint32_t size = _overflow_size; + memmove(_buf.buf(), _buf.buf() + _overflow_offset, _overflow_size); + _overflow_offset = _overflow_size = 0; + return size; +} + +inline int Message::serialize_inner() { + return 0; +} + +inline int Message::deserialize_inner() { + return 0; +} + +template +inline int Message::serialize_inner(const Arg* const arg, const Args* const... args) { + JB_RETURN(_buf.serialize_with_meta(arg)); + return serialize_inner(args...); +} + +template +inline int Message::deserialize_inner(Arg* const& arg, Args* const&... args) { + JB_RETURN(_buf.deserialize_with_meta(arg)); + return deserialize_inner(args...); +} + +template +inline int Message::serialize(const Args* const... args) { + return serialize_inner(args...); +} + +template +inline int Message::deserialize(Args* const&... args) { + return deserialize_inner(args...); +} + +inline void Message::serialize_meta() { + _buf.set_cur_offset(0); + _buf.serialize_no_meta(_meta.msg_size); + _buf.serialize_no_meta(_meta.msg_type); + assert(cur_buf_offset() == meta_size, "sanity"); +} + +inline void Message::deserialize_meta() { + _buf.set_cur_offset(0); + _buf.deserialize_ref_no_meta(_meta.msg_size); + _buf.deserialize_ref_no_meta(_meta.msg_type); + assert(cur_buf_offset() == meta_size, "sanity"); +} + +#endif // SHARE_JBOOSTER_NET_MESSAGE_INLINE_HPP diff --git a/src/hotspot/share/jbooster/net/messageBuffer.cpp b/src/hotspot/share/jbooster/net/messageBuffer.cpp new file mode 100644 index 000000000..4673bb784 --- /dev/null +++ b/src/hotspot/share/jbooster/net/messageBuffer.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#include "jbooster/net/messageBuffer.inline.hpp" + +MessageBuffer::MessageBuffer(SerializationMode smode, CommunicationStream* stream): + _smode(smode), + _buf_size(_default_buf_size), + _buf(NEW_C_HEAP_ARRAY(char, _buf_size, mtJBooster)), + _cur_offset(0), + _stream(stream) {} + +MessageBuffer::~MessageBuffer() { + FREE_C_HEAP_ARRAY(char, _buf); +} + +/** + * Round capacity to power of 2, at most limit. + * Make sure that 0 < _buf_size < required_size <= 0x80000000. + */ +uint32_t MessageBuffer::calc_new_buf_size(uint32_t required_size) { + guarantee(required_size <= 0x80000000, "Message size is too big"); + uint32_t v = required_size - 1; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return v + 1; +} + +void MessageBuffer::expand_buf(uint32_t required_size, uint32_t copy_size) { + 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); + memcpy(new_buf, old_buf, copy_size); + + _buf = new_buf; + _buf_size = new_buf_size; + FREE_C_HEAP_ARRAY(char, old_buf); +} diff --git a/src/hotspot/share/jbooster/net/messageBuffer.hpp b/src/hotspot/share/jbooster/net/messageBuffer.hpp new file mode 100644 index 000000000..aaa8e7c3b --- /dev/null +++ b/src/hotspot/share/jbooster/net/messageBuffer.hpp @@ -0,0 +1,114 @@ +/* + * 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 SHARE_JBOOSTER_NET_MESSAGEBUFFER_HPP +#define SHARE_JBOOSTER_NET_MESSAGEBUFFER_HPP + +#include "jbooster/net/netCommon.hpp" +#include "memory/allocation.hpp" + +class CommunicationStream; + +class SerializationMode { +public: + enum Value: uint8_t { + BOTH, + SERIALIZE, + DESERIALIZE + }; + +private: + Value _mode; + +public: + SerializationMode(Value mode): _mode(mode) {} + + void set(Value mode) { _mode = mode; } + + bool operator == (const SerializationMode other) const { + return _mode == other._mode; + } + + bool can_serialize() const { return _mode != DESERIALIZE; } + bool can_deserialize() const { return _mode != SERIALIZE; } + + void assert_can_serialize() const NOT_DEBUG_RETURN; + void assert_can_deserialize() const NOT_DEBUG_RETURN; +}; + +class MessageBuffer final: public StackObj { + friend class Message; + +private: + const uint32_t _default_buf_size = 4 * 1024; // 4k + + SerializationMode _smode; + uint32_t _buf_size; + char* _buf; + uint32_t _cur_offset; + CommunicationStream* const _stream; + +private: + static uint32_t calc_new_buf_size(uint32_t required_size); + void expand_buf(uint32_t required_size, uint32_t copy_size); + +public: + MessageBuffer(SerializationMode smode, CommunicationStream* stream = nullptr); + ~MessageBuffer(); + + char* buf() const { return _buf; } + uint32_t buf_size() const { return _buf_size; } + + uint32_t cur_offset() const { return _cur_offset; } + void set_cur_offset(uint32_t offset) { _cur_offset = offset; } + void skip_cur_offset(uint32_t offset) { _cur_offset += offset; } + void reset_cur_offset() { _cur_offset = 0u; } + + char* cur_buf_ptr() const { return _buf + _cur_offset; } + + CommunicationStream* stream() const { return _stream; } + + void expand_if_needed(uint32_t required_size, uint32_t copy_size) { + if (_buf_size < required_size) { + expand_buf(required_size, copy_size); + } + } + + // serializers + int serialize_memcpy(const void* from, uint32_t arg_size); + template + int serialize_no_meta(const Arg& arg); + template + int serialize_with_meta(const Arg* arg_ptr); + + // deserializers + int deserialize_memcpy(void* to, uint32_t arg_size); + template + int deserialize_ref_no_meta(Arg& arg); + template + int deserialize_ptr_no_meta(Arg*& arg_ptr); + template + int deserialize_with_meta(Arg* const& arg_ptr); +}; + +#endif // SHARE_JBOOSTER_NET_MESSAGEBUFFER_HPP diff --git a/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp b/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp new file mode 100644 index 000000000..c7dc0986f --- /dev/null +++ b/src/hotspot/share/jbooster/net/messageBuffer.inline.hpp @@ -0,0 +1,137 @@ +/* + * 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 SHARE_JBOOSTER_NET_MESSAGEBUFFER_INLINE_HPP +#define SHARE_JBOOSTER_NET_MESSAGEBUFFER_INLINE_HPP + +#include "jbooster/net/messageBuffer.hpp" +#include "jbooster/net/serialization.hpp" +#include "logging/log.hpp" + +#ifdef ASSERT +inline void SerializationMode::assert_can_serialize() const { + assert(can_serialize(), "serialization only"); +} + +inline void SerializationMode::assert_can_deserialize() const { + assert(can_deserialize(), "deserialization only"); +} +#endif + +inline int MessageBuffer::serialize_memcpy(const void* from, uint32_t arg_size) { + _smode.assert_can_serialize(); + assert(from != nullptr, "sanity"); + uint32_t nxt_offset = _cur_offset + arg_size; + expand_if_needed(nxt_offset, _cur_offset); + memcpy((void*) (_buf + _cur_offset), from, arg_size); + _cur_offset = nxt_offset; + return 0; +} + +template +inline int MessageBuffer::serialize_no_meta(const Arg& arg) { + _smode.assert_can_serialize(); + return SerializationImpl::serialize(*this, arg); +} + +template +inline int MessageBuffer::serialize_with_meta(const Arg* arg_ptr) { + _smode.assert_can_serialize(); + if (arg_ptr == nullptr) { + return serialize_no_meta(MessageConst::NULL_PTR); + } + const Arg& arg = *arg_ptr; + uint32_t meta_offset = _cur_offset; + skip_cur_offset(MessageConst::arg_meta_size); + expand_if_needed(_cur_offset, _cur_offset); + JB_RETURN(serialize_no_meta(arg)); + + // 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)); + return 0; +} + +inline int MessageBuffer::deserialize_memcpy(void* to, uint32_t arg_size) { + _smode.assert_can_deserialize(); + assert(to != nullptr, "sanity"); + uint32_t nxt_offset = _cur_offset + 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; + } + memcpy(to, (void*) (_buf + _cur_offset), arg_size); + _cur_offset = nxt_offset; + return 0; +} + +template +inline int MessageBuffer::deserialize_ref_no_meta(Arg& arg) { + _smode.assert_can_deserialize(); + return SerializationImpl::deserialize_ref(*this, arg); +} + +template +inline int MessageBuffer::deserialize_ptr_no_meta(Arg*& arg_ptr) { + _smode.assert_can_deserialize(); + assert(arg_ptr == nullptr, "memory will be allocated by this deserializer"); + return SerializationImpl::deserialize_ptr(*this, arg_ptr); +} + +template +inline int MessageBuffer::deserialize_with_meta(Arg* const& arg_ptr) { + _smode.assert_can_deserialize(); + uint32_t arg_size; + JB_RETURN(deserialize_ref_no_meta(arg_size)); + uint32_t arg_begin = _cur_offset; + int jb_err; + if (arg_ptr == nullptr) { + if (arg_size == MessageConst::NULL_PTR) { + return 0; + } + Arg*& nonconst_ptr = const_cast(arg_ptr); + jb_err = deserialize_ptr_no_meta(nonconst_ptr); + if (jb_err != JBErr::DESER_TERMINATION) JB_RETURN(jb_err); + } else { + assert(arg_size != MessageConst::NULL_PTR, "nullptr cannot be assigned to \"Args* const&\""); + jb_err = deserialize_ref_no_meta(*arg_ptr); + if (jb_err != JBErr::DESER_TERMINATION) JB_RETURN(jb_err); + } + if (_cur_offset - arg_begin != arg_size) { + if (jb_err == JBErr::DESER_TERMINATION && (_cur_offset - arg_begin < arg_size)) { + _cur_offset = arg_begin + arg_size; + return 0; + } + const char* type_name = DebugUtils::type_name(); + log_warning(jbooster, rpc)("The arg size does 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); + return JBErr::BAD_ARG_SIZE; + } + return 0; +} + +#endif // SHARE_JBOOSTER_NET_MESSAGEBUFFER_INLINE_HPP diff --git a/src/hotspot/share/jbooster/net/messageType.cpp b/src/hotspot/share/jbooster/net/messageType.cpp new file mode 100644 index 000000000..3d0065f2b --- /dev/null +++ b/src/hotspot/share/jbooster/net/messageType.cpp @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include "jbooster/net/messageType.hpp" + +#define REGISTER_MESSAGE_TYPE_STR(type_name, human_readable) #type_name, + +static const char* msg_type_name_arr[] = { + MESSAGE_TYPES(REGISTER_MESSAGE_TYPE_STR) + "END_OF_MESSAGE_TYPE" +}; + +const char* msg_type_name(MessageType meg_type) { + int mt = (int)meg_type; + if (mt < 0) return "UNKNOWN_NEGATIVE"; + if (mt >= (int)(sizeof(msg_type_name_arr) / sizeof(char*))) return "UNKNOWN_POSITIVE"; + return msg_type_name_arr[(int)meg_type]; +} + +#undef REGISTER_MESSAGE_TYPE_STR diff --git a/src/hotspot/share/jbooster/net/messageType.hpp b/src/hotspot/share/jbooster/net/messageType.hpp new file mode 100644 index 000000000..f8cb8f3e6 --- /dev/null +++ b/src/hotspot/share/jbooster/net/messageType.hpp @@ -0,0 +1,81 @@ +/* + * 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 SHARE_JBOOSTER_NET_MESSAGETYPE_HPP +#define SHARE_JBOOSTER_NET_MESSAGETYPE_HPP + +#include "utilities/globalDefinitions.hpp" // for uint16_t + +#define MESSAGE_TYPES(f) \ + /* session/stream meta related */ \ + f(ClientSessionMeta, "from client" ) \ + f(ClientStreamMeta, "from client" ) \ + f(ClientSessionMetaAgain, "from client" ) \ + \ + /* task related */ \ + f(EndOfCurrentPhase, "from both" ) \ + f(NoMoreRequests, "from client" ) \ + f(ClientDaemonTask, "from client" ) \ + f(CacheFilesSyncTask, "from client" ) \ + f(LazyAOTCompilationTask, "from client" ) \ + \ + /* cache file related */ \ + f(GetLazyAOTCache, "from client" ) \ + f(GetAggressiveCDSCache, "from client" ) \ + f(GetClassLoaderResourceCache, "from client" ) \ + f(CacheAggressiveCDS, "from server" ) \ + f(CacheClassLoaderResource, "from server" ) \ + \ + /* Lazy AOT related */ \ + f(ClassLoaderLocators, "from server" ) \ + f(DataOfClassLoaders, "from server" ) \ + f(KlassLocators, "from server" ) \ + f(Profilinginfo, "from server" ) \ + f(ArrayKlasses, "from server" ) \ + f(DataOfKlasses, "from server" ) \ + f(MethodLocators, "from server" ) \ + \ + /* others */ \ + f(FileSegment, "from both" ) \ + f(Heartbeat, "from both" ) \ + f(AOTRelatedClassNames, "from client" ) \ + f(AOTCompilationResult, "from server" ) \ + f(AbortCompilation, "from both" ) \ + f(CompilationFailure, "from both" ) \ + f(UnexpectedMessageType, "from both" ) \ + f(UnsupportedClient, "from server" ) \ + f(Unknown, "from both" ) \ + + +#define REGISTER_MESSAGE_TYPE_ENUM(type_name, human_readable) type_name, + +enum class MessageType: uint16_t { + MESSAGE_TYPES(REGISTER_MESSAGE_TYPE_ENUM) + END_OF_MESSAGE_TYPE +}; + +#undef REGISTER_MESSAGE_TYPE_ENUM + +const char* msg_type_name(MessageType meg_type); + +#endif // SHARE_JBOOSTER_NET_MESSAGETYPE_HPP diff --git a/src/hotspot/share/jbooster/net/netCommon.hpp b/src/hotspot/share/jbooster/net/netCommon.hpp new file mode 100644 index 000000000..8706b7f22 --- /dev/null +++ b/src/hotspot/share/jbooster/net/netCommon.hpp @@ -0,0 +1,154 @@ +/* + * 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 SHARE_JBOOSTER_NET_NETCOMMON_HPP +#define SHARE_JBOOSTER_NET_NETCOMMON_HPP + +#include "jbooster/net/errorCode.hpp" +#include "jbooster/net/messageType.hpp" + +// C++ exceptions are disabled in Hotspot (see hotspot-style.md). +// And Thread::current() is not always available in our cases so we do not choose +// to use the tools in exceptions.hpp. +// Instead we choose to return the error code just like ZGC does (see zErrno.hpp). +// +// Here is a simple implementation of the "try&catch" statements. They are used +// only for CommunicationStream. How to use: +// +// // try (no support for "break" or "continue" when in a for/switch block) +// JB_TRY { +// JB_THROW(func()); +// if (...) JB_THROW(err_code1); +// while (...) {...} JB_THROW(JB_ERR); +// } JB_TRY_END +// +// // or use this (supports "break" and "continue" when in a for/switch block)) +// JB_TRY_BREAKABLE { +// JB_THROW(func()); +// if (...) JB_THROW(err_code1); +// while (...) {...} JB_THROW(JB_ERR); +// if (...) continue; else break; +// } JB_TRY_BREAKABLE_END +// +// // catch (do not insert any code between "JB_TRY_END" and "JB_CATCH") +// JB_CATCH(err_code2) {...} +// JB_CATCH(err_code3, err_code4, err_code5) {...} +// JB_CATCH_REST() { +// if (JB_ERR != err_code6) break; +// else continue; +// } JB_CATCH_END; +// +// Note1: Use JB_TRY_BREAKABLE if you are in "while/for/switch" statements and want +// to use "break" or "continue" in our "try" block. Use JB_TRY if not. +// Note2: Always use JB_THROW in a JB_TRY or JB_TRY_BREAKABLE block! +// It does not support being thrown to the upper layer. +// Note3: We use "do {...} while (false);" to implement the "try/catch" statements. +// So if you have another "while/for/switch" statement inside the JB_TRY, +// insert a JB_THROW(JB_ERR) after the statement. +#define JB_ERR __jbooster_errcode__ + +// do not call JB_INNER_ macros +#define JB_INNER_CONTROL __jbooster_control__ +#define JB_INNER_NORMAL 0 +#define JB_INNER_CONTINUE 1 +#define JB_INNER_BREAK 2 + +#define JB_INNER_CMP_1(e) JB_ERR == (e) +#define JB_INNER_CMP_2(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_1(__VA_ARGS__) +#define JB_INNER_CMP_3(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_2(__VA_ARGS__) +#define JB_INNER_CMP_4(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_3(__VA_ARGS__) +#define JB_INNER_CMP_5(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_4(__VA_ARGS__) +#define JB_INNER_CMP_6(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_5(__VA_ARGS__) +#define JB_INNER_CMP_7(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_6(__VA_ARGS__) +#define JB_INNER_CMP_8(e, ...) JB_INNER_CMP_1(e) || JB_INNER_CMP_7(__VA_ARGS__) + +#define JB_INNER_CMP_GET(_1, _2, _3, _4, _5, _6, _7, _8, V, ...) V +#define JB_INNER_CMP(...) JB_INNER_CMP_GET(__VA_ARGS__, \ + JB_INNER_CMP_8, \ + JB_INNER_CMP_7, \ + JB_INNER_CMP_6, \ + JB_INNER_CMP_5, \ + JB_INNER_CMP_4, \ + JB_INNER_CMP_3, \ + JB_INNER_CMP_2, \ + JB_INNER_CMP_1) \ + (__VA_ARGS__) + +// the try/catch statements +#define JB_TRY { int JB_ERR = 0; do +#define JB_TRY_END while (false); \ + if (JB_ERR == 0) { /* do nothing */ } + +#define JB_TRY_BREAKABLE { int JB_ERR = 0; \ + uint8_t JB_INNER_CONTROL = JB_INNER_BREAK; \ + do { +#define JB_TRY_BREAKABLE_END JB_INNER_CONTROL = JB_INNER_NORMAL; \ + break; \ + } while ((JB_INNER_CONTROL = JB_INNER_CONTINUE) \ + != JB_INNER_CONTINUE); \ + if (JB_ERR == 0) { \ + if (JB_INNER_CONTROL != JB_INNER_NORMAL) { \ + if (JB_INNER_CONTROL == JB_INNER_BREAK) break; \ + else continue; \ + } \ + } + +// Here we use "expression" instead of "(expression)" to be compatible with "CHECK" +// in exceptions.hpp. +#define JB_THROW(expression) { JB_ERR = expression; \ + if (JB_ERR != 0) break; \ + } + +#define JB_CATCH(...) else if (JB_INNER_CMP(__VA_ARGS__)) +#define JB_CATCH_REST() else +#define JB_CATCH_END } + +// Here we use "expression" instead of "(expression)" to be compatible with "CHECK" +// in exceptions.hpp. +#define JB_RETURN(expression) { int JB_ERR = expression; \ + if (JB_ERR != 0) return JB_ERR; \ + } + +// End of the definition of JB_TRY/JB_CATCH. + +class MessageConst { +public: + 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 | + */ + meta_size = sizeof(uint32_t) + sizeof(MessageType), + /** + * The layout of each argument in the buffer: + * | arg_size | ... (payload of the argument) ... | + * | 4 bytes | arg_size bytes | + */ + arg_meta_size = sizeof(uint32_t), + + NULL_PTR = 0xffffffff // @see ArrayWrapper::NULL_PTR + }; +}; + +#endif // SHARE_JBOOSTER_NET_NETCOMMON_HPP diff --git a/src/hotspot/share/jbooster/net/rpcCompatibility.cpp b/src/hotspot/share/jbooster/net/rpcCompatibility.cpp new file mode 100644 index 000000000..7f849a6fe --- /dev/null +++ b/src/hotspot/share/jbooster/net/rpcCompatibility.cpp @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#include "jbooster/dataTransmissionUtils.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/jClientArguments.hpp" +#include "jbooster/jClientVMFlags.hpp" +#include "jbooster/net/clientStream.hpp" +#include "jbooster/net/communicationStream.hpp" +#include "jbooster/net/errorCode.hpp" +#include "jbooster/net/message.hpp" +#include "jbooster/net/messageBuffer.hpp" +#include "jbooster/net/rpcCompatibility.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/net/serverStream.hpp" + +static constexpr uint32_t calc_new_hash(uint32_t old_hash, uint32_t ele_hash) { + return 31 * old_hash + ele_hash; +} + +template +static constexpr uint32_t calc_class_hash() { + uint32_t hash = (uint32_t) sizeof(T); + return hash ^ (hash >> 3); +} + +template +static constexpr uint32_t calc_classes_hash() { + return calc_class_hash(); +} + +template +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() { + return calc_classes_hash< + JBoosterManager, JClientVMFlags, JClientArguments, + JBErr, Message, MessageBuffer, + CommunicationStream, ClientStream, ServerStream, + ArrayWrapper, MemoryWrapper, StringWrapper, FileWrapper, + ClassLoaderKey, ClassLoaderChain, ClassLoaderLocator, KlassLocator, MethodLocator, ProfileDataCollector + >(); +} + +uint32_t RpcCompatibility::magic_num() { + return calc_magic_num(); +} diff --git a/src/hotspot/share/jbooster/net/rpcCompatibility.hpp b/src/hotspot/share/jbooster/net/rpcCompatibility.hpp new file mode 100644 index 000000000..da4283d41 --- /dev/null +++ b/src/hotspot/share/jbooster/net/rpcCompatibility.hpp @@ -0,0 +1,50 @@ +/* + * 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 SHARE_JBOOSTER_NET_COMPATIBILITY_HPP +#define SHARE_JBOOSTER_NET_COMPATIBILITY_HPP + +#include "jbooster/net/serializationWrappers.hpp" +#include "memory/allocation.hpp" + +/** + * Check whether the server is compatible with the client. + * If the magic numbers are different, then the RPC formats may be different. + */ +class RpcCompatibility: public StackObj { +public: + static uint32_t magic_num(); + + int serialize(MessageBuffer& buf) const { + return buf.serialize_no_meta(magic_num()); + } + int deserialize(MessageBuffer& buf) { + uint32_t hash; + JB_RETURN(buf.deserialize_ref_no_meta(hash)); + return ((hash == magic_num()) ? 0 : JBErr::INCOMPATIBLE_RPC); + } +}; + +DECLARE_SERIALIZER_INTRUSIVE(RpcCompatibility); + +#endif // SHARE_JBOOSTER_NET_COMPATIBILITY_HPP diff --git a/src/hotspot/share/jbooster/net/serialization.cpp b/src/hotspot/share/jbooster/net/serialization.cpp new file mode 100644 index 000000000..b0db65e68 --- /dev/null +++ b/src/hotspot/share/jbooster/net/serialization.cpp @@ -0,0 +1,333 @@ +/* + * 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. + */ + +#include "classfile/classFileStream.hpp" +#include "classfile/classLoadInfo.hpp" +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmClasses.hpp" +#include "jbooster/dataTransmissionUtils.hpp" +#include "jbooster/jbooster_globals.hpp" +#include "jbooster/net/messageBuffer.inline.hpp" +#include "jbooster/net/serialization.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "memory/resourceArea.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/instanceKlass.hpp" +#include "prims/jvmtiClassFileReconstituter.hpp" +#include "runtime/handles.inline.hpp" +#include "utilities/stringUtils.hpp" + +// ------------------ Serializer of C-style String (i.e., char*) ------------------- + +int SerializationImpl::serialize(MessageBuffer& buf, const Arg& arg) { + if (arg == nullptr) { + return buf.serialize_no_meta(MessageConst::NULL_PTR); + } + uint32_t str_len = strlen(arg); + JB_RETURN(buf.serialize_no_meta(str_len)); + return buf.serialize_memcpy(arg, str_len); +} + +int SerializationImpl::deserialize_ref(MessageBuffer& buf, Arg& arg) { + char* str = nullptr; + JB_RETURN(buf.deserialize_ref_no_meta(str)); + arg = str; + return 0; +} + +int SerializationImpl::serialize(MessageBuffer& buf, const Arg& arg) { + const char* str = arg; + return buf.serialize_no_meta(arg); +} + +int SerializationImpl::deserialize_ref(MessageBuffer& buf, Arg& arg) { + guarantee(arg == nullptr, "do not pre-allocate the string"); + uint32_t str_len; + JB_RETURN(buf.deserialize_ref_no_meta(str_len)); + if (str_len == MessageConst::NULL_PTR) { + return 0; + } + char* s = NEW_RESOURCE_ARRAY(char, str_len + 1); + JB_RETURN(buf.deserialize_memcpy(s, str_len)); + s[str_len] = '\0'; + arg = s; + return 0; +} + +// ----------------------------- Serializer for Symbol ----------------------------- + +int SerializationImpl::serialize(MessageBuffer& buf, const Symbol& arg) { + JB_RETURN(buf.serialize_no_meta(arg.utf8_length())); + return buf.serialize_memcpy(arg.base(), arg.utf8_length()); +} + +int SerializationImpl::deserialize_ptr(MessageBuffer& buf, Symbol*& arg_ptr) { + int len; + JB_RETURN(buf.deserialize_ref_no_meta(len)); + arg_ptr = SymbolTable::new_symbol(buf.cur_buf_ptr(), len); + buf.skip_cur_offset((uint32_t) len); + return 0; +}; + +// ------------------------- Serializer for InstanceKlass -------------------------- +// We rebuild the InstanceKlass based on the following method: +// InstanceKlass* SystemDictionary::resolve_from_stream( +// ClassFileStream* st, Symbol* class_name, Handle class_loader, +// const ClassLoadInfo& cl_info, TRAPS) + +int SerializationImpl::serialize(MessageBuffer& buf, const InstanceKlass& arg) { + assert(UseJBooster, "only for client klass"); + + // client-side klass pointer + JB_RETURN(buf.serialize_no_meta((uintptr_t) &arg)); + + // [arg of resolve_from_stream] Symbol* class_name + JB_RETURN(buf.serialize_with_meta(arg.name())); + + // [arg of resolve_from_stream] Handle class_loader + ClassLoaderLocator cll(arg.class_loader_data()); + JB_RETURN(buf.serialize_no_meta(cll)); + + // [arg of resolve_from_stream] const ClassLoadInfo& cl_info (ignored for now) + JB_RETURN(buf.serialize_no_meta((int) 0x1c2d3e4f)); + + // [arg of resolve_from_stream] ClassFileStream* st + { + JavaThread* THREAD = JavaThread::current(); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + + char* cf_buf = nullptr; + uint32_t cf_size = 0; + + bool should_send_class_file = !arg.class_loader_data()->is_boot_class_loader_data(); + if (should_send_class_file) { + InstanceKlass* ik = const_cast(&arg); + JvmtiClassFileReconstituter reconstituter(ik); + if (reconstituter.get_error() == JVMTI_ERROR_NONE) { + cf_buf = (char*) reconstituter.class_file_bytes(); + cf_size = (uint32_t) reconstituter.class_file_size(); + } + } + MemoryWrapper mw((void*) cf_buf, cf_size); + JB_RETURN(buf.serialize_no_meta(mw)); + } + + return 0; +} + +static InstanceKlass* resolve_instance_klass(Symbol* class_name, + ClassLoaderData* class_loader_data, + Handle protection_domain, + TRAPS) { + Handle class_loader(THREAD, class_loader_data->class_loader()); + Klass* result = SystemDictionary::resolve_or_null(class_name, class_loader, + protection_domain, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_warning(jbooster, serialization)("Failed to deserialize and resolve InstanceKlass \"%s\" " + "(loader=\"%s:%p\"): see stack trace.", + class_name->as_C_string(), + class_loader_data->loader_name(), + class_loader_data); + LogTarget(Warning, jbooster, serialization) lt; + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + return nullptr; + } else if (result == nullptr) { + log_warning(jbooster, serialization)("Failed to deserialize InstanceKlass \"%s\" " + "(loader=\"%s:%p\"): no class file found.", + class_name->as_C_string(), + class_loader_data->loader_name(), + class_loader_data); + return nullptr; + } + return InstanceKlass::cast(result); +} + +static InstanceKlass* rebuild_instance_klass(Symbol* class_name, + ClassLoaderData* class_loader_data, + ClassLoadInfo& cl_info, + u1* class_file_buf, + int class_file_size, + TRAPS) { + Handle class_loader(THREAD, class_loader_data->class_loader()); + ClassFileStream st(class_file_buf, class_file_size, "__VM_JBoosterDeserialization__"); + InstanceKlass* result = SystemDictionary::resolve_from_stream(&st, class_name, class_loader, + cl_info, THREAD); + if (result == nullptr || HAS_PENDING_EXCEPTION) { + assert(HAS_PENDING_EXCEPTION, "sanity"); + Handle first_ex(THREAD, THREAD->pending_exception()); + if (PENDING_EXCEPTION->is_a(vmClasses::LinkageError_klass())) { + // The class loader attempted duplicate class definition for the target class. + Handle ex(THREAD, PENDING_EXCEPTION); + const char* efile = ((ThreadShadow*)THREAD)->exception_file(); + int eline = ((ThreadShadow*)THREAD)->exception_line(); + CLEAR_PENDING_EXCEPTION; + Klass* found = SystemDictionary::find_instance_klass(class_name, class_loader, Handle()); + if (found != nullptr) { + result = InstanceKlass::cast(found); + } + } + // still null + if (result == nullptr) { + LogTarget(Warning, jbooster, serialization) lt; + if (lt.is_enabled()) { + lt.print("Failed to deserialize and rebuild InstanceKlass \"%s\" " + "(loader=\"%s:%p\"): see stack trace.", + class_name->as_C_string(), + class_loader_data->loader_name(), + class_loader_data); + if (HAS_PENDING_EXCEPTION) { + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } else { + lt.print("(A LinkageError but not caused by duplicate definition)"); + LogStream ls(lt); + java_lang_Throwable::print_stack_trace(first_ex, &ls); + } + } + } + CLEAR_PENDING_EXCEPTION; + } + return result; +} + +int SerializationImpl::deserialize_ptr(MessageBuffer& buf, InstanceKlass*& arg_ptr) { + assert(AsJBooster, "only called on the server"); + JavaThread* THREAD = JavaThread::current(); + ResourceMark rm(THREAD); + + // client-side InstanceKlass pointer + uintptr_t client_klass; + JB_RETURN(buf.deserialize_ref_no_meta(client_klass)); + + // [arg of resolve_from_stream] Symbol* class_name + Symbol* class_name = nullptr; + JB_RETURN(buf.deserialize_with_meta(class_name)); + TempNewSymbol class_name_wrapper = class_name; + + // [arg of resolve_from_stream] Handle class_loader + ClassLoaderLocator cll; + JB_RETURN(buf.deserialize_ref_no_meta(cll)); + if (cll.class_loader_data() == nullptr) { + log_warning(jbooster, serialization)("Failed to deserialize InstanceKlass \"%s\": " + "cannot deserialize class loader " INTPTR_FORMAT ".", + class_name->as_C_string(), cll.client_cld_address()); + return JBErr::DESER_TERMINATION; + } + ClassLoaderData* class_loader_data = cll.class_loader_data(); + + // [arg of resolve_from_stream] const ClassLoadInfo& cl_info (ignored for now) + int placeholder_num; + JB_RETURN(buf.deserialize_ref_no_meta(placeholder_num)); + if (placeholder_num != 0x1c2d3e4f) { + log_warning(jbooster, serialization)("Failed to deserialize klass \"%s\": " + "wrong placeholder_num: %d.", + class_name->as_C_string(), placeholder_num); + return JBErr::BAD_ARG_DATA; + } + Handle protection_domain; + ClassLoadInfo cl_info(protection_domain); + + // [arg of resolve_from_stream] ClassFileStream* st + MemoryWrapper mw; + JB_RETURN(buf.deserialize_ref_no_meta(mw)); + + InstanceKlass* res = nullptr; + if (mw.is_null()) { + res = resolve_instance_klass(class_name, class_loader_data, + cl_info.protection_domain(), THREAD); + } else { + res = rebuild_instance_klass(class_name, class_loader_data, cl_info, + (u1*) mw.get_memory(), (int) mw.size(), THREAD); + } + if (res != nullptr) { + JClientSessionData* session_data = ((ServerStream*) buf.stream())->session_data(); + session_data->add_klass_address((address) client_klass, (address) res, THREAD); + arg_ptr = res; + } + return 0; +} + +// --------------------------- Serializer for ArrayKlass --------------------------- +// We rebuild the klass by: +// SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, +// Handle protection_domain, TRAPS) + +int SerializationImpl::serialize(MessageBuffer& buf, const ArrayKlass& arg) { + assert(UseJBooster, "only for client klass"); + + // client-side klass pointer + JB_RETURN(buf.serialize_no_meta((uintptr_t) &arg)); + + // class_name + JB_RETURN(buf.serialize_with_meta(arg.name())); + + // class loader + ClassLoaderLocator cll(arg.class_loader_data()); + JB_RETURN(buf.serialize_no_meta(cll)); + + return 0; +} + +int SerializationImpl::deserialize_ptr(MessageBuffer& buf, ArrayKlass*& arg_ptr) { + assert(AsJBooster, "only called on the server"); + JavaThread* THREAD = JavaThread::current(); + ResourceMark rm(THREAD); + + // client-side ArrayKlass pointer + uintptr_t client_klass; + JB_RETURN(buf.deserialize_ref_no_meta(client_klass)); + + // class_name + Symbol* class_name = nullptr; + JB_RETURN(buf.deserialize_with_meta(class_name)); + TempNewSymbol class_name_wrapper = class_name; + + // class loader + ClassLoaderLocator cll; + JB_RETURN(buf.deserialize_ref_no_meta(cll)); + if (cll.class_loader_data() == nullptr) { + log_warning(jbooster, serialization)("Failed to deserialize ArrayKlass \"%s\": " + "cannot deserialize class loader " INTPTR_FORMAT ".", + class_name->as_C_string(), cll.client_cld_address()); + return JBErr::DESER_TERMINATION; + } + Handle class_loader(THREAD, cll.class_loader_data()->class_loader()); + + Klass* res = SystemDictionary::resolve_or_null(class_name, class_loader, Handle(), THREAD); + if (res == nullptr || HAS_PENDING_EXCEPTION) { + log_warning(jbooster, serialization)("Failed to deserialize ArrayKlass \"%s\" (loader={%s}): see stack trace.", + class_name->as_C_string(), cll.class_loader_data()->loader_name()); + LogTarget(Warning, jbooster, serialization) lt; + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } else { + JClientSessionData* session_data = ((ServerStream*) buf.stream())->session_data(); + session_data->add_klass_address((address) client_klass, (address) res, THREAD); + arg_ptr = ArrayKlass::cast(res); + } + return 0; +} diff --git a/src/hotspot/share/jbooster/net/serialization.hpp b/src/hotspot/share/jbooster/net/serialization.hpp new file mode 100644 index 000000000..f9a2996c5 --- /dev/null +++ b/src/hotspot/share/jbooster/net/serialization.hpp @@ -0,0 +1,187 @@ +/* + * 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. + */ + +// --------------------------------------------------------------------------------- +// This file implements serialization for the C++ classes in Hotspot. +// This file has nothing to do with the serialization of Java classes. +// --------------------------------------------------------------------------------- + +#ifndef SHARE_JBOOSTER_NET_SERIALIZATION_HPP +#define SHARE_JBOOSTER_NET_SERIALIZATION_HPP + +#include + +#include "jbooster/net/messageBuffer.hpp" +#include "jbooster/net/netCommon.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "memory/allocation.hpp" +#include "memory/resourceArea.hpp" +#include "utilities/globalDefinitions.hpp" + +// ----------------------------- Serializer Implements ----------------------------- +// template +// struct CppClassSerializerImpl { +// // Serialize the argument (invoked only if the arg is not null). +// static int serialize(MessageBuffer& buf, const Arg& arg); +// +// // There are two deserialization modes. You don't need to implement both. +// // * Implement the first one if the memory space of the arg has been +// // pre-allocated. +// // * Implement the second one if the memory space of the arg should be +// // allocated inside deserialization. +// static int deserialize_ref(MessageBuffer& buf, Arg& arg); +// static int deserialize_ptr(MessageBuffer& buf, Arg*& arg_ptr); +// }; + +// ------------------------ Unsupported Deserializer Macro ------------------------- + +#define CANNOT_DESERIALIZE_REFERENCE(Arg) \ + static int deserialize_ref(MessageBuffer& buf, Arg& arg) { \ + fatal("Reference deserialization is not supported: %s,", \ + DebugUtils::type_name()); \ + return 0; \ + } + +#define CANNOT_DESERIALIZE_POINTER(Arg) \ + static int deserialize_ptr(MessageBuffer& buf, Arg*& arg_ptr) { \ + fatal("Pointer deserialization is not supported: %s,", \ + DebugUtils::type_name()); \ + return 0; \ + } + +// ------------------------------ 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. + +template +struct SerializationImpl { + static int serialize(MessageBuffer& buf, const Arg& arg) { + return buf.serialize_memcpy(&arg, sizeof(Arg)); + } + + static int deserialize_ref(MessageBuffer& buf, Arg& arg) { + return buf.deserialize_memcpy(&arg, sizeof(Arg)); + } + + CANNOT_DESERIALIZE_POINTER(Arg); +}; + +// ----------------------- Intrusive Serializer Declaration ------------------------ + +#define DECLARE_SERIALIZER_INTRUSIVE(Arg) \ +template <> struct SerializationImpl { \ + static int serialize(MessageBuffer& buf, const Arg& arg) { \ + return arg.serialize(buf); \ + } \ + static int deserialize_ref(MessageBuffer& buf, Arg& arg) { \ + return arg.deserialize(buf); \ + } \ + CANNOT_DESERIALIZE_POINTER(Arg); \ +}; + +// --------------------- Non-Intrusive Serializer Declaration ---------------------- + +#define DECLARE_SERIALIZER_ONLY_REFERENCE(Arg) \ +class Arg; \ +template <> struct SerializationImpl { \ + static int serialize(MessageBuffer& buf, const Arg& arg); \ + static int deserialize_ref(MessageBuffer& buf, Arg& arg); \ + CANNOT_DESERIALIZE_POINTER(Arg); \ +}; + +#define DECLARE_SERIALIZER_ONLY_POINTER(Arg) \ +class Arg; \ +template <> struct SerializationImpl { \ + static int serialize(MessageBuffer& buf, const Arg& arg); \ + static int deserialize_ptr(MessageBuffer& buf, Arg*& arg_ptr); \ + CANNOT_DESERIALIZE_REFERENCE(Arg); \ +}; + +// Declare the classes that do not belong to JBooster below. +DECLARE_SERIALIZER_ONLY_POINTER(Symbol); +DECLARE_SERIALIZER_ONLY_POINTER(InstanceKlass); +DECLARE_SERIALIZER_ONLY_POINTER(ArrayKlass); + +// ------------------ Serializer of C-style String (i.e., char*) ------------------- +// Threre are 4 specializations of the template: +// - const char [arr_size] : serialize() only +// - char [arr_size] : both serialize() and deserialize() +// - const char * : both serialize() and deserialize() +// - char * : both serialize() and deserialize() +// They and StringWrapper are compatible with each other. +// +// **ATTENTION** Type `address` will be identified as `char*`. +// So if you want to send an `address`, cast it to an `uintptr_t`. + +template <> struct SerializationImpl { + using Arg = const char*; + static int serialize(MessageBuffer& buf, const Arg& arg); + static int deserialize_ref(MessageBuffer& buf, Arg& arg); + CANNOT_DESERIALIZE_POINTER(Arg); +}; + +template <> struct SerializationImpl { + using Arg = char*; + static int serialize(MessageBuffer& buf, const Arg& arg); + static int deserialize_ref(MessageBuffer& buf, Arg& arg); + CANNOT_DESERIALIZE_POINTER(Arg); +}; + +template struct SerializationImpl { + using Arg = const char[arr_size]; + static int serialize(MessageBuffer& buf, const Arg& arg) { + return buf.serialize_no_meta(arg); + } + + // Should not implement deserialize() here as the arg cannot be const. +}; + +template struct SerializationImpl { + using Arg = char[arr_size]; + static int serialize(MessageBuffer& buf, const Arg& arg) { + return buf.serialize_no_meta(arg); + } + + static int deserialize_ref(MessageBuffer& buf, Arg& arg) { + uint32_t str_len; + JB_RETURN(buf.deserialize_ref_no_meta(str_len)); + guarantee(arr_size >= (size_t) (str_len + 1), + "array index out of bounds: arr_size=" SIZE_FORMAT ", str_len=%u", + arr_size, str_len + 1); + guarantee(str_len != MessageConst::NULL_PTR, "cannot set array to null"); + char* p = arg; + JB_RETURN(buf.deserialize_memcpy(p, str_len)); + p[str_len] = '\0'; + return 0; + } + + CANNOT_DESERIALIZE_POINTER(Arg); +}; + +#endif // SHARE_JBOOSTER_NET_SERIALIZATION_HPP diff --git a/src/hotspot/share/jbooster/net/serializationWrappers.cpp b/src/hotspot/share/jbooster/net/serializationWrappers.cpp new file mode 100644 index 000000000..13db948f6 --- /dev/null +++ b/src/hotspot/share/jbooster/net/serializationWrappers.cpp @@ -0,0 +1,440 @@ +/* + * 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. + */ + +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/communicationStream.inline.hpp" +#include "jbooster/net/serializationWrappers.inline.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/stringUtils.hpp" + +// ------------------------- MemoryWrapper for Serializer -------------------------- + +MemoryWrapper::MemoryWrapper(const void* base, uint32_t size): + WrapperBase(SerializationMode::SERIALIZE), + _base(base), + _size(base == nullptr ? MessageConst::NULL_PTR : size), + _allocater(nullptr) { +} + +MemoryWrapper::MemoryWrapper(MemoryAllocater* allocater): + WrapperBase(SerializationMode::DESERIALIZE), + _base(nullptr), + _size(MessageConst::NULL_PTR), + _allocater(allocater) {} + +MemoryWrapper::~MemoryWrapper() { + if (!_free_memory || _base == nullptr) return; + FREE_C_HEAP_ARRAY(char*, _base); +} + +int MemoryWrapper::serialize(MessageBuffer& buf) const { + _smode.assert_can_serialize(); + JB_RETURN(buf.serialize_no_meta(_size)); + if (_base == nullptr) { + assert(_size == MessageConst::NULL_PTR, "sanity"); + return 0; + } + return buf.serialize_memcpy(_base, _size); +} + +int MemoryWrapper::deserialize(MessageBuffer& buf) { + _smode.assert_can_deserialize(); + assert(_base == nullptr, "can only be deserialized once"); + JB_RETURN(buf.deserialize_ref_no_meta(_size)); + if (_size == MessageConst::NULL_PTR) { + return 0; + } + void* mem; + if (_allocater != nullptr) { + mem = _allocater->alloc(_size); + } else { + _free_memory = true; + mem = (void*) NEW_C_HEAP_ARRAY(char, _size, mtJBooster); + } + buf.deserialize_memcpy(mem, _size); + _base = mem; + return 0; +} + +// ------------------------- StringWrapper for Serializer -------------------------- + +StringWrapper::StringWrapper(const char* str): + WrapperBase(SerializationMode::SERIALIZE), + _base(str), + _size(str == nullptr ? (uint32_t) MessageConst::NULL_PTR : (uint32_t) strlen(str)) {} + +StringWrapper::StringWrapper(const char* str, uint32_t size): + WrapperBase(SerializationMode::SERIALIZE), + _base(str), + _size(str == nullptr ? (uint32_t) MessageConst::NULL_PTR : size) { +#ifdef ASSERT + if (str != nullptr) { + assert(strlen(str) >= size, "sanity"); + } +#endif +} + +StringWrapper::StringWrapper(): + WrapperBase(SerializationMode::DESERIALIZE), + _base(nullptr), + _size(MessageConst::NULL_PTR) {} + +StringWrapper::~StringWrapper() { + if (!_free_memory || _base == nullptr) return; + FREE_C_HEAP_ARRAY(char, _base); +} + +int StringWrapper::serialize(MessageBuffer& buf) const { + _smode.assert_can_serialize(); + JB_RETURN(buf.serialize_no_meta(_size)); + if (_base == nullptr) { + assert(_size == MessageConst::NULL_PTR, "sanity"); + return 0; + } + assert(_base[_size] == '\0', "sanity"); + return buf.serialize_memcpy(_base, _size); +} + +int StringWrapper::deserialize(MessageBuffer& buf) { + _smode.assert_can_deserialize(); + assert(_base == nullptr, "can only be deserialized once"); + JB_RETURN(buf.deserialize_ref_no_meta(_size)); + if (_size == MessageConst::NULL_PTR) { + return 0; + } + char* mem = NEW_C_HEAP_ARRAY(char, _size + 1, mtJBooster); + JB_RETURN(buf.deserialize_memcpy(mem, _size)); + mem[_size] = '\0'; + _base = mem; + return 0; +} + +// -------------------------- FileWrapper for Serializer --------------------------- + +FileWrapper::FileWrapper(const char* file_path, SerializationMode smode): + WrapperBase(smode), + _file_path(file_path), // don't have to be copied + _file_size(MessageConst::NULL_PTR), + _tmp_file_path(nullptr), + _fd(-1), + _errno(0), + _handled_file_size(0), + _handled_once(false) { + if (smode == SerializationMode::SERIALIZE) { + init_for_serialize(); + } else if (smode == SerializationMode::DESERIALIZE) { + init_for_deserialize(); + } else { + fatal("smode should not be SerializationMode::BOTH"); + } +} + +FileWrapper::~FileWrapper() { + if (has_file_err()) { + if (_smode.can_serialize()) { + log_trace(jbooster, serialization)("The file \"%s\" is serialized as null: error=%s(\"%s\").", + _file_path, os::errno_name(_errno), os::strerror(_errno)); + } else { + log_trace(jbooster, serialization)("The file \"%s\" is not deserialized (by this thread): error=%s(\"%s\").", + _file_path, os::errno_name(_errno), os::strerror(_errno)); + } + } else if (!is_file_all_handled()) { + if (_smode.can_serialize()) { + log_warning(jbooster, serialization)("The file \"%s\" is not fully serialized.", + _file_path); + } else { + log_warning(jbooster, serialization)("The file \"%s\" is not fully deserialized and will be deleted.", + _tmp_file_path); + } + if (_fd >= 0) { + os::close(_fd); + _fd = -1; + if (_smode.can_deserialize()) { + FileUtils::remove(_tmp_file_path); + } + } + } + guarantee(_fd < 0, "sanity"); + StringUtils::free_from_heap(_tmp_file_path); +} + +void FileWrapper::init_for_serialize() { + errno = 0; + _fd = os::open(_file_path, O_BINARY | O_RDONLY, 0); + _errno = errno; + // We use lseek() instead of stat() to get the file size since we have opened the file. + if (_errno == 0 && _fd >= 0) { + _file_size = (uint32_t) os::lseek(_fd, 0, SEEK_END); + os::lseek(_fd, 0, SEEK_SET); + } else { + assert(_fd < 0 && _errno != 0, "sanity"); + } +} + +void FileWrapper::on_deser_tmp_file_opened() { + // The tmp file is successfully created and opened. + guarantee(_fd >= 0, "sanity"); + // Recheck if the target file already exists. Skip deserialization if the target file exists. + if (FileUtils::is_file(_file_path)) { + os::close(_fd); + _fd = -1; + FileUtils::remove(_tmp_file_path); + log_info(jbooster, serialization)("The target file of \"%s\" already exists. Skipped deserialization.", + _file_path); + // Use is_tmp_file_already_exists() to check later. + _errno = EEXIST; + } +} + +void FileWrapper::on_deser_tmp_file_exist() { + // The tmp file already exists. Failed to open the file. + // Use is_tmp_file_already_exists() to check later. + log_info(jbooster, serialization)("The tmp file of \"%s\" already exists. Skipped deserialization.", + _file_path); +} + +void FileWrapper::on_deser_dir_not_exist() { + // The folder does not exist. Create it. + assert(_fd < 0, "sanity"); + const int dir_buf_len = strlen(_file_path) + 1; + char* dir = NEW_C_HEAP_ARRAY(char, dir_buf_len, mtJBooster); + const char* r = strrchr(_file_path, FileUtils::separator()[0]); + if (r == nullptr) { + snprintf(dir, dir_buf_len, ".%s", FileUtils::separator()); + } else { + int len = r - _file_path; + memcpy(dir, _file_path, len); + dir[len] = '\0'; + } + FileUtils::mkdirs(dir); + FREE_C_HEAP_ARRAY(char, dir); +} + +void FileWrapper::init_for_deserialize() { + // Write to file ".tmp" and then rename to "". + _tmp_file_path = JBoosterManager::calc_tmp_cache_path(_file_path); + + const int max_retry_times = 2; + int retry_times = 0; + while (++retry_times <= max_retry_times) { + errno = 0; + _fd = JBoosterManager::create_and_open_tmp_cache_file(_tmp_file_path); + _errno = errno; + switch (_errno) { + case 0: + on_deser_tmp_file_opened(); + return; + case EEXIST: + on_deser_tmp_file_exist(); + return; + case ENOENT: + // No need to try to make dirs twice. + guarantee(retry_times == 1, "Failed to make dirs."); + on_deser_dir_not_exist(); + break; + default: + fatal("failed to open file \"%s\": %s", _tmp_file_path, os::strerror(_errno)); + break; + } + } +} + +uint32_t FileWrapper::get_size_to_send_this_time() const { + assert(_fd >= 0, "sanity"); + return MIN2(MAX_SIZE_PER_TRANS, _file_size - _handled_file_size); +} + +uint32_t FileWrapper::get_arg_meta_size_if_not_null() { + return sizeof(_file_size) + sizeof(MAX_SIZE_PER_TRANS); +} + +int FileWrapper::serialize(MessageBuffer& buf) const { + _smode.assert_can_serialize(); + guarantee(!is_file_all_handled(), "the file is all parsed"); + + // file size + JB_RETURN(buf.serialize_no_meta(_file_size)); + if (_file_size == MessageConst::NULL_PTR) { + assert(_fd < 0, "sanity"); + _handled_file_size = _file_size; + _handled_once = true; + return 0; + } + + // size to handle this time + uint32_t size_to_send = get_size_to_send_this_time(); + JB_RETURN(buf.serialize_no_meta(size_to_send)); + + // content (use low-level APIs to save a memcpy) + buf.expand_if_needed(buf.cur_offset() + size_to_send, buf.cur_offset()); + uint32_t left = size_to_send; + do { + ssize_t read_size_ret = os::read(_fd, buf.cur_buf_ptr(), left); + if (read_size_ret == -1) { + return errno; + } + uint32_t read_size = (uint32_t) read_size_ret; + buf.skip_cur_offset(read_size); + left -= read_size; + } while (left > 0); + + // update status + _handled_file_size += size_to_send; + _handled_once = true; + if (is_file_all_handled()) { + os::close(_fd); + _fd = -1; + } + return 0; +} + +/** + * The file to deserialize is null. + */ +void FileWrapper::on_deser_null() { + if (!_handled_once && _handled_file_size == 0) { + assert(_fd >= 0, "sanity"); + _handled_file_size = _file_size; + _handled_once = true; + os::close(_fd); + _fd = -1; + FileUtils::remove(_tmp_file_path); + } else { + guarantee(_handled_once && _handled_file_size == _file_size && _fd == -1, "sanity"); + } +} + +/** + * The whole file is all deserialized. + */ +void FileWrapper::on_deser_end() { + assert(_fd >= 0, "sanity"); + os::close(_fd); + _fd = -1; + bool rename_successful = false; + if (!FileUtils::is_file(_file_path)) { + // We rename the tmp file to the target file finally if the file is deserialized successfully. + // FileUtils::rename() may replace the target file by the tmp file if the target file exists, + // which is not what we want. + // Instead, we want the rename function to fail (atomically) when the target file already exists. + // But we don't have to use some atomic function like renameat2 with RENAME_NOREPLACE here. + // Because every target file is renamed from the tmp file so a target file cannot be created after + // its tmp file is locked. + chmod(_tmp_file_path, S_IREAD); + rename_successful = FileUtils::rename(_tmp_file_path, _file_path); + if (!rename_successful) { + log_warning(jbooster, serialization)("Failed to rename the tmp file of \"%s\" " + "and the tmp file will be removed. errno=%s(\"%s\").", + _file_path, os::errno_name(errno), os::strerror(errno)); + } + } else { + // This branch should not be walked on. + log_error(jbooster, serialization)("File \"%s\" already exists and will not be overwritten.", _file_path); + } + if (!rename_successful) { + FileUtils::remove(_tmp_file_path); + } +} + +int FileWrapper::deserialize(MessageBuffer& buf) { + _smode.assert_can_deserialize(); + assert(_fd >= 0, "the tmp file"); + guarantee(!is_file_all_handled(), "the deserialization is ended"); + + // file size + uint32_t file_size; + JB_RETURN(buf.deserialize_ref_no_meta(file_size)); + if (_file_size != file_size) { + if (_file_size == MessageConst::NULL_PTR) { // first transmission + _file_size = file_size; + } else { + log_warning(jbooster, serialization)("File size mismatch while deserializing file \"%s\": " + "expect_file_size=%u, actual_file_size=%u", + _file_path, _file_size, file_size); + return JBErr::BAD_ARG_DATA; + } + } + if (_file_size == MessageConst::NULL_PTR) { + on_deser_null(); + return 0; + } + + // size to handle this time + uint32_t size_to_recv; + JB_RETURN(buf.deserialize_ref_no_meta(size_to_recv)); + + // content (use low-level APIs to save a memcpy) + uint32_t left = size_to_recv; + do { + uint32_t write_size = (uint32_t) os::write(_fd, buf.cur_buf_ptr(), left); + buf.skip_cur_offset(write_size); + left -= write_size; + } while (left > 0); + + // update status + _handled_file_size += size_to_recv; + _handled_once = true; + if (is_file_all_handled()) { + on_deser_end(); + } + return 0; +} + +bool FileWrapper::wait_for_file_deserialization(int timeout_millisecond) { + jlong start = os::javaTimeMillis(); + do { + if (FileUtils::is_file(_file_path)) { + return true; + } + os::naked_short_sleep(50); + } while ((os::javaTimeMillis() - start) < timeout_millisecond); + log_warning(jbooster, serialization)("File \"%s\" is still being deserialized by another thread or process " + "after %dms. Stop waiting.", + _file_path, timeout_millisecond); + return false; +} + +int FileWrapper::send_file(CommunicationStream* stream) { + // No need to check has_file_err() here because it's not an exception. + // It's OK that the target file failed to be opened. + while (!is_file_all_handled()) { + bool ok = false; + JB_RETURN(stream->send_request(MessageType::FileSegment, this)); + JB_RETURN(stream->recv_request(MessageType::FileSegment, &ok)); + guarantee(ok, "sanity"); + } + return 0; +} + +int FileWrapper::recv_file(CommunicationStream* stream) { + // Check has_file_err() here and treat the err as an exception. + // The tmp file should be opened. + if (has_file_err()) return _errno; + while (!is_file_all_handled()) { + bool ok = true; + JB_RETURN(stream->recv_request(MessageType::FileSegment, this)); + JB_RETURN(stream->send_request(MessageType::FileSegment, &ok)); + } + return 0; +} diff --git a/src/hotspot/share/jbooster/net/serializationWrappers.hpp b/src/hotspot/share/jbooster/net/serializationWrappers.hpp new file mode 100644 index 000000000..cc7f96c15 --- /dev/null +++ b/src/hotspot/share/jbooster/net/serializationWrappers.hpp @@ -0,0 +1,276 @@ +/* + * 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 SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_HPP +#define SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_HPP + +#include "jbooster/net/serialization.hpp" + +template class Array; +class ClassLoaderData; +class CommunicationStream; +template class GrowableArray; +class InstanceKlass; +class Method; + +// Here are some wrappers defined for serialization and deserialization. + +class WrapperBase: public StackObj { +public: + // signed-int version of unsigned-int MessageConst::NULL_PTR + enum: int { + INT_NULL_PTR = static_cast(MessageConst::NULL_PTR) + }; + +protected: + SerializationMode _smode; + + // should free the memory for deserialization at ~WrapperBase() + bool _free_memory; + + WrapperBase(SerializationMode smode): _smode(smode), _free_memory(false) {} + +public: + // The APIs that must be implemented by each wrapper: + // int serialize(MessageBuffer& buf) const; + // int deserialize(MessageBuffer& buf); + // + // And don't forget to register the wrapper to SerializationImpl: + // DECLARE_SERIALIZER_INTRUSIVE(TheWrapper); +}; + +/** + * ArrayWrapper supports serialization/deserialization of both pointers or values, + * that is, it can (de)serialize Arg* and Arg**. + * + * Layout of the serialized array: + * | array meta | ...... elements ...... | + * | element count | arg0 meta | arg0 payload | arg1 meta | arg1 payload | ... | + */ +template +class ArrayWrapper final: public WrapperBase { +public: + using ConstArgPtr = const Arg*; + + template + struct ArrayAllocater { + virtual void* alloc_val_container(int size) { ShouldNotReachHere(); return nullptr; } + virtual void* alloc_ptr_container(int size) { ShouldNotReachHere(); return nullptr; } + }; + +private: + const void* _base; // start of the list + int _size; // count of elements + bool _is_ptr; // whether the elements are pointers or values + + // Used to allocate third-party arrays such as GrowableArray and Array. + // This API is not implemented yet. + ArrayAllocater* const _allocater; + + template + void check_arr_type(); + + Arg* cast_to_non_const_val_base(); // for deserialization only + Arg** cast_to_non_const_ptr_base(); // for deserialization only + + void* alloc_val_container(); + void* alloc_ptr_container(); + +private: + template + static const void* arr_base(ArrLike* array); + + template + static int arr_size(ArrLike* array); + +public: + ArrayWrapper(bool is_ptr, ArrayAllocater* allocater = nullptr); // for deserialization only + + ArrayWrapper(const Arg* val_base, int size); // for serialization only + ArrayWrapper(const ConstArgPtr* ptr_base, int size); // for serialization only + + ArrayWrapper(const Array* array); // for serialization only + ArrayWrapper(const Array* array); // for serialization only + ArrayWrapper(const GrowableArray* array); // for serialization only + ArrayWrapper(const GrowableArray* array); // for serialization only + + ~ArrayWrapper(); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + bool is_null() const { return _base == nullptr; } + int size() const { return _size; } + + // Attention: it always return "Arg*" whether it is an array of "Arg*" or "Arg". + // Use get_array() if you care about the performance. + const Arg* get(int index) const; + + // This function is for serialization, And it's irreversible. + int set_sub_arr(int start, int size); + + // after deserialization + + // The array will still be destroyed (if in deserialization mode) in ~ArrayWrapper. + template + T* get_array(); + // Now the array will not be destroyed in ~ArrayWrapper. + template + T* export_array(); +}; + +template +struct SerializationImpl> { + static int serialize(MessageBuffer& buf, const ArrayWrapper& arg); + static int deserialize_ref(MessageBuffer& buf, ArrayWrapper& arg); + CANNOT_DESERIALIZE_POINTER(ArrayWrapper); +}; + +/** + * Serializing contiguous memory (using memcpy()) + */ +class MemoryWrapper final: public WrapperBase { +public: + struct MemoryAllocater { + virtual void* alloc(int size) { ShouldNotReachHere(); return nullptr; } + }; + +private: + const void* _base; // start of the memory + uint32_t _size; // bytes of the memory + + MemoryAllocater* const _allocater; + +public: + MemoryWrapper(const void* base, uint32_t size); // for serialization only + MemoryWrapper(MemoryAllocater* allocater = nullptr); // for deserialization only + + ~MemoryWrapper(); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + // after deserialization + bool is_null() const { return _base == nullptr; } + uint32_t size() const { return _size; } + + // The memory will still be destroyed (if in deserialization mode) in ~MemoryWrapper. + void* get_memory() const { return const_cast(_base); } + // Now the memory will not be destroyed in ~MemoryWrappe. + void* export_memory() { + _free_memory = false; + return get_memory(); + } +}; + +DECLARE_SERIALIZER_INTRUSIVE(MemoryWrapper); + +/** + * Compatible with the serializers of `char*` and `char[]`, but supports RAII. + */ +class StringWrapper final: public WrapperBase { +private: + const char* _base; + uint32_t _size; // not including '\0' + +public: + StringWrapper(const char* str); // for serialization only + StringWrapper(const char* str, uint32_t size); // for serialization only + StringWrapper(); // for deserialization only + ~StringWrapper(); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + uint32_t size() { return _size; } + + // The string will still be destroyed (if in deserialization mode) in ~StringWrapper. + const char* get_string() const { return _base; } + // Now the string will not be destroyed in ~StringWrapper. + const char* export_string() { + _free_memory = false; + return _base; + } +}; + +DECLARE_SERIALIZER_INTRUSIVE(StringWrapper); + +class FileWrapper final: public WrapperBase { +public: + static constexpr uint32_t MAX_SIZE_PER_TRANS = 40 * 1024 * 1024; // 40 MB + +private: + const char* _file_path; + uint32_t _file_size; + const char* _tmp_file_path; + + mutable int _fd; + mutable int _errno; + mutable uint32_t _handled_file_size; + mutable bool _handled_once; // to cover _file_size = 0 + +private: + void init_for_serialize(); + void init_for_deserialize(); + + void on_deser_tmp_file_opened(); + void on_deser_tmp_file_exist(); + void on_deser_dir_not_exist(); + + void on_deser_null(); + void on_deser_end(); + + uint32_t get_size_to_send_this_time() const; + +public: + FileWrapper(const char* file_path, SerializationMode smode); + ~FileWrapper(); + + static uint32_t get_arg_meta_size_if_not_null(); + + int serialize(MessageBuffer& buf) const; + int deserialize(MessageBuffer& buf); + + bool is_null() const { return _file_size == MessageConst::NULL_PTR; } + bool is_file_all_handled() const { + assert(_file_size >= _handled_file_size, "sanity"); + return _handled_once && _file_size == _handled_file_size; + } + + const char* file_path() const { return _file_path; } + uint32_t file_size() const { return _file_size; } + int fd() const { return _fd; } + int has_file_err() const { return _errno != 0; } + int file_err() const { return _errno; } + bool is_tmp_file_already_exists() { _smode.assert_can_deserialize(); return _errno == EEXIST; } + + // Wait for other threads or processes to generate files. + bool wait_for_file_deserialization(int timeout_millisecond = 2000); + + int send_file(CommunicationStream* stream); + int recv_file(CommunicationStream* stream); +}; + +DECLARE_SERIALIZER_INTRUSIVE(FileWrapper); + +#endif // SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_HPP diff --git a/src/hotspot/share/jbooster/net/serializationWrappers.inline.hpp b/src/hotspot/share/jbooster/net/serializationWrappers.inline.hpp new file mode 100644 index 000000000..b1ff6b35c --- /dev/null +++ b/src/hotspot/share/jbooster/net/serializationWrappers.inline.hpp @@ -0,0 +1,302 @@ +/* + * 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 SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_INLINE_HPP +#define SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_INLINE_HPP + +#include + +#include "jbooster/net/serializationWrappers.hpp" +#include "memory/metadataFactory.hpp" +#include "oops/array.hpp" +#include "runtime/thread.hpp" +#include "utilities/growableArray.hpp" + +// -------------------------- ArrayWrapper for Serializer -------------------------- + +template +template +inline const void* ArrayWrapper::arr_base(ArrLike* array) { + if (array == nullptr) return nullptr; + if (array->is_empty()) { + // adr_at() requires 0 <= idx < len. So just use a non-null placeholder here. + // We use 0x1 because if the program accesses it, it crashes. + return (const void*) ((uintptr_t) 0x1); + } + return static_cast(array->adr_at(0)); +} + +template +template +inline int ArrayWrapper::arr_size(ArrLike* array) { + if (array == nullptr) return INT_NULL_PTR; + return array->length(); +} + +template +inline ArrayWrapper::ArrayWrapper(bool is_ptr, ArrayAllocater* allocater): + WrapperBase(SerializationMode::DESERIALIZE), + _base(nullptr), + _size(0), + _is_ptr(is_ptr), + _allocater(allocater) {} + +template +inline ArrayWrapper::ArrayWrapper(const Arg* val_base, int size): + WrapperBase(SerializationMode::SERIALIZE), + _base(static_cast(val_base)), + _size(val_base == nullptr ? INT_NULL_PTR : size), + _is_ptr(false), + _allocater(nullptr) {} + +template +inline ArrayWrapper::ArrayWrapper(const ConstArgPtr* ptr_base, int size): + WrapperBase(SerializationMode::SERIALIZE), + _base(static_cast(ptr_base)), + _size(ptr_base == nullptr ? INT_NULL_PTR : size), + _is_ptr(true), + _allocater(nullptr) {} + +template +inline ArrayWrapper::ArrayWrapper(const Array* array): + WrapperBase(SerializationMode::SERIALIZE), + _base(arr_base(array)), + _size(arr_size(array)), + _is_ptr(false), + _allocater(nullptr) {} + +template +inline ArrayWrapper::ArrayWrapper(const Array* array): + WrapperBase(SerializationMode::SERIALIZE), + _base(arr_base(array)), + _size(arr_size(array)), + _is_ptr(true), + _allocater(nullptr) {} + +template +inline ArrayWrapper::ArrayWrapper(const GrowableArray* array): + WrapperBase(SerializationMode::SERIALIZE), + _base(arr_base(array)), + _size(arr_size(array)), + _is_ptr(false), + _allocater(nullptr) {} + +template +inline ArrayWrapper::ArrayWrapper(const GrowableArray* array): + WrapperBase(SerializationMode::SERIALIZE), + _base(arr_base(array)), + _size(arr_size(array)), + _is_ptr(true), + _allocater(nullptr) {} + +template +inline ArrayWrapper::~ArrayWrapper() { + if (!_free_memory || _base == nullptr) return; + if (!_is_ptr) { + Arg* base = cast_to_non_const_val_base(); + for (int i = 0; i < _size; ++i) base[i].~Arg(); + FREE_C_HEAP_ARRAY(Arg, _base); + } + else { + // We do not delete the value of pointers here. + FREE_C_HEAP_ARRAY(ConstArgPtr, _base); + } +} + +template +inline int ArrayWrapper::set_sub_arr(int start, int max_size) { + _smode.assert_can_serialize(); + _size = MIN2(max_size, MAX2(0, _size - start)); + // _base may be a illegal address when _size is small than max_size. + // But it's ok because _size is 0 in such condition. + if (!_is_ptr) { + const Arg* base = static_cast(_base); + _base = base + start; + } else { + const ConstArgPtr* base = static_cast(_base); + _base = base + start; + } + return _size; +} + +template +template +inline void ArrayWrapper::check_arr_type() { + if (!_is_ptr) { + assert((std::is_same::value), "wrong type"); + } else { + assert((std::is_same::value), "wrong type"); + } +} + +template +inline Arg* ArrayWrapper::cast_to_non_const_val_base() { + _smode.assert_can_deserialize(); + assert(!_is_ptr, "elements should be values"); + void* base_non_const = const_cast(_base); + return static_cast(base_non_const); +} +template +inline Arg** ArrayWrapper::cast_to_non_const_ptr_base() { + _smode.assert_can_deserialize(); + assert(_is_ptr, "elements should be pointers"); + void* base_non_const = const_cast(_base); + return static_cast(base_non_const); +} + +template +inline void* ArrayWrapper::alloc_val_container() { + assert(_base == nullptr, "sanity"); + if (_allocater != nullptr) { + return _allocater->alloc_val_container(_size); + } + _free_memory = true; + void* res = (void*)NEW_C_HEAP_ARRAY(Arg, _size, mtJBooster); + // We do not initialize the values here. + return res; +} + +template +inline void* ArrayWrapper::alloc_ptr_container() { + assert(_base == nullptr, "sanity"); + if (_allocater != nullptr) { + return _allocater->alloc_ptr_container(_size); + } + _free_memory = true; + void* res = (void*)NEW_C_HEAP_ARRAY(Arg*, _size, mtJBooster); + // We do not initialize the pointers here. + return res; +} + +template +inline int ArrayWrapper::serialize(MessageBuffer& buf) const { + _smode.assert_can_serialize(); + if (_base == nullptr) { + return buf.serialize_no_meta(INT_NULL_PTR); + } + JB_RETURN(buf.serialize_no_meta(_size)); + if (!_is_ptr) { + const Arg* base = static_cast(_base); + for (int i = 0; i < _size; ++i) { + JB_RETURN(buf.serialize_with_meta(base + i)); + } + } else { + const ConstArgPtr* base = static_cast(_base); + for (int i = 0; i < _size; ++i) { + JB_RETURN(buf.serialize_with_meta(base[i])); + } + } + return 0; +} + +template +class ArrayWrapperUtils: public AllStatic { +public: + static C* placement_new_if_possible(void* base) { + return ::new (base) C(); + } +}; + +template +class ArrayWrapperUtils::value || !std::is_default_constructible::value>::type>: public AllStatic { +public: + static C* placement_new_if_possible(void* base) { + if (std::is_abstract::value) { + fatal("Unable to preallocate memory for abstract classes!"); + } + if (!std::is_default_constructible::value) { + fatal("Unable to preallocate memory for classes with no default constructors!"); + } + return nullptr; + } +}; + +template +inline int ArrayWrapper::deserialize(MessageBuffer& buf) { + _smode.assert_can_deserialize(); + assert(_base == nullptr, "can only deserialize once"); + JB_RETURN(buf.deserialize_ref_no_meta(_size)); + if (_size == INT_NULL_PTR) { + _size = 0; + return 0; + } + + if (!_is_ptr) { + _base = alloc_val_container(); + Arg* base = cast_to_non_const_val_base(); + for (int i = 0; i < _size; ++i) { + // initialize each element + ArrayWrapperUtils::placement_new_if_possible((void*) (base + i)); + JB_RETURN(buf.deserialize_with_meta(base + i)); + } + } else { + _base = alloc_ptr_container(); + Arg** base = cast_to_non_const_ptr_base(); + // set all Arg* to nullptr + memset(base, 0, _size * sizeof(Arg*)); + for (int i = 0; i < _size; ++i) { + JB_RETURN(buf.deserialize_with_meta(base[i])); + } + } + return 0; +} + +template +inline const Arg* ArrayWrapper::get(int index) const { + assert(_base != nullptr && index >= 0 && index < size(), "out-of-bounds"); + if (!_is_ptr) { + const Arg* base = static_cast(_base); + return base + index; + } else { + const ConstArgPtr* base = static_cast(_base); + return base[index]; + } +} + +template +template +inline T* ArrayWrapper::get_array() { + check_arr_type(); + return (T*)const_cast(_base); +} + +template +template +inline T* ArrayWrapper::export_array() { + _free_memory = false; + return get_array(); +} + +// --------------------- Serializer register for ArrayWrapper ---------------------- + +template +inline int SerializationImpl>::serialize(MessageBuffer& buf, const ArrayWrapper& arg) { + return arg.serialize(buf); +} + +template +inline int SerializationImpl>::deserialize_ref(MessageBuffer& buf, ArrayWrapper& arg) { + return arg.deserialize(buf); +} + +#endif // SHARE_JBOOSTER_NET_SERIALIZATIONWRAPPERS_INLINE_HPP diff --git a/src/hotspot/share/jbooster/net/serverListeningThread.cpp b/src/hotspot/share/jbooster/net/serverListeningThread.cpp new file mode 100644 index 000000000..0f7b5dc8d --- /dev/null +++ b/src/hotspot/share/jbooster/net/serverListeningThread.cpp @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#include "classfile/javaClasses.inline.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/net/serverListeningThread.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/server/serverMessageHandler.hpp" +#include "logging/log.hpp" +#include "runtime/atomic.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/java.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.inline.hpp" +#include "runtime/thread.inline.hpp" + +ServerListeningThread* ServerListeningThread::_singleton = nullptr; + +/** + * This function is called in the main thread. + */ +ServerListeningThread* ServerListeningThread::start_thread(const char* address, + uint16_t port, + uint32_t timeout_ms, + TRAPS) { + JavaThread* new_thread = new JavaThread(&server_listener_thread_entry); + guarantee(new_thread != nullptr && new_thread->osthread() != nullptr, "sanity"); + guarantee(_singleton == nullptr, "sanity"); + _singleton = new ServerListeningThread(new_thread, address, port, timeout_ms); + + Handle name = java_lang_String::create_from_str("JBooster Listening Thread", CHECK_NULL); + Handle thread_group(THREAD, Universe::main_thread_group()); + Handle thread_oop = JavaCalls::construct_new_instance( + vmClasses::Thread_klass(), + vmSymbols::threadgroup_string_void_signature(), + thread_group, + name, + CHECK_NULL); + + Klass* group = vmClasses::ThreadGroup_klass(); + JavaValue result(T_VOID); + JavaCalls::call_special(&result, + thread_group, + group, + vmSymbols::add_method_name(), + vmSymbols::thread_void_signature(), + thread_oop, + THREAD); + + JavaThread::start_internal_daemon(THREAD, new_thread, thread_oop, NearMaxPriority); + return _singleton; +} + +void ServerListeningThread::server_listener_thread_entry(JavaThread* thread, TRAPS) { + JB_TRY { + JB_THROW(_singleton->run_listener(thread)); + } JB_TRY_END + JB_CATCH_REST() { + log_error(jbooster, rpc)("ServerListeningThread failed to listen on the port: error=%s(\"%s\").", + JBErr::err_name(JB_ERR), JBErr::err_message(JB_ERR)); + vm_exit(1); + } JB_CATCH_END; + vm_exit(0); +} + +ServerListeningThread::ServerListeningThread(JavaThread* the_java_thread, + const char* address, + uint16_t port, + uint32_t timeout_ms): + _the_java_thread(the_java_thread), + _address(address), + _port(port), + _timeout_ms(timeout_ms), + _stream_id_for_alloc(0), + _exit_flag(false) { +} + +ServerListeningThread::~ServerListeningThread() { + log_debug(jbooster, rpc)("The JBooster server listener thread is destroyed."); +} + +uint32_t ServerListeningThread::new_stream_id() { + return Atomic::add(&_stream_id_for_alloc, 1U); +} + +/** + * Add the new connection to the connection thread pool. + */ +void ServerListeningThread::handle_new_connection(int conn_fd, TRAPS) { + ThreadInVMfromNative tiv(THREAD); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + + JavaValue result(T_BOOLEAN); + JavaCallArguments args; + args.push_int(conn_fd); + JavaCalls::call_static(&result, ServerDataManager::get().main_klass(), + vmSymbols::receiveConnection_name(), + vmSymbols::int_bool_signature(), + &args, CATCH); + if (!result.get_jboolean()) { + log_warning(jbooster, rpc)("Failed to handle the new connection as the thread pool is full."); + os::close(conn_fd); + } +} + +/** + * Handle the connection obtained from the connection thread pool. + * + * 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) { + JavaThread* THREAD = JavaThread::current(); + ThreadToNativeFromVM ttn(THREAD); + + ServerStream* server_stream = new ServerStream(conn_fd, THREAD); + ThreadServerStreamMark tssm(server_stream, true, THREAD); + server_stream->handle_meta_request(new_stream_id()); + if (server_stream->is_stream_closed()) return; + + ServerMessageHandler msg_handler(server_stream); + JB_TRY { + JB_THROW(msg_handler.handle_tasks_from_client(THREAD)); + } JB_TRY_END + JB_CATCH_REST() { + log_warning(jbooster, rpc)("Unhandled exception at ServerListeningThread::handle_connection(): " + "error=%s(\"%s\"), session_id=%u, stream_id=%u.", + JBErr::err_name(JB_ERR), JBErr::err_message(JB_ERR), + server_stream->session_id(), server_stream->stream_id()); + } JB_CATCH_END; +} diff --git a/src/hotspot/share/jbooster/net/serverListeningThread.hpp b/src/hotspot/share/jbooster/net/serverListeningThread.hpp new file mode 100644 index 000000000..49f5efb3f --- /dev/null +++ b/src/hotspot/share/jbooster/net/serverListeningThread.hpp @@ -0,0 +1,67 @@ +/* + * 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 SHARE_JBOOSTER_NET_SERVERLISTENINGTHREAD_HPP +#define SHARE_JBOOSTER_NET_SERVERLISTENINGTHREAD_HPP + +#include "runtime/thread.hpp" + +class ServerListeningThread : public CHeapObj { +private: + static ServerListeningThread* _singleton; + + JavaThread* const _the_java_thread; + const char* const _address; + const uint16_t _port; + const uint32_t _timeout_ms; + + volatile uint32_t _stream_id_for_alloc; + + volatile bool _exit_flag; + +private: + 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); + + int run_listener(TRAPS); + +public: + static ServerListeningThread* start_thread(const char* address, + uint16_t port, + uint32_t timeout_ms, + TRAPS); + + ~ServerListeningThread(); + + bool get_exit_flag() { return _exit_flag; } + void set_exit_flag() { _exit_flag = true; } + + void handle_connection(int conn_fd); +}; + +#endif // SHARE_JBOOSTER_NET_SERVERLISTENINGTHREAD_HPP diff --git a/src/hotspot/share/jbooster/net/serverStream.cpp b/src/hotspot/share/jbooster/net/serverStream.cpp new file mode 100644 index 000000000..a3e7fa5c6 --- /dev/null +++ b/src/hotspot/share/jbooster/net/serverStream.cpp @@ -0,0 +1,234 @@ +/* + * 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. + */ + +#include "compiler/compileTask.hpp" +#include "jbooster/net/rpcCompatibility.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "runtime/thread.hpp" +#include "runtime/timerTrace.hpp" + +ServerStream::ServerStream(int conn_fd): + CommunicationStream(Thread::current_or_null()), + _session_data(nullptr) { + init_stream(conn_fd); +} + +ServerStream::ServerStream(int conn_fd, Thread* thread): + CommunicationStream(thread), + _session_data(nullptr) { + init_stream(conn_fd); +} + +ServerStream::~ServerStream() { + set_session_data(nullptr); +} + +uint32_t ServerStream::session_id() { + return session_data()->session_id(); +} + +void ServerStream::set_session_data(JClientSessionData* sd) { + JClientSessionData* old_sd = _session_data; + if (sd == old_sd) return; + // Do not call sd->ref_cnt().inc() here as it has been inc when obtained. + if (old_sd != nullptr) { + old_sd->ref_cnt().dec_and_update_time(); + } + _session_data = sd; +} + +void ServerStream::handle_meta_request(uint32_t stream_id) { + set_stream_id(stream_id); + MessageType type; + JB_TRY { + JB_THROW(recv_request(type)); + switch (type) { + case MessageType::ClientSessionMeta: { + JB_THROW(sync_session_meta__server()); + close_stream(); + break; + } + case MessageType::ClientStreamMeta: { + JB_THROW(sync_stream_meta__server()); + break; + } + default: { + JB_THROW(JBErr::BAD_MSG_TYPE); + break; + } + // Recheck JB_ERR because the JB_THROWs above are intercepted + // by the switch statement. + JB_THROW(JB_ERR); + } + } JB_TRY_END + JB_CATCH(JBErr::INCOMPATIBLE_RPC) { + const char* unsupport_reason = "RPC version"; + log_warning(jbooster)("The server does not support this client because the \"%s\" of the two are different! stream_id=%u.", + unsupport_reason, stream_id); + send_request(MessageType::UnsupportedClient, &unsupport_reason); + close_stream(); + } JB_CATCH_REST() { + log_warning(jbooster, rpc)("Unhandled exception at ServerStream::handle_meta_request(): " + "error=%s(\"%s\"), first_msg_type=%s, stream_id=%u.", + JBErr::err_name(JB_ERR), JBErr::err_message(JB_ERR), + msg_type_name(type), stream_id); + close_stream(); + } JB_CATCH_END; +} + +int ServerStream::sync_session_meta__server() { + ServerDataManager& sdm = ServerDataManager::get(); + + 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)); + + const char* unsupport_reason = nullptr; + if (!program_args.check_compatibility_with_server(&unsupport_reason)) { + log_warning(jbooster)("The server does not support this client because the \"%s\" of the two are different! stream_id=%u.", + unsupport_reason, stream_id()); + JB_RETURN(send_request(MessageType::UnsupportedClient, &unsupport_reason)); + JB_RETURN(JBErr::ABORT_CUR_PHRASE); + } + + JClientSessionData* sd = sdm.create_session(client_random_id, &program_args, Thread::current()); + JClientProgramData* pd = sd->program_data(); + set_session_data(sd); + + uint64_t server_random_id = sdm.random_id(); + 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(); + 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, " + "random_id=" UINT64_FORMAT_X ", program_name=%s, " + "has_clr=%s, has_cds=%s, has_aot=%s, " + "stream_id=%u.", + session_id, program_id, + client_random_id, program_args.program_name(), + BOOL_TO_STR(has_remote_clr), + BOOL_TO_STR(has_remote_cds), + BOOL_TO_STR(has_remote_aot), + stream_id()); + + return handle_sync_requests(pd); +} + +int ServerStream::handle_sync_requests(JClientProgramData* pd) { + bool not_end = true; + do { + MessageType type; + JB_RETURN(recv_request(type)); + switch (type) { + case MessageType::GetClassLoaderResourceCache: { + TraceTime tt("Send clr", TRACETIME_LOG(Info, jbooster)); + FileWrapper file(pd->clr_cache_state().file_path(), SerializationMode::SERIALIZE); + JB_RETURN(file.send_file(this)); + break; + } + case MessageType::GetAggressiveCDSCache: { + TraceTime tt("Send cds", TRACETIME_LOG(Info, jbooster)); + FileWrapper file(pd->cds_cache_state().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); + JB_RETURN(file.send_file(this)); + break; + } + default: not_end = false; break; + } + } while (not_end); + return 0; +} + +int ServerStream::sync_stream_meta__server() { + ServerDataManager& sdm = ServerDataManager::get(); + + uint32_t session_id; + uint64_t client_random_id, server_random_id; + JB_RETURN(parse_request(&session_id, &client_random_id, &server_random_id)); + set_session_data(sdm.get_session(session_id, Thread::current())); + + if (_session_data == nullptr + || client_random_id != _session_data->random_id() + || server_random_id != sdm.random_id()) { + // The server has been restarted, and this client was connected to the previous server instance. + log_warning(jbooster, rpc)("Unrecognized session! Sync again. " + "obtained_session_id=%u, " + "obtained_client_random_id=" UINT64_FORMAT_X ", " + "obtained_server_random_id=" UINT64_FORMAT_X ", " + "stream_id=%u.", + session_id, client_random_id, server_random_id, + stream_id()); + JB_RETURN(resync_session_and_stream_meta__server()); + JB_RETURN(sync_stream_meta__server()); + } else { + JB_RETURN(send_response(stream_id_addr())); + log_trace(jbooster, rpc)("New ServerStream: session_id=%u, stream_id=%u.", session_id, stream_id()); + } + return 0; +} + +int ServerStream::resync_session_and_stream_meta__server() { + JB_RETURN(send_request(MessageType::ClientSessionMetaAgain)); + MessageType type; + JB_RETURN(recv_request(type)); + if (type != MessageType::ClientSessionMeta) { + JB_RETURN(JBErr::BAD_MSG_TYPE); + } + JB_RETURN(sync_session_meta__server()); + return 0; +} + +// ============================ ThreadServerStreamMark ============================= + +ThreadServerStreamMark::ThreadServerStreamMark(ServerStream* server_stream, bool should_delete): + _server_stream(server_stream), + _delete(should_delete), + _thread(Thread::current_or_null()) { + guarantee(_thread == _server_stream->current_thread(), "sanity"); +} + +ThreadServerStreamMark::ThreadServerStreamMark(ServerStream* server_stream, bool should_delete, TRAPS): + _server_stream(server_stream), + _delete(should_delete), + _thread(THREAD) { + guarantee(_thread == _server_stream->current_thread(), "sanity"); +} + +ThreadServerStreamMark::~ThreadServerStreamMark() { + if (_delete && (_thread == _server_stream->current_thread())) { + delete _server_stream; + log_debug(jbooster, rpc)("A ServerStream is automatically deleted."); + } +} diff --git a/src/hotspot/share/jbooster/net/serverStream.hpp b/src/hotspot/share/jbooster/net/serverStream.hpp new file mode 100644 index 000000000..56d05dabb --- /dev/null +++ b/src/hotspot/share/jbooster/net/serverStream.hpp @@ -0,0 +1,71 @@ +/* + * 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 SHARE_JBOOSTER_NET_SERVERSTREAM_HPP +#define SHARE_JBOOSTER_NET_SERVERSTREAM_HPP + +#include "jbooster/net/communicationStream.inline.hpp" +#include "memory/allocation.hpp" + +class JClientSessionData; +class JClientProgramData; + +class ServerStream: public CommunicationStream { + JClientSessionData* _session_data; + +private: + void set_session_data(JClientSessionData* sd); + + int sync_session_meta__server(); + int sync_stream_meta__server(); + int resync_session_and_stream_meta__server(); + + int handle_sync_requests(JClientProgramData* pd); + +public: + ServerStream(int conn_fd); + ServerStream(int conn_fd, Thread* thread); + ~ServerStream(); + + void handle_meta_request(uint32_t stream_id); + + JClientSessionData* session_data() { return _session_data; } + uint32_t session_id(); +}; // ServerStream + +/** + * Use RAII to auto deleting closed server stream. + * The server stream will be deleted only if it's handled by current thread. + */ +class ThreadServerStreamMark: public StackObj { + ServerStream* const _server_stream; + const bool _delete; + Thread* const _thread; + +public: + ThreadServerStreamMark(ServerStream* server_stream, bool should_delete); + ThreadServerStreamMark(ServerStream* server_stream, bool should_delete, TRAPS); + ~ThreadServerStreamMark(); +}; + +#endif // SHARE_JBOOSTER_NET_SERVERSTREAM_HPP diff --git a/src/hotspot/share/jbooster/server/serverControlThread.cpp b/src/hotspot/share/jbooster/server/serverControlThread.cpp new file mode 100644 index 000000000..9b4ff6857 --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverControlThread.cpp @@ -0,0 +1,293 @@ +/* + * 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. + */ + +#include "classfile/javaClasses.inline.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverControlThread.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/server/serverMessageHandler.hpp" +#include "jbooster/utilities/concurrentHashMap.inline.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "logging/log.hpp" +#include "runtime/globals.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.inline.hpp" + +uint32_t ServerControlThread::_unused_shared_data_cleanup_timeout = -1; // set in java code +uint32_t ServerControlThread::_heartbeat_read_write_timeout = 4 * 1000; +uint32_t ServerControlThread::_session_no_ref_timeout = 16 * 1000; + +ServerControlThread* ServerControlThread::_singleton = nullptr; + +ServerControlThread::ClientSessionState::ClientSessionState(ServerStream* server_stream): + _server_stream(server_stream), + _heartbeat_failed_times(0) {} + +ServerControlThread* ServerControlThread::start_thread(TRAPS) { + JavaThread* new_thread = new JavaThread(&server_control_thread_entry); + guarantee(new_thread != nullptr && new_thread->osthread() != nullptr, "sanity"); + guarantee(_singleton == nullptr, "sanity"); + _singleton = new ServerControlThread(new_thread); + + Handle string = java_lang_String::create_from_str("JBooster Serevr Control", CHECK_NULL); + Handle thread_group(THREAD, Universe::main_thread_group()); + Handle thread_oop = JavaCalls::construct_new_instance( + vmClasses::Thread_klass(), + vmSymbols::threadgroup_string_void_signature(), + thread_group, + string, + CHECK_NULL); + + Klass* group = vmClasses::ThreadGroup_klass(); + JavaValue result(T_VOID); + JavaCalls::call_special(&result, + thread_group, + group, + vmSymbols::add_method_name(), + vmSymbols::thread_void_signature(), + thread_oop, + THREAD); + + JavaThread::start_internal_daemon(THREAD, new_thread, thread_oop, NormPriority); + return _singleton; +} + +void ServerControlThread::server_control_thread_entry(JavaThread* thread, TRAPS) { + _singleton->control_loop(thread); +} + +ServerControlThread::ServerControlThread(JavaThread* java_thread): + _the_java_thread(java_thread), + _client_daemons(), + _sleep_lock(Mutex::leaf, "JBooster Server Control", true, Mutex::_safepoint_check_never) { +} + +/** + * This method is invoked in other thread. + * So pay attention to the difference between THREAD and this. + */ +void ServerControlThread::add_client_daemon_connection(ServerStream* server_stream, TRAPS) { + uint32_t session_id = server_stream->session_id(); + server_stream->set_current_thread(_the_java_thread); + server_stream->set_read_write_timeout(_heartbeat_read_write_timeout); + ClientSessionState state(server_stream); + ClientSessionState* in_map = _client_daemons.put_if_absent(session_id, state, THREAD); + if (in_map->server_stream() != server_stream) { + server_stream->set_current_thread(THREAD); + log_error(jbooster)("Duplicate client daemon connection, ignored: session_id=%u, stream_id=%u.", + session_id, server_stream->stream_id()); + return; + } +} + +/** + * Send and receive the heartbeat. + */ +int ServerControlThread::send_heartbeat(ServerStream* server_stream) { + int magic = 0xa1b2c3; + JB_RETURN(server_stream->send_request(MessageType::Heartbeat, &magic)); + int num; + JB_RETURN(server_stream->recv_response(&num)); + if (num != magic) JB_RETURN(JBErr::BAD_ARG_DATA); + return 0; +} + +/** + * Try to delete a session data that meets: + * - It has a daemon stream and the stream has been closed; + * - It has no other stream. + */ +void ServerControlThread::try_remove_session_data(uint32_t session_id, ClientSessionState* state, TRAPS) { + ServerStream* server_stream = state->server_stream(); + JClientSessionData* sd = server_stream->session_data(); + assert(sd->session_id() == session_id, "sanity"); + + // the last server stream should be this one + if (sd->ref_cnt().get() > 1) { + return; + } + assert(sd->ref_cnt().get() == 1, "sanity"); + + guarantee(server_stream->current_thread() == THREAD, "sanity"); + + log_info(jbooster)("The client has exited, so its session data is cleared: session_id=%u, stream_id=%u.", + session_id, server_stream->stream_id()); + + _client_daemons.remove(session_id, THREAD); + state = nullptr; // deleted in _client_daemons + delete server_stream; + assert(sd->ref_cnt().get() == 0, "sanity"); + ServerDataManager::get().try_remove_session(session_id, THREAD); +} + +/** + * Update the heart beat of the session data. + * Delete the session data if the client has exited (or lost contact). + */ +void ServerControlThread::update_session_state(uint32_t session_id, ClientSessionState* state, TRAPS) { + ServerStream* server_stream = state->server_stream(); + + if (server_stream->is_stream_closed()) { + try_remove_session_data(session_id, state, THREAD); + return; + } + + JB_TRY { + JB_THROW(send_heartbeat(server_stream)); + } JB_TRY_END + JB_CATCH(JBErr::CONN_CLOSED_BY_PEER) { + try_remove_session_data(session_id, state, THREAD); + return; + } JB_CATCH_REST() { + int times = state->inc_heartbeat_failed_times(); + log_warning(jbooster)("Heartbeat sync failed: error=%s(\"%s\"), " + "failed_times=%d, session_id=%u, stream_id=%u.", + JBErr::err_name(JB_ERR), JBErr::err_message(JB_ERR), + state->heartbeat_failed_times(), + session_id, server_stream->stream_id()); + if (times >= 4) { + try_remove_session_data(session_id, state, THREAD); + } + return; + } JB_CATCH_END; + + state->reset_heartbeat_failed_times(); +} + +/** + * Update the states of all sessions by heart beat. + */ +void ServerControlThread::update_session_states(TRAPS) { + GrowableArray sessions_with_daemon; + auto scan_func = [&sessions_with_daemon] (ServerControlThread::SessionStateMap::KVNode* p) -> bool { + assert(p->key() == p->value().server_stream()->session_id(), "sanity"); + sessions_with_daemon.append(&p->value()); + return true; + }; + _client_daemons.for_each(scan_func, THREAD); + for (GrowableArrayIterator iter = sessions_with_daemon.begin(); + iter != sessions_with_daemon.end(); + ++iter) { + // Don't do the following logic in the scan_func above, because that's in the + // critical section, where the map is locked. We should avoid locking the map + // for too long. + update_session_state((*iter)->server_stream()->session_id(), *iter, THREAD); + } +} + +/** + * Delete session datas with closed daemon stream. + * Most unused sessions are cleared in update_session_state(). This function is + * mainly used to clear some special cases. + * So it doesn't have to be invoked very often. + */ +void ServerControlThread::clear_unused_sessions(jlong current_time, TRAPS) { + auto eval_func = [current_time] (ServerDataManager::JClientSessionDataMap::KVNode* p) -> bool { + JClientSessionData* sd = p->value(); + if (sd->ref_cnt().no_ref_time(current_time) < (jlong) session_no_ref_timeout()) { + return false; + } + return sd->ref_cnt().get() == 0; + }; + + auto del_func = [&keys = _client_daemons, THREAD] (ServerDataManager::JClientSessionDataMap::KVNode* p) { + uint32_t session_id = p->key(); + // collect sessions without daemon stream + guarantee(!keys.contains(session_id, THREAD), "ref cnt mismatch"); + + log_info(jbooster)("A session data is deleted due to no daemon stream: " + "session_id=%u.", session_id); + // The real deletion of session data will be done in JClientSessionDataMapEvents::on_del(). + }; + + ServerDataManager& sdm = ServerDataManager::get(); + sdm.sessions()->bulk_remove_if(eval_func, del_func, THREAD); +} + +/** + * Delete program datas with no sessions for a long time. + */ +void ServerControlThread::clear_unused_programs(jlong current_time, TRAPS) { + ResourceMark rm; + GrowableArray deleted_program_ids; + + auto eval_func = [current_time] (ServerDataManager::JClientProgramDataMap::KVNode* p) -> bool { + JClientProgramData* pd = p->value(); + if (pd->ref_cnt().no_ref_time(current_time) < (jlong) unused_shared_data_cleanup_timeout()) { + return false; + } + return pd->ref_cnt().get() == 0; + }; + + auto del_func = [&deleted_program_ids] (ServerDataManager::JClientProgramDataMap::KVNode* p) { + deleted_program_ids.append(p->value()->program_id()); + log_info(jbooster)("A program data is deleted due to no sessions for a long time: " + "program_id=%u.", p->value()->program_id()); + // The real deletion of program data will be done in JClientProgramDataMapEvents::on_del(). + }; + + ServerDataManager& sdm = ServerDataManager::get(); + sdm.programs()->bulk_remove_if(eval_func, del_func, THREAD); + + // Call the java methods after the critical region of bulk_remove_if(). + for (GrowableArrayIterator iter = deleted_program_ids.begin(); + iter != deleted_program_ids.end(); ++iter) { + ServerDataManager::get().remove_java_side_program_data(*iter, THREAD); + if (HAS_PENDING_EXCEPTION) { + LogTarget(Error, jbooster) lt; + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + } + } +} + +/** + * Main loop of ServerControlThread. + */ +void ServerControlThread::control_loop(TRAPS) { + ThreadToNativeFromVM ttn(THREAD); + jlong program_last_check_time = os::javaTimeMillis(); + + while (true) { + { MonitorLocker locker(&_sleep_lock, Mutex::_no_safepoint_check_flag); + locker.wait(JBoosterManager::heartbeat_timeout() / 4); + } + jlong current_time = os::javaTimeMillis(); + ResourceMark rm(THREAD); + + update_session_states(THREAD); + + if (current_time - program_last_check_time > (jlong) (unused_shared_data_cleanup_timeout() >> 1)) { + program_last_check_time = current_time; + + clear_unused_sessions(current_time, THREAD); + clear_unused_programs(current_time, THREAD); + } + } +} diff --git a/src/hotspot/share/jbooster/server/serverControlThread.hpp b/src/hotspot/share/jbooster/server/serverControlThread.hpp new file mode 100644 index 000000000..0acfa38b3 --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverControlThread.hpp @@ -0,0 +1,102 @@ +/* + * 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 SHARE_JBOOSTER_SERVER_SERVERCONTROLTHREAD_HPP +#define SHARE_JBOOSTER_SERVER_SERVERCONTROLTHREAD_HPP + +#include "jbooster/utilities/concurrentHashMap.hpp" +#include "runtime/mutex.hpp" +#include "runtime/thread.hpp" + +class ServerStream; + +/** + * Goals of this thread: + * - Sync heartbeat with the clients; + * - Manage the client sessions and client programs (clear out-of-date data); + */ +class ServerControlThread : public CHeapObj { +public: + static uint32_t _unused_shared_data_cleanup_timeout; + static uint32_t _heartbeat_read_write_timeout; + static uint32_t _session_no_ref_timeout; + + class ClientSessionState: public StackObj { + ServerStream* _server_stream; + int _heartbeat_failed_times; + + public: + ClientSessionState(ServerStream* server_stream); + + ServerStream* server_stream() { return _server_stream; } + + int heartbeat_failed_times() { return _heartbeat_failed_times; } + int inc_heartbeat_failed_times() { return ++_heartbeat_failed_times; } + int reset_heartbeat_failed_times() { return _heartbeat_failed_times = 0; } + }; + + // key: session id + using SessionStateMap = ConcurrentHashMap; + +private: + static ServerControlThread* _singleton; + + JavaThread* const _the_java_thread; + SessionStateMap _client_daemons; + Monitor _sleep_lock; + +private: + static void server_control_thread_entry(JavaThread* thread, TRAPS); + + ServerControlThread(JavaThread* java_thread); + + int send_heartbeat(ServerStream* server_stream); + void try_remove_session_data(uint32_t session_id, ClientSessionState* state, TRAPS); + void update_session_state(uint32_t session_id, ClientSessionState* state, TRAPS); + void update_session_states(TRAPS); + void clear_unused_sessions(jlong current_time, TRAPS); + void clear_unused_programs(jlong current_time, TRAPS); + void control_loop(TRAPS); + +public: + static ServerControlThread* start_thread(TRAPS); + + static uint32_t unused_shared_data_cleanup_timeout() { return _unused_shared_data_cleanup_timeout; } + static void set_unused_shared_data_cleanup_timeout(uint32_t timeout) { + _unused_shared_data_cleanup_timeout = timeout; + } + + static uint32_t heartbeat_read_write_timeout() { return _heartbeat_read_write_timeout; } + static void set_heartbeat_read_write_timeout(uint32_t timeout) { + _heartbeat_read_write_timeout = timeout; + } + + static uint32_t session_no_ref_timeout() { return _session_no_ref_timeout; } + static void set_session_no_ref_timeout(uint32_t timeout) { + _session_no_ref_timeout = timeout; + } + + void add_client_daemon_connection(ServerStream* server_stream, TRAPS); +}; + +#endif // SHARE_JBOOSTER_SERVER_SERVERCONTROLTHREAD_HPP diff --git a/src/hotspot/share/jbooster/server/serverDataManager.cpp b/src/hotspot/share/jbooster/server/serverDataManager.cpp new file mode 100644 index 000000000..b4a11dc5c --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverDataManager.cpp @@ -0,0 +1,610 @@ +/* + * 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. + */ + +#include "classfile/javaClasses.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmSymbols.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/net/serialization.hpp" +#include "jbooster/net/serverListeningThread.hpp" +#include "jbooster/server/serverControlThread.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/concurrentHashMap.inline.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "logging/log.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/methodData.hpp" +#include "oops/symbol.hpp" +#include "runtime/atomic.hpp" +#include "runtime/globals.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/java.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/os.inline.hpp" +#include "runtime/thread.hpp" +#include "utilities/formatBuffer.hpp" +#include "utilities/stringUtils.hpp" + +static InstanceKlass* get_jbooster_class_loader_klass(TRAPS) { + assert(THREAD->thread_state() == _thread_in_vm, "sanity"); + + TempNewSymbol loader_k_name = SymbolTable::new_symbol("jdk/jbooster/JBoosterClassLoader"); + Handle class_loader(THREAD, SystemDictionary::java_system_loader()); + Klass* loader_k = SystemDictionary::resolve_or_fail(loader_k_name, class_loader, Handle(), true, CHECK_NULL); + guarantee(loader_k != nullptr && loader_k->is_instance_klass(), "sanity"); + InstanceKlass* loader_ik = InstanceKlass::cast(loader_k); + loader_ik->initialize(CHECK_NULL); + return loader_ik; +} + +static ClassLoaderData* create_jbooster_class_loader(uint32_t program_id, + const ClassLoaderKey& key, + ClassLoaderData* parent, + TRAPS) { + ThreadInVMfromNative tiv(THREAD); + + // get the class + InstanceKlass* loader_ik = get_jbooster_class_loader_klass(CHECK_NULL); + + // get the method + TempNewSymbol method_name = SymbolTable::new_symbol("create"); + TempNewSymbol method_sig = SymbolTable::new_symbol("(ILjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)" + "Ljdk/jbooster/JBoosterClassLoader;"); + + // invoke it and get a new loader obj + JavaValue result(T_OBJECT); + JavaCallArguments java_args; + int arg1 = (int) program_id; + Handle arg2 = key.loader_class_name() == nullptr ? Handle() + : java_lang_String::create_from_symbol(key.loader_class_name(), CHECK_NULL); + Handle arg3 = key.loader_name() == nullptr ? Handle() + : java_lang_String::create_from_symbol(key.loader_name(), CHECK_NULL); + Handle arg4(THREAD, parent->class_loader()); + java_args.push_int(arg1); + java_args.push_oop(arg2); + java_args.push_oop(arg3); + java_args.push_oop(arg4); + JavaCalls::call_static(&result, loader_ik, method_name, method_sig, &java_args, CHECK_NULL); + Handle new_loader(THREAD, result.get_oop()); + + // register and get ClassLoaderData + ClassLoaderData* res_data = SystemDictionary::register_loader(new_loader); + guarantee(res_data != nullptr, "sanity"); + + ResourceMark rm; + log_trace(jbooster, compilation)("New custom class loader:\n - loader: \"%s:%p\"\n - parent: \"%s:%p\"", + res_data->loader_name(), res_data, parent->loader_name(), parent); + + return res_data; +} + +static void destroy_jbooster_class_loader(uint32_t program_id, TRAPS) { + ThreadInVMfromNative tiv(THREAD); + + // get the class + InstanceKlass* loader_ik = get_jbooster_class_loader_klass(CHECK); + + // get the method + TempNewSymbol method_name = SymbolTable::new_symbol("destroy"); + TempNewSymbol method_sig = vmSymbols::int_void_signature(); + + // invoke it + JavaValue result(T_VOID); + JavaCallArguments java_args; + int arg1 = (int) program_id; + java_args.push_int(arg1); + JavaCalls::call_static(&result, loader_ik, method_name, method_sig, &java_args, CHECK); +} + +// -------------------------------------- RefCnt -------------------------------------- + +int RefCnt::get() const { + return Atomic::load_acquire(&_ref_cnt); +} + +/** + * If the returned value is < 0, this ref cnt has been + * locked by another thread and is no longer avaliable. + */ +int RefCnt::inc() const { + int res; + do { + res = Atomic::load_acquire(&_ref_cnt); + if (res == LOCKED) return res; + } while (Atomic::cmpxchg(&_ref_cnt, res, res + 1) != res); + return res + 1; +} + +/** + * This function should be invoked after inc() + * (and the returned value of inc() must be >= 0). + */ +int RefCnt::dec() const { + return Atomic::add(&_ref_cnt, (int) -1); +} + +bool RefCnt::try_lock() const { + return Atomic::cmpxchg(&_ref_cnt, 0, LOCKED) == 0; +} + +RefCntWithTime::RefCntWithTime(int init_ref_cnt): RefCnt(init_ref_cnt), + _no_ref_begin_time(os::javaTimeMillis()) {} + +int RefCntWithTime::dec_and_update_time() const { + if (get() == 1) { + Atomic::release_store(&_no_ref_begin_time, os::javaTimeMillis()); + } + return dec(); +} + +jlong RefCntWithTime::no_ref_time() const { + return no_ref_time(os::javaTimeMillis()); +} + +jlong RefCntWithTime::no_ref_time(jlong current_time) const { + return current_time - Atomic::load_acquire(&_no_ref_begin_time); +} + +// -------------------------------- JClientCacheState --------------------------------- + +JClientCacheState::JClientCacheState(): _is_allowed(false), + _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; + _file_path = file_path; + bool file_exists = FileUtils::is_file(file_path); + _state = file_exists ? GENERATED : NOT_GENERATED; + update_file_timestamp(); +} + +JClientCacheState::~JClientCacheState() { + remove_file(); + StringUtils::free_from_heap(_file_path); +} + +void JClientCacheState::update_file_timestamp() { + if (is_not_generated()) { + _file_timestamp = 0; + } else { + _file_timestamp = FileUtils::modify_time(_file_path); + } +} + +void JClientCacheState::remove_file_and_set_not_generated_sync() { + if (set_being_generated_from_generated()) { + FileUtils::remove(file_path()); + update_file_timestamp(); + set_not_generated_from_being_generated(); + } +} + +bool JClientCacheState::is_not_generated() { + return Atomic::load_acquire(&_state) == NOT_GENERATED; +} + +bool JClientCacheState::is_being_generated() { + return Atomic::load_acquire(&_state) == BEING_GENERATED; +} + +bool JClientCacheState::is_generated() { + return Atomic::load_acquire(&_state) == GENERATED; +} + +bool JClientCacheState::set_being_generated() { + return Atomic::cmpxchg(&_state, NOT_GENERATED, BEING_GENERATED) == NOT_GENERATED; +} + +void JClientCacheState::set_generated() { + update_file_timestamp(); + bool success = Atomic::cmpxchg(&_state, BEING_GENERATED, GENERATED) == BEING_GENERATED; + guarantee(success, "sanity"); +} + +void JClientCacheState::set_not_generated() { + bool success = Atomic::cmpxchg(&_state, BEING_GENERATED, NOT_GENERATED) == BEING_GENERATED; + guarantee(success, "sanity"); +} + +bool JClientCacheState::set_being_generated_from_generated() { + return Atomic::cmpxchg(&_state, GENERATED, BEING_GENERATED) == GENERATED; +} + +void JClientCacheState::set_not_generated_from_being_generated() { + bool success = Atomic::cmpxchg(&_state, BEING_GENERATED, NOT_GENERATED) == BEING_GENERATED; + guarantee(success, "sanity"); +} + +bool JClientCacheState::is_cached() { + if (is_generated()) { + bool is_file = FileUtils::is_file(file_path()); + if (is_file) { + uint64_t timestamp = FileUtils::modify_time(file_path()); + if (timestamp == _file_timestamp) return true; + log_warning(jbooster)("Cache file \"%s\" is tampered with so will be deleted: " + "actual=" UINT64_FORMAT ", expect=" UINT64_FORMAT ".", + file_path(), timestamp, _file_timestamp); + } else { + log_warning(jbooster)("Cache file \"%s\" does not exist! Reset state to NOT_GENERATED.", file_path()); + } + remove_file_and_set_not_generated_sync(); + } + return false; +} + +void JClientCacheState::remove_file() { + if (is_cached()) { + remove_file_and_set_not_generated_sync(); + } +} + +// ------------------------------ JClientProgramData ------------------------------- + +JClientProgramData::JClientProgramData(uint32_t program_id, JClientArguments* program_args): + _program_id(program_id), + _program_args(program_args->move()), + _class_loaders(), + _ref_cnt(1) { + const char* sd = ServerDataManager::get().cache_dir_path(); + _program_str_id = JBoosterManager::calc_program_string_id(_program_args->program_name(), + _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(); + 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 = JBoosterManager::calc_cache_path(sd, _program_str_id, "aot.so"); + clr_cache_state().init(allow_clr, clr_path); + cds_cache_state().init(allow_cds, cds_path); + aot_cache_state().init(allow_aot, aot_path); +} + +JClientProgramData::~JClientProgramData() { + guarantee(ref_cnt().get() == 0, "ref=%d, pd=%u", ref_cnt().get(), program_id()); + delete _program_args; + StringUtils::free_from_heap(_program_str_id); +} + +ClassLoaderData* JClientProgramData::class_loader_data(const ClassLoaderKey& key, Thread* thread) { + ClassLoaderData** res = _class_loaders.get(key, thread); + return res == nullptr ? nullptr : *res; +} + +ClassLoaderData* JClientProgramData::add_class_loader_if_absent(const ClassLoaderKey& key, + const ClassLoaderKey& parent_key, + Thread* thread) { + // return the existing cld if if exists + ClassLoaderData* res_data = class_loader_data(key, thread); + if (res_data != nullptr) return res_data; + + // get/create the cld to insert + ClassLoaderData* new_data; + if (key.is_boot_loader() && parent_key.is_boot_loader()) { + new_data = ClassLoaderData::the_null_class_loader_data(); + } else if (key.is_platform_loader() && parent_key.is_boot_loader()) { + new_data = ServerDataManager::get().platform_class_loader_data(); + } else { + ClassLoaderData* parent_data = class_loader_data(parent_key, thread); + JavaThread* THREAD = thread->as_Java_thread(); + new_data = create_jbooster_class_loader(program_id(), key, parent_data, CATCH); + } + + // try to insert + ClassLoaderData** res = _class_loaders.put_if_absent(key, new_data, thread); + guarantee(res != nullptr, "sanity"); + res_data = *res; + + // duplicate insertion, so delete the duplicate cld + if (res_data != new_data) { + if (!new_data->is_builtin_class_loader_data()) { + // [JBOOSTER TODO] failed to insert so unregister the `res_data` above + } + } + + return res_data; +} + +// ------------------------------ JClientSessionData ------------------------------- + +class AddressMapKVArray: public StackObj { + GrowableArray
* _key_array; + GrowableArray
* _value_array; +public: + 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()); + return true; + } +}; + +JClientSessionData::JClientSessionData(uint32_t session_id, + uint64_t client_random_id, + JClientProgramData* program_data): + _session_id(session_id), + _random_id(client_random_id), + _program_data(program_data), + _cl_s2c(), + _cl_c2s(), + _k_c2s(), + _m2md(), + _ref_cnt(1) {} + +JClientSessionData::~JClientSessionData() { + guarantee(ref_cnt().get() == 0, "sanity"); + _program_data->ref_cnt().dec_and_update_time(); +} + +address JClientSessionData::get_address(AddressMap& table, address key, Thread* thread) { + address* res = table.get(key, thread); + if (res == nullptr) return nullptr; + return *res; +} + +bool JClientSessionData::put_address(AddressMap& table, address key, address value, Thread* thread) { + address* res = table.put_if_absent(key, value, thread); + return res != nullptr && *res == value; +} + +bool JClientSessionData::remove_address(AddressMap& table, address key, Thread* thread) { + return table.remove(key, thread); +} + +address JClientSessionData::client_cld_address(ClassLoaderData* server_data, Thread* thread) { + return get_address(_cl_s2c, (address) server_data, thread); +} + +ClassLoaderData* JClientSessionData::server_cld_address(address client_data, Thread* thread) { + return (ClassLoaderData*) get_address(_cl_c2s, client_data, thread); +} + +Klass* JClientSessionData::server_klass_address(address client_klass_addr, Thread* thread) { + return (Klass*) get_address(_k_c2s, client_klass_addr, thread); +} + +void JClientSessionData::add_klass_address(address client_klass_addr, + address server_cld_addr, + Thread* thread) { + put_address(_k_c2s, client_klass_addr, server_cld_addr, thread); +} + +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(); + iter != klass_array->end(); ++iter) { + InstanceKlass* klass = (InstanceKlass*) (*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); + if (method_data == nullptr) continue; + // relocate klass pointer in method_data + int position = 0; + while (position < method_data->data_size()) { + ProfileData* profile_data = method_data->data_at(position); + profile_data->klass_pointer_map_to_server(this, thread); + position += profile_data->size_in_bytes(); + } + } + } +} + +void JClientSessionData::add_method_data(address method, address method_data, Thread* thread) { + put_address(_m2md, method, method_data, thread); +} + +bool JClientSessionData::remove_method_data(address method, Thread* thread) { + return remove_address(_m2md, method, thread); +} + +MethodData* JClientSessionData::method_data_address(address method, Thread* thread) { + return (MethodData*) get_address(_m2md, method, thread); +} + +ClassLoaderData* JClientSessionData::add_class_loader_if_absent(address client_cld_addr, + const ClassLoaderKey& key, + const ClassLoaderKey& parent_key, + Thread* thread) { + // has been registered + address server_cld_addr = get_address(_cl_c2s, client_cld_addr, thread); + if (server_cld_addr != nullptr) return (ClassLoaderData*) server_cld_addr; + + // register + ClassLoaderData* data = _program_data->add_class_loader_if_absent(key, parent_key, thread); + server_cld_addr = (address) data; + bool success = put_address(_cl_s2c, server_cld_addr, client_cld_addr, thread); + if (success) { + success = put_address(_cl_c2s, client_cld_addr, server_cld_addr, thread); + } + if (!success) { + ResourceMark rm(thread); + log_trace(jbooster)("Duplicate class loader key: loader_class=%s, first_loaded_class=%s, " + "client_cld_addr=%p, server_cld_addr=%p.", + StringUtils::str(key.loader_class_name()), + StringUtils::str(key.first_loaded_class_name()), + client_cld_addr, server_cld_addr); + } + return success ? data : nullptr; +} + +// ------------------------------- ServerDataManager ------------------------------- + +ServerDataManager* ServerDataManager::_singleton = nullptr; + +ServerDataManager::ServerDataManager(TRAPS): _programs(Mutex::leaf + 1), + _sessions(Mutex::leaf + 1), + _next_program_allocation_id(0), + _next_session_allocation_id(0) { + _cache_dir_path = JBoosterCachePath; + _random_id = JBoosterManager::calc_random_id(); + + TempNewSymbol jbooster_main_name = SymbolTable::new_symbol("jdk/jbooster/JBooster"); + Klass* main_k = SystemDictionary::resolve_or_null( + jbooster_main_name, + Handle(THREAD, SystemDictionary::java_platform_loader()), + Handle(), THREAD); + guarantee(main_k != nullptr && main_k->is_instance_klass(), "sanity"); + _main_klass = InstanceKlass::cast(main_k); + + Handle platform_loader(THREAD, SystemDictionary::java_platform_loader()); + _platform_loader_data = ClassLoaderData::class_loader_data(platform_loader()); +} + +jint ServerDataManager::init_server_vm_options() { +#define DO_NOT_SET_FLAG(flag) \ + if (FLAG_IS_CMDLINE(flag)) { \ + vm_exit_during_initialization("The flag " #flag " is client-only!"); \ + } + + // Check some typical flags. + // In fact, all flags should not be used (manually) on the server. + DO_NOT_SET_FLAG(UseJBooster); + DO_NOT_SET_FLAG(JBoosterPort); + DO_NOT_SET_FLAG(JBoosterTimeout); + DO_NOT_SET_FLAG(JBoosterCachePath); + +#undef DO_NOT_SET_FLAG + if (FLAG_SET_CMDLINE(TypeProfileWidth, 8) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } + return JNI_OK; +} + +jint ServerDataManager::init_phase1() { + JBoosterManager::server_only(); + return init_server_vm_options(); +} + +void ServerDataManager::init_cache_path(const char* optional_cache_path) { + if (optional_cache_path == nullptr) { + FLAG_SET_DEFAULT(JBoosterCachePath, JBoosterManager::calc_cache_dir_path(false)); + } else { + FLAG_SET_CMDLINE(JBoosterCachePath, StringUtils::copy_to_heap(optional_cache_path, mtJBooster)); + } + FileUtils::mkdirs(JBoosterCachePath); +} + +void ServerDataManager::init_phase3(int server_port, int connection_timeout, int cleanup_timeout, const char* cache_path, TRAPS) { + JBoosterManager::server_only(); + init_cache_path(cache_path); + + _singleton = new ServerDataManager(CHECK); + + 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); +} + +/** + * The ref_cnt of the returned JClientProgramData will +1. + */ +JClientProgramData* ServerDataManager::get_program(JClientArguments* program_args, Thread* thread) { + JClientProgramData** res = _programs.get(program_args, thread); + if (res == nullptr) return nullptr; + return *res; +} + +/** + * The ref_cnt of the returned JClientProgramData will +1. + */ +JClientProgramData* ServerDataManager::get_or_create_program(JClientArguments* program_args, Thread* thread) { + JClientProgramData** res = _programs.get(program_args, thread); + if (res != nullptr) return *res; + + uint32_t program_id = Atomic::add(&_next_program_allocation_id, 1u); + JClientProgramData* new_pd = new JClientProgramData(program_id, program_args); + res = _programs.put_if_absent(new_pd->program_args(), new_pd, thread); + guarantee(res != nullptr, "sanity"); + JClientProgramData* res_pd = *res; + + if (res_pd != new_pd) { + new_pd->ref_cnt().dec(); + delete new_pd; + } + + return res_pd; +} + +bool ServerDataManager::try_remove_program(JClientArguments* program_args, Thread* thread) { + auto eval_func = [](const JClientProgramDataKey& key, JClientProgramData* pd) -> bool { + return pd->ref_cnt().get() == 0; + }; + return _programs.remove_if(program_args, eval_func, thread); +} + +/** + * We can't call a java method with a lock held by this thread. + * So we destroy the java-side data after JClientProgramDataMapEvents::on_del(). + */ +void ServerDataManager::remove_java_side_program_data(uint32_t program_id, TRAPS) { + destroy_jbooster_class_loader(program_id, THREAD); +} + +/** + * The ref_cnt of the returned JClientSessionData will +1. + */ +JClientSessionData* ServerDataManager::get_session(uint32_t session_id, Thread* thread) { + JClientSessionData** res = _sessions.get(session_id, thread); + if (res == nullptr) return nullptr; + return *res; +} + +/** + * The ref_cnt of the returned JClientSessionData will +1. + */ +JClientSessionData* ServerDataManager::create_session(uint64_t client_random_id, + JClientArguments* program_args, + 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** res = _sessions.put_if_absent(session_id, sd, thread); + guarantee(res != nullptr && *res == sd, "sanity"); + return sd; +} + +bool ServerDataManager::try_remove_session(uint32_t session_id, Thread* thread) { + auto eval_func = [](uint32_t session_id, JClientSessionData* sd) -> bool { + return sd->ref_cnt().get() == 0; + }; + return _sessions.remove_if(session_id, eval_func, thread); +} diff --git a/src/hotspot/share/jbooster/server/serverDataManager.hpp b/src/hotspot/share/jbooster/server/serverDataManager.hpp new file mode 100644 index 000000000..22b416cd9 --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverDataManager.hpp @@ -0,0 +1,428 @@ +/* + * 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 SHARE_JBOOSTER_SERVER_SERVERDATAMANAGER_HPP +#define SHARE_JBOOSTER_SERVER_SERVERDATAMANAGER_HPP + +#include "jbooster/dataTransmissionUtils.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/jClientArguments.hpp" +#include "jbooster/net/serialization.hpp" +#include "jbooster/utilities/concurrentHashMap.hpp" + +// Every client has a JClientSessionData and a JClientProgramData on the server. +// +// The JClientSessionData maintains the runtime info of the client session (a +// session represents a run of java program), such as the pointers of the class +// loaders. +// +// The JClientProgramData maintains the immutable data of a program (a program +// can be a .jar or a .class), especially the keys of the class loaders. +// +// Each JClientSessionData has one JClientProgramData and different JClientSessionData +// can share the same JClientProgramData, so the program data such as the class +// loaders can be shared between different client sessions. +// +// On the server the ServerDataManager manages the JClientSessionData list and +// the JClientProgramData list. +// +// +--------------------------------------------------------------------------+ +// | ServerDataManager | +// | +------------------+ +------------------+ | +// | | ClassLoaderTable | .--------------->| ClassLoaderTable | | +// | +------------------+ | +------------------+ | +// | A \ | / A | +// | | +--------------------+ | +--------------------+ | | +// | | | JClientProgramData | | | JClientProgramData | | | +// | | | task1.class | | | task2.jar | | | +// | | +--------------------+ | +--------------------+ | | +// | | / | / \ | | +// | | / | / \ | | +// | +--------------------+ +--------------------+ +--------------------+ | +// | | JClientSessionData | | JClientSessionData | | JClientSessionData | | +// | | 192.168.0.11 (1) | | 192.168.0.11 (2) | | 192.168.0.12 | | +// | +--------------------+ +--------------------+ +--------------------+ | +// | | +// +--------------------------------------------------------------------------+ + +// For each class loader on the client, we generate a corresponding fake class +// loader on the server. +// +// +---------------------+ +// | bootstrap loader | +// +---------------------+ +// A (share boot loader +// | and platform loader) +// +---------------------+ +// | PlatformClassLoader | +// +---------------------+ +// A A +// +-----------------------+ | | +----------------------------------------------+ +// | ClassLoaderTable1 | | | | ClassLoaderTable2 | +// | | | | | | +// | +-------------------+ | | | | +--------------------+ | +// | | FakeAppLoader1 |---+ +--------------| FakeAppLoader2 | | +// | +-------------------+ | | +--------------------+ | +// | A | | A A | +// | | | | | | | +// | +-------------------+ | | +-------------------+ +-------------------+ | +// | | FakeCustomLoader1 | | | | FakeCustomLoader3 | | FakeCustomLoader4 | | +// | +-------------------+ | | +-------------------+ +-------------------+ | +// | A | | | +// | | | +----------------------------------------------+ +// | +-------------------+ | +// | | FakeCustomLoader2 | | +// | +-------------------+ | +// | | +// +-----------------------+ + +class ClassLoaderData; +class InstanceKlass; +class ServerControlThread; +class ServerListeningThread; + +class RefCnt: public StackObj { + static const int LOCKED = -1000; + + mutable volatile int _ref_cnt; + +public: + RefCnt(int init_ref_cnt = 0): _ref_cnt(init_ref_cnt) {} + + int get() const; + int inc() const; + int dec() const; + bool try_lock() const; + bool is_locked() const { return get() == LOCKED; } +}; + +class RefCntWithTime: public RefCnt { + mutable volatile jlong _no_ref_begin_time; + +public: + RefCntWithTime(int init_ref_cnt = 0); + int dec_and_update_time() const; + jlong no_ref_time() const; + jlong no_ref_time(jlong current_time) const; +}; + +class JClientCacheState final: public StackObj { + friend class JClientProgramData; + + static const int NOT_GENERATED = 0; + 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; + +private: + NONCOPYABLE(JClientCacheState); + void update_file_timestamp(); + void remove_file_and_set_not_generated_sync(); + bool set_being_generated_from_generated(); + void set_not_generated_from_being_generated(); + +public: + JClientCacheState(); + ~JClientCacheState(); + + void init(bool allow, const char* file_path); + + bool is_allowed() { return _is_allowed; } + + // /server/cache-- + const char* file_path() { return _file_path; } + uint64_t file_timestamp() { return _file_timestamp; } + + bool is_not_generated(); + bool is_being_generated(); + bool is_generated(); + bool set_being_generated(); // from state of not_generated + void set_generated(); // from state of being_generated + void set_not_generated(); // from state of being_generate + + // 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(); + + void remove_file(); +}; + +/** + * The data of the program (usually a .class or .jar file) executed by the client. + */ +class JClientProgramData final: public CHeapObj { +public: + using ClassLoaderTable = ConcurrentHashMap; + +private: + uint32_t _program_id; // generated by the server + const char* _program_str_id; // generated by the client + JClientArguments* _program_args; // used to uniquely identify a client program + + ClassLoaderTable _class_loaders; + + RefCntWithTime _ref_cnt; + + JClientCacheState _clr_cache_state; + JClientCacheState _cds_cache_state; + JClientCacheState _aot_cache_state; + + NONCOPYABLE(JClientProgramData); + +public: + JClientProgramData(uint32_t program_id, JClientArguments* program_args); + ~JClientProgramData(); + + uint32_t program_id() const { return _program_id; } + JClientArguments* program_args() const { return _program_args; } + + ClassLoaderTable* class_loaders() { return &_class_loaders; } + + ClassLoaderData* class_loader_data(const ClassLoaderKey& key, Thread* thread); + ClassLoaderData* add_class_loader_if_absent(const ClassLoaderKey& key, + const ClassLoaderKey& parent_key, Thread* thread); + + 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; } +}; + +/** + * @see TempNewSymbol + */ +class TempJClientProgramData : public StackObj { + JClientProgramData* _temp; + +public: + TempJClientProgramData(JClientProgramData *s) : _temp(s) {} + TempJClientProgramData(const TempJClientProgramData& rhs) : _temp(rhs._temp) { if (_temp != nullptr) _temp->ref_cnt().inc(); } + ~TempJClientProgramData() { if (_temp != nullptr) _temp->ref_cnt().dec(); } + + void operator=(TempJClientProgramData rhs) = delete; + bool operator == (JClientProgramData* o) const = delete; + operator JClientProgramData*() { return _temp; } + JClientProgramData* operator -> () const { return _temp; } +}; + +/** + * The session data of a client. + */ +class JClientSessionData final: public CHeapObj { + friend class ServerStream; + +public: + using AddressMap = ConcurrentHashMap; + +private: + uint32_t _session_id; // generated by the server + uint64_t _random_id; // generated by the client + + JClientProgramData* const _program_data; + + // server-side CLD pointer -> client-side CLD pointer + AddressMap _cl_s2c; + // client-side CLD pointer -> server-side CLD pointer + AddressMap _cl_c2s; + + // client-side klass pointer -> server-side klass pointer + AddressMap _k_c2s; + + // method pointer -> method data pointer (both server-side) + AddressMap _m2md; + + RefCntWithTime _ref_cnt; + +private: + NONCOPYABLE(JClientSessionData); + + static address get_address(AddressMap& table, address key, Thread* thread); + static bool put_address(AddressMap& table, address key, address value, Thread* thread); + 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() const { return _session_id; } + + uint64_t random_id() const { return _random_id; } + + JClientProgramData* program_data() const { return _program_data; } + + 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, + const ClassLoaderKey& key, + const ClassLoaderKey& parent_key, + Thread* thread); + + 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_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); + MethodData* method_data_address(address method, Thread* thread); + + RefCntWithTime& ref_cnt() { return _ref_cnt; } +}; + +/** + * @see TempNewSymbol + */ +class TempJClientSessionData : public StackObj { + JClientSessionData* _temp; + +public: + TempJClientSessionData(JClientSessionData *s) : _temp(s) {} + TempJClientSessionData(const TempJClientSessionData& rhs) : _temp(rhs._temp) { if (_temp != nullptr) _temp->ref_cnt().inc(); } + ~TempJClientSessionData() { if (_temp != nullptr) _temp->ref_cnt().dec(); } + + void operator=(TempJClientSessionData rhs) = delete; + bool operator == (JClientSessionData* o) const = delete; + operator JClientSessionData*() { return _temp; } + JClientSessionData* operator -> () const { return _temp; } +}; + +/** + * The manager of JClientSessionData and JClientProgramData. + */ +class ServerDataManager: public CHeapObj { +public: + class JClientProgramDataKey: public StackObj { + private: + // The lifetime of _args is managed by the value of JClientProgramDataMap. + // So make sure the lifetime of JClientProgramData is outlive its JClientProgramDataKey. + JClientArguments* _args; + public: + JClientProgramDataKey(JClientArguments* args): _args(args) {} + uintx hash() const { return (uintx) _args->hash(); } + bool equals(const JClientProgramDataKey& other) const { + return _args->equals(other._args); + } + }; + + struct JClientProgramDataMapEvents { + static bool is_dead(const JClientProgramDataKey& key, JClientProgramData*& value) { return false; } + static void on_get(const JClientProgramDataKey& key, JClientProgramData*& value) { value->ref_cnt().inc(); } + static void on_del(const JClientProgramDataKey& key, JClientProgramData*& value) { + if (value->ref_cnt().get() == 0) { + // Do not invoke destroy_jbooster_class_loader() here as we are now in the lock of the map. + delete value; + } else { + // The thread lost a race to insert this newly created object. + guarantee(value->ref_cnt().get() == 1, "sanity"); + } + } + }; + + struct JClientSessionDataMapEvents { + static bool is_dead(const uint32_t& key, JClientSessionData*& value) { return false; } + static void on_get(const uint32_t& key, JClientSessionData*& value) { value->ref_cnt().inc(); } + static void on_del(const uint32_t& key, JClientSessionData*& value) { + // There shouldn't be a insert race failure here, so the ref won't be 1. + assert(value->ref_cnt().get() == 0, "sanity"); + delete value; + } + }; + + using JClientProgramDataMap = ConcurrentHashMap; + using JClientSessionDataMap = ConcurrentHashMap; + +private: + static ServerDataManager* _singleton; + + const char* _cache_dir_path; + + uint64_t _random_id; + + JClientProgramDataMap _programs; + JClientSessionDataMap _sessions; + + volatile uint32_t _next_program_allocation_id; + volatile uint32_t _next_session_allocation_id; + + ServerListeningThread* _listening_thread; + ServerControlThread* _control_thread; + + InstanceKlass* _main_klass; + ClassLoaderData* _platform_loader_data; + +private: + NONCOPYABLE(ServerDataManager); + + ServerDataManager(TRAPS); + + static jint init_server_vm_options(); + static void init_cache_path(const char* optional_cache_path); + + JClientProgramData* get_or_create_program(JClientArguments* program_args, Thread* thread); + +public: + static ServerDataManager& get() { + JBoosterManager::server_only(); + assert(_singleton != nullptr, "sanity"); + return *_singleton; + } + + 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); + + // $HOME/.jbooster/server + const char* cache_dir_path() { return _cache_dir_path; } + + uint64_t random_id() { return _random_id; } + + JClientProgramDataMap* programs() { return &_programs; } + JClientSessionDataMap* sessions() { return &_sessions; } + + JClientProgramData* get_program(JClientArguments* program_args, Thread* thread); + bool try_remove_program(JClientArguments* program_args, Thread* thread); + void remove_java_side_program_data(uint32_t program_id, TRAPS); + + JClientSessionData* get_session(uint32_t session_id, Thread* thread); + JClientSessionData* create_session(uint64_t client_random_id, + JClientArguments* program_args, + Thread* thread); + bool try_remove_session(uint32_t session_id, Thread* thread); + + ServerListeningThread* listening_thread() { return _listening_thread; } + ServerControlThread* control_thread() { return _control_thread; } + + InstanceKlass* main_klass() { return _main_klass; }; + ClassLoaderData* platform_class_loader_data() { return _platform_loader_data; } + + void log_all_state(bool print_all = false); +}; + +#endif // SHARE_JBOOSTER_SERVER_SERVERDATAMANAGER_HPP diff --git a/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp b/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp new file mode 100644 index 000000000..47e9c6b01 --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverDataManagerLog.cpp @@ -0,0 +1,145 @@ +/* + * 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. + */ + + +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/javaClasses.hpp" +#include "jbooster/jBoosterManager.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/utilities/concurrentHashMap.inline.hpp" +#include "memory/allocation.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/globals.hpp" +#include "utilities/growableArray.hpp" + +// log mark of ServerDataManager +#define OUT_0(format, args...) tty->print_cr("" format, ##args) +#define OUT_1(format, args...) tty->print_cr(" " format, ##args) +#define OUT_2(format, args...) tty->print_cr(" " format, ##args) +#define OUT_3(format, args...) tty->print_cr(" " format, ##args) +#define OUT_4(format, args...) tty->print_cr(" " format, ##args) +#define OUT_5(format, args...) tty->print_cr(" " format, ##args) + +class LogClassLoaderNodeIterator: public StackObj { + bool _print_details; + +public: + LogClassLoaderNodeIterator(bool print_details): _print_details(print_details) {} + + bool operator () (JClientProgramData::ClassLoaderTable::KVNode* p) { + const ClassLoaderKey& key = p->key(); + ClassLoaderData* cld = p->value(); + + ResourceMark rm; + Symbol* s1 = key.loader_class_name(); + Symbol* s2 = key.first_loaded_class_name(); + + OUT_3("-"); + + int cnt = 0; + for (Klass* loaded_k = cld->klasses(); loaded_k != nullptr; loaded_k = loaded_k->next_link()) { + guarantee(loaded_k->class_loader_data() == cld, "sanity"); + ++cnt; + } + + if (cld->is_builtin_class_loader_data()) { + OUT_4("loader_name: %s", cld->loader_name()); + OUT_4("loaded_class_cnt: %d", cnt); + return true; + } else { + OUT_4("client_loader_class: %s", s1 == nullptr ? "" : s1->as_C_string()); + OUT_4("first_loaded_class: %s", s2 == nullptr ? "" : s2->as_C_string()); + OUT_4("parent: %s", ClassLoaderData::class_loader_data_or_null( + java_lang_ClassLoader::parent(cld->class_loader()))->loader_name()); + OUT_4("loaded_class_cnt: %d", cnt); + if (_print_details && cnt > 0) { + OUT_4("loaded_classes:"); + for (Klass* loaded_k = cld->klasses(); loaded_k != nullptr; loaded_k = loaded_k->next_link()) { + guarantee(loaded_k->class_loader_data() == cld, "sanity"); + OUT_5("class_name: %s", loaded_k->internal_name()); + } + } + } + return true; + } +}; + +class LogJClientProgramDataIterator: public StackObj { + bool _print_details; + +public: + LogJClientProgramDataIterator(bool print_details): _print_details(print_details) {} + + 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")); + 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("class_loader_size: " SIZE_FORMAT, pd->class_loaders()->size()); + if (pd->class_loaders()->size() > 0) { + OUT_2("class_loaders:"); + LogClassLoaderNodeIterator ci(_print_details); + pd->class_loaders()->for_each(ci, Thread::current()); + } + return true; + } +}; + +class LogJClientSessionDataIterator: public StackObj { +public: + bool operator () (ServerDataManager::JClientSessionDataMap::KVNode* kv_node) { + JClientSessionData* sd = kv_node->value(); + OUT_1("-"); + OUT_2("random_id: " UINT64_FORMAT, sd->random_id()); + OUT_2("session_id: %u", sd->session_id()); + OUT_2("program_id: %u", sd->program_data()->program_id()); + OUT_2("ref_cnt: %d", sd->ref_cnt().get()); + return true; + } +}; + +void ServerDataManager::log_all_state(bool print_details) { + Thread* THREAD = Thread::current(); + ResourceMark rm(THREAD); + LogJClientSessionDataIterator ri; + LogJClientProgramDataIterator pi(print_details); + + OUT_0("JClientSessionData:"); + _sessions.for_each(ri, THREAD); + OUT_0(""); + + OUT_0("JClientProgramData:"); + _programs.for_each(pi, THREAD); + OUT_0(""); +} diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.cpp b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp new file mode 100644 index 000000000..9e66b6d4c --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverMessageHandler.cpp @@ -0,0 +1,406 @@ +/* + * 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. + */ + +#include "jbooster/lazyAot.hpp" +#include "jbooster/net/serializationWrappers.inline.hpp" +#include "jbooster/net/serverStream.hpp" +#include "jbooster/server/serverControlThread.hpp" +#include "jbooster/server/serverDataManager.hpp" +#include "jbooster/server/serverMessageHandler.hpp" +#include "jbooster/utilities/debugUtils.inline.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "runtime/interfaceSupport.inline.hpp" + +ServerMessageHandler::ServerMessageHandler(ServerStream* server_stream): _server_stream(server_stream), + _no_more_client_request(false) {} + +int ServerMessageHandler::handle_a_task_from_client(MessageType msg_type, TRAPS) { + _no_more_client_request = false; + switch (msg_type) { + case MessageType::ClientDaemonTask: + ServerDataManager::get().control_thread()->add_client_daemon_connection(&ss(), THREAD); + _no_more_client_request = true; + break; + case MessageType::CacheFilesSyncTask: + JB_RETURN(handle_cache_file_sync_task(THREAD)); + break; + case MessageType::LazyAOTCompilationTask: + JB_RETURN(handle_lazy_aot_compilation_task(THREAD)); + break; + case MessageType::NoMoreRequests: + _no_more_client_request = true; + break; + default: + JB_RETURN(JBErr::BAD_MSG_TYPE); + break; + } + return 0; +} + +int ServerMessageHandler::handle_tasks_from_client(TRAPS) { + MessageType msg_type; + do { + JB_RETURN(ss().recv_request(msg_type)); + JB_RETURN(handle_a_task_from_client(msg_type, THREAD)); + } while (!_no_more_client_request); + return 0; +} + +int ServerMessageHandler::request_class_loaders(GrowableArray *loaders, TRAPS) { + if (loaders->is_empty()) return 0; + ArrayWrapper chains_aw(false); + int size = loaders->length(); + ArrayWrapper aw(loaders); + JB_RETURN(ss().send_request(MessageType::DataOfClassLoaders, &aw)); + JB_RETURN(ss().recv_response(&chains_aw)); + guarantee(size == chains_aw.size(), "sanity"); + + JClientSessionData* sd = ss().session_data(); + ClassLoaderChain* chains = chains_aw.get_array(); + for (int i = 0; i < size; ++i) { + ClassLoaderChain& loader_chain = chains[i]; + GrowableArray* chain = loader_chain.chain(); + ClassLoaderChain::Node& last_node = chain->at(chain->length() - 1); + guarantee(last_node.key.is_boot_loader(), "sanity"); + sd->add_class_loader_if_absent((address) last_node.client_cld_addr, + last_node.key, + last_node.key, + CHECK_(JBErr::THREAD_EXCEPTION)); + for (int j = chain->length() - 2; j >= 0; --j) { + ClassLoaderChain::Node& node = chain->at(j); + ClassLoaderChain::Node& parent_node = chain->at(j + 1); + ClassLoaderData* cld = sd->add_class_loader_if_absent((address) node.client_cld_addr, + node.key, + parent_node.key, + CHECK_(JBErr::THREAD_EXCEPTION)); + if (cld == nullptr) { + // Failed to register the class loader. So skip the loaders above too. + break; + } + } + } + return 0; +} + +int ServerMessageHandler::request_klasses(GrowableArray* klasses, TRAPS) { + const int max_req_cnt = 1000; + const int klass_cnt = klasses->length(); + for (int recv_cnt = 0; recv_cnt < klass_cnt; recv_cnt += max_req_cnt) { + int req_cnt; + { ArrayWrapper kl_aw(klasses); + req_cnt = kl_aw.set_sub_arr(recv_cnt, max_req_cnt); + JB_RETURN(ss().send_request(MessageType::DataOfKlasses, &kl_aw)); + // Cannot use recv_response() here because deserialization of InstanceKlass must be in + // _threa_in_vm state. So we split it into recv_request() and parse_request(). + MessageType msg_type; + JB_RETURN(ss().recv_request(msg_type)); + if (msg_type != MessageType::DataOfKlasses) { + JB_RETURN(JBErr::BAD_MSG_TYPE); + } + } + ThreadInVMfromNative tiv(THREAD); + ArrayWrapper ik_aw(true); + JB_RETURN(ss().parse_request(&ik_aw)); + guarantee(ik_aw.size() == req_cnt, "sanity"); + } + return 0; +} + +int ServerMessageHandler::request_missing_class_loaders(TRAPS) { + JB_RETURN(ss().send_request(MessageType::ClassLoaderLocators)); + ArrayWrapper cll_aw(false); + JB_RETURN(ss().recv_response(&cll_aw)); + ClassLoaderLocator* cll_arr = cll_aw.get_array(); + + ResourceMark rm(THREAD); + GrowableArray missing_loaders; + for (int i = 0; i < cll_aw.size(); ++i) { + ClassLoaderLocator& cll = cll_arr[i]; + if (cll.class_loader_data() != nullptr) continue; + missing_loaders.append(cll); + } + + // get class loaders + JB_RETURN(request_class_loaders(&missing_loaders, THREAD)); + return 0; +} + +int ServerMessageHandler::request_missing_klasses(TRAPS) { + JB_RETURN(ss().send_request(MessageType::KlassLocators)); + ArrayWrapper kl_aw(false); + JB_RETURN(ss().recv_response(&kl_aw)); + KlassLocator* kl_arr = kl_aw.get_array(); + log_debug(jbooster, compilation)("Related klasses: %d. session_id=%u.", + kl_aw.size(), + ss().session_id()); + + ResourceMark rm(THREAD); + GrowableArray missing_klasses; + { + JavaThread* java_thread = THREAD->as_Java_thread(); + for (int i = 0; i < kl_aw.size(); ++i) { + KlassLocator& kl = kl_arr[i]; + InstanceKlass* ik; + { ThreadInVMfromNative tivm(java_thread); + ik = kl.try_to_get_ik(THREAD); + } + if (ik == nullptr) missing_klasses.append(kl); + else { + assert(ik->has_stored_fingerprint(), "add -XX:+CalculateClassFingerprint please"); + if (ik->get_stored_fingerprint() != kl.fingerprint()) { + missing_klasses.append(kl); + ResourceMark rm(THREAD); + log_warning(jbooster, compilation)("Bad fingerprint of \"%s\": client=%lu, server=%lu. session_id=%u.", + ik->internal_name(), + kl.fingerprint(), ik->get_stored_fingerprint(), + ss().session_id()); + } else { + JClientSessionData* session_data = ss().session_data(); + session_data->add_klass_address((address)(kl.client_klass()), + (address)ik, + CHECK_(JBErr::THREAD_EXCEPTION)); + } + } + } + } + log_debug(jbooster, compilation)("Missing klasses: %d. session_id=%u.", + missing_klasses.length(), + ss().session_id()); + if (!missing_klasses.is_empty()) { + JB_RETURN(request_klasses(&missing_klasses, THREAD)); + } + return 0; +} + +int ServerMessageHandler::request_method_data(TRAPS) { + ResourceMark rm(THREAD); + 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); + JB_RETURN(ss().send_request(MessageType::Profilinginfo, &aw)); + InstanceKlass** klass_array_base = NULL; + if (server_klass_array.length() > 0) { + klass_array_base = (InstanceKlass**)server_klass_array.adr_at(0); + } + ProfileDataCollector data_collector(server_klass_array.length(), klass_array_base); + JB_RETURN(ss().recv_response(&data_collector)); + } + + { + JB_RETURN(ss().send_request(MessageType::ArrayKlasses)); + MessageType msg_type; + JB_RETURN(ss().recv_request(msg_type)); + if (msg_type != MessageType::ArrayKlasses) { + JB_RETURN(JBErr::BAD_MSG_TYPE); + } + ThreadInVMfromNative tivm(THREAD); + ArrayWrapper klasses(true); + JB_RETURN(ss().parse_request(&klasses)); + } + + ss().session_data()->klass_pointer_map_to_server(&server_klass_array, THREAD); + return 0; +} + +int ServerMessageHandler::request_methods_to_compile(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + TRAPS) { + bool to_compile = true; + JB_RETURN(ss().send_request(MessageType::MethodLocators, &to_compile)); + ArrayWrapper ml_aw(false); + JB_RETURN(ss().recv_response(&ml_aw)); + InstanceKlass* last_ik = nullptr; + MethodLocator* ml_arr = ml_aw.get_array(); + for (int i = 0; i < ml_aw.size(); ++i) { + Method* m = ml_arr[i].get_method(); + if (m != nullptr) { + methods_to_compile->append(m); + InstanceKlass* ik = m->method_holder(); + if (last_ik != ik) { + last_ik = ik; + klasses_to_compile->append(ik); + } + } + } + log_debug(jbooster, compilation)("To compile: klasses=%d, methods=%d, session_id=%u.", + klasses_to_compile->length(), + methods_to_compile->length(), + ss().session_id()); + return 0; +} + +int ServerMessageHandler::request_methods_not_compile(GrowableArray* methods_not_compile, TRAPS) { + bool to_compile = false; + JB_RETURN(ss().send_request(MessageType::MethodLocators, &to_compile)); + ArrayWrapper ml_aw(false); + JB_RETURN(ss().recv_response(&ml_aw)); + MethodLocator* ml_arr = ml_aw.get_array(); + for (int i = 0; i < ml_aw.size(); ++i) { + Method* m = ml_arr[i].get_method(); + if (m != nullptr) { + methods_not_compile->append(m); + } + } + log_debug(jbooster, compilation)("Methods not compile: %d. session_id=%u.", + methods_not_compile->length(), + ss().session_id()); + return 0; +} + +int ServerMessageHandler::request_client_cache(MessageType msg_type, JClientCacheState& cache) { + if (cache.is_allowed() && !cache.is_cached() && cache.set_being_generated()) { + JB_TRY { + JB_THROW(ss().send_request(msg_type)); + FileWrapper file(cache.file_path(), SerializationMode::DESERIALIZE); + JB_THROW(file.recv_file(&ss())); + if (file.is_null()) cache.set_not_generated(); + else cache.set_generated(); + } JB_TRY_END + JB_CATCH_REST() { + cache.set_not_generated(); + return JB_ERR; + } JB_CATCH_END; + } + return 0; +} + +int ServerMessageHandler::handle_cache_file_sync_task(TRAPS) { + DebugUtils::assert_thread_nonjava_or_in_native(); + + JClientProgramData* pd = ss().session_data()->program_data(); + + JB_RETURN(request_client_cache(MessageType::CacheClassLoaderResource, pd->clr_cache_state())); + JB_RETURN(request_client_cache(MessageType::CacheAggressiveCDS, pd->cds_cache_state())); + + JB_RETURN(ss().send_request(MessageType::EndOfCurrentPhase)); + return 0; +} + +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(); + ResourceMark rm(THREAD); + GrowableArray klasses_to_compile; + GrowableArray methods_to_compile; + GrowableArray methods_not_compile; + bool compile_in_current_thread = false; + + 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_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)); + JB_THROW(request_methods_not_compile(&methods_not_compile, THREAD)); + JB_THROW(request_method_data(THREAD)); + JB_THROW(ss().send_request(MessageType::EndOfCurrentPhase)); + } + } JB_TRY_END JB_CATCH_REST() { + if (compile_in_current_thread) { + aot_cache_state.set_not_generated(); + } + return JB_ERR; + } JB_CATCH_END; + + if (compile_in_current_thread) { + JB_RETURN(try_to_compile_lazy_aot(&klasses_to_compile, + &methods_to_compile, + &methods_not_compile, + THREAD)); + } else { // not compile in current thread + if (aot_cache_state.is_being_generated()) { + log_info(jbooster, compilation)("Skippd as this program is being compiled. session_id=%u.", + ss().session_id()); + } else if (aot_cache_state.is_generated()) { + log_info(jbooster, compilation)("Skippd as this program has been compiled. session_id=%u.", + ss().session_id()); + } else { + log_error(jbooster, compilation)("Unknown compile state. session_id=%u.", + ss().session_id()); + } + } + guarantee(!(compile_in_current_thread && aot_cache_state.is_being_generated()), "some logic missing?"); + return 0; +} + +int ServerMessageHandler::try_to_compile_lazy_aot(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + TRAPS) { + JClientProgramData* pd = ss().session_data()->program_data(); + JClientCacheState& aot_cache_state = pd->aot_cache_state(); + if (klasses_to_compile->is_empty()) { + aot_cache_state.set_not_generated(); + log_error(jbooster, compilation)("Failed to compile as the compilation list is empty. session_id=%u.", + ss().session_id()); + return 0; + } + + bool successful; + int session_id = ss().session_id(); + const char* file_path = aot_cache_state.file_path(); + + 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); + } else { + successful = LazyAOT::compile_methods_by_graal(session_id, file_path, klasses_to_compile, + methods_to_compile, methods_not_compile, use_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.", + klasses_to_compile->length(), + ss().session_id()); + } else if (HAS_PENDING_EXCEPTION) { + aot_cache_state.set_not_generated(); + LogTarget(Error, jbooster, compilation) lt; + if (lt.is_enabled()) { + lt.print("Failed to compile %d classes because:", klasses_to_compile->length()); + } + DebugUtils::clear_java_exception_and_print_stack_trace(lt, THREAD); + if (lt.is_enabled()) { + lt.print("session_id=%u.", ss().session_id()); + } + } else { + aot_cache_state.set_not_generated(); + log_error(jbooster, compilation)("Failed to compile %d classes. session_id=%u.", + klasses_to_compile->length(), + ss().session_id()); + } + + return 0; +} diff --git a/src/hotspot/share/jbooster/server/serverMessageHandler.hpp b/src/hotspot/share/jbooster/server/serverMessageHandler.hpp new file mode 100644 index 000000000..6d510c6ca --- /dev/null +++ b/src/hotspot/share/jbooster/server/serverMessageHandler.hpp @@ -0,0 +1,75 @@ +/* + * 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 SHARE_JBOOSTER_SERVER_SERVERMESSAGEHANDLER_HPP +#define SHARE_JBOOSTER_SERVER_SERVERMESSAGEHANDLER_HPP + +#include "jbooster/net/messageType.hpp" +#include "memory/allocation.hpp" + +class ClassLoaderLocator; +template class GrowableArray; +class InstanceKlass; +class KlassLocator; +class Method; +class ServerStream; + +/** + * Unlike in ClientMessageHandler, the use of TRAPS is encouraged here because these + * logics are all executed in JavaThread and ServerDataManager uses THREAD a lot. + */ +class ServerMessageHandler: public StackObj { + ServerStream* _server_stream; + bool _no_more_client_request; + +private: + NONCOPYABLE(ServerMessageHandler); + + int request_class_loaders(GrowableArray *loaders, TRAPS); + int request_klasses(GrowableArray* klasses, TRAPS); + int request_missing_class_loaders(TRAPS); + int request_missing_klasses(TRAPS); + int request_method_data(TRAPS); + int request_methods_to_compile(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + TRAPS); + int request_methods_not_compile(GrowableArray* methods_not_compile, TRAPS); + int request_client_cache(MessageType msg_type, JClientCacheState& cache); + + int try_to_compile_lazy_aot(GrowableArray* klasses_to_compile, + GrowableArray* methods_to_compile, + GrowableArray* methods_not_compile, + TRAPS); +public: + ServerMessageHandler(ServerStream* server_stream); + + ServerStream& ss() { return *_server_stream; } + + int handle_a_task_from_client(MessageType msg_type, TRAPS); + int handle_tasks_from_client(TRAPS); + + int handle_cache_file_sync_task(TRAPS); + int handle_lazy_aot_compilation_task(TRAPS); +}; + +#endif // SHARE_JBOOSTER_SERVER_SERVERMESSAGEHANDLER_HPP diff --git a/src/hotspot/share/jbooster/utilities/concurrentHashMap.hpp b/src/hotspot/share/jbooster/utilities/concurrentHashMap.hpp new file mode 100644 index 000000000..199d30e53 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/concurrentHashMap.hpp @@ -0,0 +1,164 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_HPP +#define SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_HPP + +#include "memory/allocation.hpp" +#include "utilities/concurrentHashTable.hpp" +#include "utilities/globalDefinitions.hpp" + +template +struct DefaultConcurrentHashMapEvents { + static bool is_dead(const K& key, V& value) { return false; } + + static void on_get(const K& key, V& value) {} + + /** + * This function will be invoked in two cases: + * 1. The element is being removed from the map; + * 2. The element is failed to be inserted into the map (lost a race). + * Consider both cases when implementing this function. + * + * @see SymbolTableConfig::free_node + * @see ServerDataManager::get_or_create_program + */ + static void on_del(const K& key, V& value) {} +}; + +/** + * A simple and user-friendly concurrent hash map based on ConcurrentHashTable. + * + * About the `hash` of the key: + * - If K is a scalar type (arithmetic, enum, pointer and nullptr types), then + * the hash is calculated by primitive_hash(); + * - Or please implement the member function `uintx hash() const` for your + * custom K class. + * + * About the `equals` of the key: + * - Always use the operater `==`. Please overload the operator `==` in your + * custom K class. + */ +template > +class ConcurrentHashMap: public CHeapObj { +public: + class KVNode { + const K _key; + mutable V _value; + public: + KVNode(const K& key, const V& value): _key(key), _value(value) {} + const K& key() const { return _key; } + V& value() const { return _value; } + }; + +private: + class KVNodeConfig : public AllStatic { + public: + typedef KVNode Value; + static uintx get_hash(KVNode const& node, bool* is_dead); + static void* allocate_node(void* context, size_t size, KVNode const& node); + static void free_node(void* context, void* memory, KVNode const& node); + }; + + typedef ConcurrentHashTable KVNodeTable; + + class Lookup: public StackObj { + protected: + const K& _key; + const uintx _hash; + public: + Lookup(const K& key); + uintx get_hash() const { return _hash; } + bool equals(KVNode* kv_node) const; + bool is_dead(KVNode* value) const { return Events::is_dead(value->key(), value->value()); } + }; + + template + class RmLookup: public Lookup { + EVALUATE_FUNC& _eval_f; + public: + RmLookup(const K& key, EVALUATE_FUNC& eval_f): Lookup(key), _eval_f(eval_f) {} + bool equals(KVNode* kv_node) const; + }; + + class Get: public StackObj { + KVNode* _res; + public: + Get() : _res(nullptr) {} + void operator () (KVNode* kv_node); + KVNode* res() const { return _res; } + }; + +private: + const float PREF_AVG_LIST_LEN = 2.0F; + + KVNodeTable _table; + volatile size_t _items_count; + +private: + void init_lock(int lock_rank) NOT_DEBUG_RETURN; + + size_t item_added(); + size_t item_removed(); + size_t table_size(Thread* thread); + float load_factor(Thread* thread); + void grow_if_needed(Thread* thread); + +public: + ConcurrentHashMap(); + ConcurrentHashMap(int lock_rank); + virtual ~ConcurrentHashMap(); + + size_t size(); + + bool contains(const K& key, Thread* thread); + + /** + * Returns the value if found, or returns null. + */ + V* get(const K& key, Thread* thread); + + /** + * Returns the input value if the insertion is successful, + * or returns the previously inserted value. + */ + V* put_if_absent(const K& key, V& value, Thread* thread); + + /** + * Returns whether the key is deleted. + * * We cannot return the pointer of the removed value because + * it has been deleted. + */ + bool remove(const K& key, Thread* thread); + + template + bool remove_if(const K& key, EVALUATE_FUNC& eval_f, Thread* thread); + + template + void for_each(SCAN_FUNC& scan_f, Thread* thread); + + template + void bulk_remove_if(EVALUATE_FUNC& eval_f, DELETE_FUNC& del_f, Thread* thread); +}; + +#endif // SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_HPP diff --git a/src/hotspot/share/jbooster/utilities/concurrentHashMap.inline.hpp b/src/hotspot/share/jbooster/utilities/concurrentHashMap.inline.hpp new file mode 100644 index 000000000..e69da290f --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/concurrentHashMap.inline.hpp @@ -0,0 +1,244 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_INLINE_HPP +#define SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_INLINE_HPP + +#include + +#include "jbooster/utilities/concurrentHashMap.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "utilities/concurrentHashTable.inline.hpp" + +template +struct ConcurrentKeyHashEquals: public AllStatic { + static uintx hash(const K& k) { return k.hash(); } + static bool equals(const K& k1, const K& k2) { return k1.equals(k2); } +}; + +template +struct ConcurrentKeyHashEquals::value>::type>: public AllStatic { + static uintx hash(const K& key) { return primitive_hash(key); } + static bool equals(const K& k1, const K& k2) { return k1 == k2; } +}; + +template +inline uintx ConcurrentHashMap::KVNodeConfig::get_hash(KVNode const& kv_node, bool* is_dead) { + *is_dead = Events::is_dead(kv_node.key(), kv_node.value()); + if (*is_dead) return 0; + return ConcurrentKeyHashEquals::hash(kv_node.key()); +} + +template +inline void* ConcurrentHashMap::KVNodeConfig::allocate_node(void* context, + size_t size, + KVNode const& kv_node) { + static_cast*>(context)->item_added(); + return AllocateHeap(size, F); +} + +template +inline void ConcurrentHashMap::KVNodeConfig::free_node(void* context, + void* memory, + KVNode const& kv_node) { + Events::on_del(kv_node.key(), kv_node.value()); + kv_node.~KVNode(); + FreeHeap(memory); + static_cast*>(context)->item_removed(); +} + +template +inline ConcurrentHashMap::Lookup::Lookup(const K& key): + _key(key), + _hash(ConcurrentKeyHashEquals::hash(key)) {} + +template +inline bool ConcurrentHashMap::Lookup::equals(KVNode* kv_node) const { + if (ConcurrentKeyHashEquals::equals(_key, kv_node->key())) { + Events::on_get(kv_node->key(), kv_node->value()); + return true; + } + return false; +} + +template +template +inline bool ConcurrentHashMap::RmLookup::equals(KVNode* kv_node) const { + // Do not invoke Events::on_get() here. + return ConcurrentKeyHashEquals::equals(Lookup::_key, kv_node->key()) + && _eval_f(kv_node->key(), kv_node->value()); +} + +template +inline void ConcurrentHashMap::Get::operator () (KVNode* kv_node) { + _res = kv_node; +} + +template +inline ConcurrentHashMap::ConcurrentHashMap(): _table(8), _items_count(0) { + _table._context = static_cast(this); +} + +template +inline ConcurrentHashMap::ConcurrentHashMap(int lock_rank): _table(8), _items_count(0) { + init_lock(lock_rank); + _table._context = static_cast(this); +} + +#ifdef ASSERT +template +inline void ConcurrentHashMap::init_lock(int lock_rank) { + if (_table._resize_lock->rank() == lock_rank) return; + delete _table._resize_lock; + _table._resize_lock = new Mutex(lock_rank, "ConcurrentHashMap", true, + Mutex::_safepoint_check_never); +} +#endif // ASSERT + +template +inline ConcurrentHashMap::~ConcurrentHashMap() {} + +template +inline size_t ConcurrentHashMap::item_added() { + return Atomic::add(&_items_count, (size_t) 1); +} + +template +inline size_t ConcurrentHashMap::item_removed() { + return Atomic::add(&_items_count, (size_t) -1); +} + +template +inline size_t ConcurrentHashMap::table_size(Thread* thread) { + return ((size_t) 1) << _table.get_size_log2(thread); +} + +template +inline float ConcurrentHashMap::load_factor(Thread* thread) { + return float(_items_count) / float(table_size(thread)); +} + +template +inline void ConcurrentHashMap::grow_if_needed(Thread* thread) { + if (load_factor(thread) > PREF_AVG_LIST_LEN && !_table.is_max_size_reached()) { + _table.grow(thread); + } +} + +template +inline size_t ConcurrentHashMap::size() { + return Atomic::load_acquire(&_items_count); +} + +template +inline bool ConcurrentHashMap::contains(const K& key, Thread* thread) { + Lookup lookup(key); + Get get; + bool grow_hint = false; + + return _table.get(thread, lookup, get, &grow_hint); +} + +template +inline V* ConcurrentHashMap::get(const K& key, Thread* thread) { + Lookup lookup(key); + Get get; + bool grow_hint = false; + + V* res = nullptr; + + if (_table.get(thread, lookup, get, &grow_hint)) { + KVNode& kv_node = *get.res(); + res = &kv_node.value(); + } + + return res; +} + +template +inline V* ConcurrentHashMap::put_if_absent(const K& key, V& value, Thread* thread) { + Lookup lookup(key); + Get get; + bool grow_hint = false; + bool clean_hint = false; + + V* res = nullptr; + + do { + if (_table.insert(thread, lookup, KVNode(key, value), &grow_hint, &clean_hint)) { + res = &value; + break; + } + + if (_table.get(thread, lookup, get, &grow_hint)) { + KVNode& kv_node = *get.res(); + res = &kv_node.value(); + break; + } + } while (true); + + if (grow_hint) { + grow_if_needed(thread); + } + + return res; +} + +template +inline bool ConcurrentHashMap::remove(const K& key, Thread* thread) { + Lookup lookup(key); + return _table.remove(thread, lookup); +} + +template +template +bool ConcurrentHashMap::remove_if(const K& key, EVALUATE_FUNC& eval_f, Thread* thread) { + RmLookup lookup(key, eval_f); + return _table.remove(thread, lookup); +} + +template +template +inline void ConcurrentHashMap::for_each(SCAN_FUNC& scan_f, Thread* thread) { + // Unlike ConcurrentHashTable, ConcurrentHashMap never do scan without resize lock. + // So we don't have to assert `!SafepointSynchronize::is_at_safepoint()`. + assert(_table._resize_lock_owner != thread, "Re-size lock held"); + _table.lock_resize_lock(thread); + _table.do_scan_locked(thread, scan_f); + _table.unlock_resize_lock(thread); + assert(_table._resize_lock_owner != thread, "Re-size lock held"); +} + +template +template +inline void ConcurrentHashMap::bulk_remove_if(EVALUATE_FUNC& eval_f, DELETE_FUNC& del_f, Thread* thread) { + // Unlike ConcurrentHashTable, ConcurrentHashMap never do scan without resize lock. + // So we don't have to assert `!SafepointSynchronize::is_at_safepoint()`. + assert(_table._resize_lock_owner != thread, "Re-size lock held"); + _table.lock_resize_lock(thread); + _table.do_bulk_delete_locked(thread, eval_f, del_f); + _table.unlock_resize_lock(thread); + assert(_table._resize_lock_owner != thread, "Re-size lock held"); +} + +#endif // SHARE_JBOOSTER_UTILITIES_CONCURRENTHASHMAP_INLINE_HPP diff --git a/src/hotspot/share/jbooster/utilities/debugUtils.cpp b/src/hotspot/share/jbooster/utilities/debugUtils.cpp new file mode 100644 index 000000000..44c2fcca0 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/debugUtils.cpp @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include "jbooster/utilities/debugUtils.inline.hpp" + +#ifdef ASSERT +void DebugUtils::assert_thread_in_vm() { + JavaThread* THREAD = JavaThread::current(); + assert(THREAD->thread_state() == _thread_in_vm, "sanity"); +} + +void DebugUtils::assert_thread_in_native() { + JavaThread* THREAD = JavaThread::current(); + assert(THREAD->thread_state() == _thread_in_native, "sanity"); +} + +void DebugUtils::assert_thread_nonjava_or_in_native() { + Thread* THREAD = Thread::current(); + assert(!THREAD->is_Java_thread() || THREAD->as_Java_thread()->thread_state() == _thread_in_native, "sanity"); +} +#endif diff --git a/src/hotspot/share/jbooster/utilities/debugUtils.hpp b/src/hotspot/share/jbooster/utilities/debugUtils.hpp new file mode 100644 index 000000000..611a31e7f --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/debugUtils.hpp @@ -0,0 +1,61 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_HPP +#define SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_HPP + +#include "logging/log.hpp" +#include "memory/allStatic.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/globalDefinitions.hpp" + +class DebugUtils: public AllStatic { +private: +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define PLATFORM_FUNC_SIG __PRETTY_FUNCTION__ + static const int _func_sig_prefix_len = 53; // "static const char* DebugUtils::type_name() [with T = " + static const int _func_sig_suffix_len = 1; // "]" +#elif defined(_MSC_VER) +#define PLATFORM_FUNC_SIG __FUNCSIG__ + static const int _func_sig_prefix_len = 0; // not done yet + static const int _func_sig_suffix_len = 1; // not done yet +#else +#define PLATFORM_FUNC_SIG __func__ + static const int _func_sig_prefix_len = 0; // not done yet + static const int _func_sig_suffix_len = 0; // not done yet +#endif + +public: + static void assert_thread_in_vm() NOT_DEBUG_RETURN; + static void assert_thread_in_native() NOT_DEBUG_RETURN; + static void assert_thread_nonjava_or_in_native() NOT_DEBUG_RETURN; + + template + static const char* type_name(); + + template + static void clear_java_exception_and_print_stack_trace(LogTargetImpl& lt, TRAPS); +}; + +#endif // SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_HPP diff --git a/src/hotspot/share/jbooster/utilities/debugUtils.inline.hpp b/src/hotspot/share/jbooster/utilities/debugUtils.inline.hpp new file mode 100644 index 000000000..9c4e58ec4 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/debugUtils.inline.hpp @@ -0,0 +1,67 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_INLINE_HPP +#define SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_INLINE_HPP + +#include "classfile/javaClasses.inline.hpp" +#include "jbooster/utilities/debugUtils.hpp" +#include "logging/logStream.hpp" +#include "memory/allocation.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/vmError.hpp" + +/** + * Attention: the type name string is allocated on the heap! + */ +template +const char* DebugUtils::type_name() { + const char* sig = PLATFORM_FUNC_SIG; + const int sig_len = strlen(sig); + const int res_len = sig_len - _func_sig_prefix_len - _func_sig_suffix_len; + char* res = NEW_C_HEAP_ARRAY(char, res_len + 1, mtJBooster); + memcpy(res, sig + _func_sig_prefix_len, res_len); + res[res_len] = '\0'; + return res; +} + +template +void DebugUtils::clear_java_exception_and_print_stack_trace(LogTargetImpl& lt, TRAPS) { + if (!HAS_PENDING_EXCEPTION) return; + + if (lt.is_enabled()) { + ResourceMark rm(THREAD); + Handle ex(THREAD, THREAD->pending_exception()); + CLEAR_PENDING_EXCEPTION; + LogStream ls(lt); + assert(THREAD->thread_state() == _thread_in_vm, "sanity"); + java_lang_Throwable::print_stack_trace(ex, &ls); + } else { + CLEAR_PENDING_EXCEPTION; + } +} + +#endif // SHARE_JBOOSTER_UTILITIES_DEBUGUTILS_INLINE_HPP diff --git a/src/hotspot/share/jbooster/utilities/fileUtils.cpp b/src/hotspot/share/jbooster/utilities/fileUtils.cpp new file mode 100644 index 000000000..f19bf8fb3 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/fileUtils.cpp @@ -0,0 +1,178 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "jbooster/net/messageBuffer.inline.hpp" +#include "jbooster/net/serializationWrappers.hpp" +#include "jbooster/utilities/fileUtils.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/os.inline.hpp" + +static const char* get_home_path() { + const char* res = ::getenv("HOME"); + if ((res == nullptr) || (*res == '\0')) { + struct passwd* pwd = getpwuid(geteuid()); + if (pwd != nullptr) res = pwd->pw_dir; + } + guarantee(res != nullptr && *res != '\0', "failed to get home"); + return res; +} + +const char* FileUtils::separator() { + return os::file_separator(); +} + +char FileUtils::separator_char() { + assert(strlen(separator()) == 1, "sanity"); + return separator()[0]; +} + +const char* FileUtils::home_path() { + static const char* home_dir = get_home_path(); + return home_dir; +} + +bool FileUtils::exists(const char* path) { + struct stat st = {0}; + return os::stat(path, &st) == 0; +} + +bool FileUtils::is_file(const char* path) { + struct stat st = {0}; + if (os::stat(path, &st) != 0) return false; + return S_ISREG(st.st_mode); +} + +bool FileUtils::is_dir(const char* path) { + struct stat st = {0}; + if (os::stat(path, &st) != 0) return false; + return S_ISDIR(st.st_mode); +} + +uint64_t FileUtils::modify_time(const char* path) { + struct stat st = {0}; + if (os::stat(path, &st) != 0) return 0; + return st.st_mtime; +} + +bool FileUtils::mkdir(const char* path) { + if (::mkdir(path, 0777) == OS_ERR) { + if (errno == EEXIST) return false; + else fatal("Cannot mkdir: %s. Err: %s", path, os::strerror(errno)); + } + return true; +} + +bool FileUtils::mkdirs(const char* path) { + int len = strlen(path) + 1; + // We don't use NEW_RESOURCE_ARRAY here as Thread::current() may + // not be initialized yet. + char* each_path = NEW_C_HEAP_ARRAY(char, len, mtJBooster); + memcpy((void*)each_path, path, len); + + const char* sp = separator(); + int sp_len = strlen(sp); + for (char* p = each_path + 1, *end = each_path + len; p < end; ++p) { + if (strncmp(p, sp, sp_len) != 0) continue; + *p = '\0'; + if (!exists(each_path)) mkdir(each_path); + *p = *sp; + } + FREE_C_HEAP_ARRAY(char, each_path); + return mkdir(path); +} + +bool FileUtils::rename(const char* path_from, const char* path_to) { + return ::rename(path_from, path_to) == 0; +} + +bool FileUtils::move(const char* path_from, const char* path_to) { + return rename(path_from, 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 new file mode 100644 index 000000000..2b5734754 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/fileUtils.hpp @@ -0,0 +1,76 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_FILEUTILS_HPP +#define SHARE_JBOOSTER_UTILITIES_FILEUTILS_HPP + +#ifdef LINUX +#include +#endif // LINUX + +#include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" + +// @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 FileUtils: AllStatic { +public: + static const char* separator(); + static char separator_char(); + static const char* home_path(); + static bool exists(const char* path); + static bool is_file(const char* path); + static bool is_dir(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; + +#ifdef LINUX + DIR* _ds; + dirent* _cur; +#endif // LINUX + + public: + ListDir(const char* path); + ~ListDir(); + + bool next(); + + const char* name(); + bool is_file(); + bool is_dir(); + }; +}; + +#endif // SHARE_JBOOSTER_UTILITIES_FILEUTILS_HPP diff --git a/src/hotspot/share/jbooster/utilities/scalarHashMap.hpp b/src/hotspot/share/jbooster/utilities/scalarHashMap.hpp new file mode 100644 index 000000000..30b9ab605 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/scalarHashMap.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 SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_HPP +#define SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_HPP + +#include "utilities/hashtable.hpp" + +/** + * A simple hash map that does not manage the lifecycle of the storage elements. + */ +template +class ScalarHashMap: protected KVHashtable { + static_assert(std::is_scalar::value, "K must be scalar because we use primitive_hash() and primitive_equals()"); + static_assert(std::is_scalar::value, "V must be scalar because we don't manage its lifecycle at all"); + + using Entry = typename KVHashtable::KVHashtableEntry; + + static const int _resize_load_trigger = 4; + + bool resize_if_needed(); + +public: + ScalarHashMap(int table_size = 107); + virtual ~ScalarHashMap(); + + V* get(K key) const { + return KVHashtable::lookup(key); + } + + V* add_if_absent(K key, V value, bool* p_created) { + V* res = KVHashtable::add_if_absent(key, value, p_created); + if (*p_created) { + resize_if_needed(); + } + return res; + } + + void clear(); + + int size() const { return KVHashtable::number_of_entries(); } +}; + +/** + * A simple hash set that does not manage the lifecycle of the storage elements. + * We do not choose to extend Hashtable because it uses hashtable.cpp, which leads + * to its poor availability (because of template). + */ +template +class ScalarHashSet: protected ScalarHashMap { + +public: + ScalarHashSet(int table_size = 107): ScalarHashMap(table_size) {} + + /** + * true: existing; false: not found + */ + bool has(T o) const { + return ScalarHashMap::get(o) != nullptr; + } + + /** + * true: added; false: existing + */ + bool add(T o) { + bool created = false; + ScalarHashMap::add_if_absent(o, true, &created); + return created; + } + + void clear() { ScalarHashMap::clear(); } + + int size() const { return ScalarHashMap::number_of_entries(); } +}; + +#endif // SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_HPP diff --git a/src/hotspot/share/jbooster/utilities/scalarHashMap.inline.hpp b/src/hotspot/share/jbooster/utilities/scalarHashMap.inline.hpp new file mode 100644 index 000000000..4564f08e9 --- /dev/null +++ b/src/hotspot/share/jbooster/utilities/scalarHashMap.inline.hpp @@ -0,0 +1,72 @@ +/* + * 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 SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_INLINE_HPP +#define SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_INLINE_HPP + +#include "jbooster/utilities/scalarHashMap.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/hashtable.inline.hpp" + +template +inline ScalarHashMap::ScalarHashMap(int table_size): KVHashtable(table_size) {} + +template +inline ScalarHashMap::~ScalarHashMap() { + clear(); + // Base class ~BasicHashtable deallocates the buckets. + // Note that ~BasicHashtable is not virtual. Be careful. +} + +template +inline void ScalarHashMap::clear() { + for (int i = 0; i < KVHashtable::table_size(); ++i) { + for (Entry* e = KVHashtable::bucket(i); e != nullptr;) { + Entry* cur = e; + // read next before freeing. + e = e->next(); + KVHashtable::free_entry(cur); + } + Entry** p = (Entry**) KVHashtable::bucket_addr(i); + *p = nullptr; // clear out buckets. + } + assert((KVHashtable::number_of_entries()) == 0, "sanity"); +} + +/** + * BasicHashtable::maybe_grow is not good enough. + */ +template +inline bool ScalarHashMap::resize_if_needed() { + if (KVHashtable::number_of_entries() > (_resize_load_trigger * KVHashtable::table_size())) { + int desired_size = KVHashtable::calculate_resize(false); + if (desired_size == KVHashtable::table_size()) { + return false; + } + assert(desired_size != 0, "sanity"); + return KVHashtable::resize(desired_size); + } + return false; +} + +#endif // SHARE_JBOOSTER_UTILITIES_SCALARHASHMAP_INLINE_HPP diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 7f31b5e66..d8ae64bc8 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -36,6 +36,7 @@ LOG_TAG(age) \ LOG_TAG(alloc) \ LOG_TAG(annotation) \ + JBOOSTER_ONLY(LOG_TAG(aot)) \ LOG_TAG(arguments) \ LOG_TAG(attach) \ LOG_TAG(barrier) \ @@ -91,6 +92,7 @@ LOG_TAG(install) \ LOG_TAG(interpreter) \ LOG_TAG(itables) \ + JBOOSTER_ONLY(LOG_TAG(jbooster)) \ LOG_TAG(jfr) \ LOG_TAG(jit) \ LOG_TAG(jni) \ @@ -154,10 +156,12 @@ LOG_TAG(reloc) \ LOG_TAG(remset) \ LOG_TAG(resolve) \ + JBOOSTER_ONLY(LOG_TAG(rpc)) \ LOG_TAG(safepoint) \ LOG_TAG(sampling) \ LOG_TAG(scavenge) \ LOG_TAG(sealed) \ + JBOOSTER_ONLY(LOG_TAG(serialization)) \ LOG_TAG(setting) \ LOG_TAG(smr) \ LOG_TAG(stackbarrier) \ diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index e881b577b..4efce9d97 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -146,6 +146,7 @@ class AllocatedObj { f(mtMetaspace, "Metaspace") \ f(mtStringDedup, "String Deduplication") \ f(mtObjectMonitor, "Object Monitors") \ + JBOOSTER_ONLY(f(mtJBooster, "JBooster")) \ f(mtNone, "Unknown") \ //end diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index fdee66a88..47c4d31c1 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -4244,6 +4244,18 @@ void InstanceKlass::log_to_classlist() const { #endif // INCLUDE_CDS } +#if INCLUDE_JBOOSTER +static Klass* _proxy_klass = nullptr; +bool InstanceKlass::is_dynamic_proxy() const { + if (_proxy_klass == nullptr) { + TempNewSymbol sym = SymbolTable::new_symbol("java/lang/reflect/Proxy", 23); + _proxy_klass = SystemDictionary::find_instance_klass(sym, Handle(), Handle()); + } + if (_proxy_klass == nullptr) return false; + return is_subclass_of(_proxy_klass); +} +#endif // INCLUDE_JBOOSTER + // Make a step iterating over the class hierarchy under the root class. // Skips subclasses if requested. void ClassHierarchyIterator::next() { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 23b773d01..c111a6ab5 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -1274,6 +1274,16 @@ public: void print_class_load_logging(ClassLoaderData* loader_data, const ModuleEntry* module_entry, const ClassFileStream* cfs) const; + +#if INCLUDE_JBOOSTER + bool is_dynamic_proxy() const; + + // [JBOOSTER TODO] aot + bool should_store_fingerprint() const { return true; } + bool has_stored_fingerprint() const { return true; } + uint64_t get_stored_fingerprint() const { return 0u; } + void store_fingerprint(uint64_t fingerprint) {} +#endif // INCLUDE_JBOOSTER }; // for adding methods diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 93fcbd139..d3e77ba70 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -91,6 +91,10 @@ class Method : public Metadata { _intrinsic_candidate = 1 << 5, _reserved_stack_access = 1 << 6, _scoped = 1 << 7 +#if INCLUDE_JBOOSTER + , + _rewrite_invokehandle = 1 << 8 +#endif // INCLUDE_JBOOSTER }; mutable u2 _flags; @@ -992,6 +996,17 @@ public: // Inlined elements address* native_function_addr() const { assert(is_native(), "must be native"); return (address*) (this+1); } address* signature_handler_addr() const { return native_function_addr() + 1; } + +#if INCLUDE_JBOOSTER + public: + bool is_rewrite_invokehandle() { + return (_flags & _rewrite_invokehandle) != 0; + } + + void set_rewrite_invokehandle(bool x) { + _flags = x ? (_flags | _rewrite_invokehandle) : (_flags & ~_rewrite_invokehandle); + } +#endif // INCLUDE_JBOOSTER }; diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 101081748..266c48a5e 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -43,6 +43,9 @@ #include "runtime/signature.hpp" #include "utilities/align.hpp" #include "utilities/copy.hpp" +#if INCLUDE_JBOOSTER +#include "jbooster/server/serverDataManager.hpp" +#endif // INCLUDE_JBOOSTER // ================================================================== // DataLayout @@ -366,6 +369,46 @@ void ReturnTypeEntry::print_data_on(outputStream* st) const { st->cr(); } +#if INCLUDE_JBOOSTER + +static void collect_a_klass(GrowableArray* ik_array, + GrowableArray* ak_array, + Klass* k) { + if (k != NULL) { + if (k->is_array_klass()) { + ArrayKlass* ak = ArrayKlass::cast(k); + ak_array->append(ak); + return; + } + guarantee(k->is_instance_klass(), "sanity"); + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_hidden() || ik->is_dynamic_proxy()) return; + Klass* loader_klass = ik->class_loader_data()->class_loader_klass(); + if (loader_klass != NULL && + loader_klass->is_subtype_of(vmClasses::reflect_DelegatingClassLoader_klass())) { + return; + } + ik_array->append(ik); + } +} + +void ReturnTypeEntry::klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) { + intptr_t p = type(); + Klass* client_klass = (Klass*)klass_part(p); + if (client_klass != NULL) { + Klass* server_klass = session_data->server_klass_address((address)client_klass, thread); + set_type(with_status(server_klass, p)); + } +} + +void ReturnTypeEntry::collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) { + intptr_t p = type(); + Klass* k = (Klass*)klass_part(p); + collect_a_klass(ik_array, ak_array, k); +} + +#endif // INCLUDE_JBOOSTER + void CallTypeData::print_data_on(outputStream* st, const char* extra) const { CounterData::print_data_on(st, extra); if (has_arguments()) { @@ -441,6 +484,38 @@ void ReceiverTypeData::print_data_on(outputStream* st, const char* extra) const print_receiver_data_on(st); } +#if INCLUDE_JBOOSTER + +void ReceiverTypeData::clean_untrusted_entries() { + for (uint row = 0; row < row_limit(); row++) { + set_receiver(row, (Klass*)NULL); + } +} + +void ReceiverTypeData::klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) { + uint row = 0; + for (row = 0; row < row_limit(); row++) { + Klass* client_klass = receiver_no_check(row); + if (client_klass != NULL) { + Klass* server_klass = session_data->server_klass_address((address)client_klass, thread); + if (server_klass == nullptr) break; + set_receiver(row, server_klass); + } + } + if (row != row_limit()) { + clean_untrusted_entries(); + } +} + +void ReceiverTypeData::collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) { + for (uint row = 0; row < row_limit(); row++) { + Klass* k = receiver_no_check(row); + collect_a_klass(ik_array, ak_array, k); + } +} + +#endif // INCLUDE_JBOOSTER + void VirtualCallData::print_data_on(outputStream* st, const char* extra) const { print_shared(st, "VirtualCallData", extra); print_receiver_data_on(st); @@ -628,6 +703,41 @@ int ParametersTypeData::compute_cell_count(Method* m) { return 0; } +#if INCLUDE_JBOOSTER + +void TypeStackSlotEntries::clean_untrusted_entries() { + for (int i = 0; i < _number_of_entries; i++) { + intptr_t p = type(i); + set_type(i, with_status((Klass*)NULL, p)); + } +} + +void TypeStackSlotEntries::klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) { + int index = 0; + for (int index = 0; index < _number_of_entries; index++) { + intptr_t p = type(index); + Klass* client_klass = (Klass*)klass_part(p); + if (client_klass != NULL) { + Klass* server_klass = session_data->server_klass_address((address)client_klass, thread); + if (server_klass == nullptr) break; + set_type(index, with_status(server_klass, p)); + } + } + if (index != _number_of_entries) { + clean_untrusted_entries(); + } +} + +void TypeStackSlotEntries::collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) { + for (int i = 0; i < _number_of_entries; i++) { + intptr_t p = type(i); + Klass* k = (Klass*)klass_part(p); + collect_a_klass(ik_array, ak_array, k); + } +} + +#endif // INCLUDE_JBOOSTER + void ParametersTypeData::post_initialize(BytecodeStream* stream, MethodData* mdo) { _parameters.post_initialize(mdo->method()->signature(), !mdo->method()->is_static(), true); } @@ -1749,7 +1859,7 @@ void MethodData::clean_extra_data(CleanExtraDataClosure* cl) { SpeculativeTrapData* data = new SpeculativeTrapData(dp); Method* m = data->method(); assert(m != NULL, "should have a method"); - if (!cl->is_live(m)) { + if (!cl->is_live(m) JBOOSTER_ONLY(|| AsJBooster)) { // "shift" accumulates the number of cells for dead // SpeculativeTrapData entries that have been seen so // far. Following entries must be shifted left by that many @@ -1832,3 +1942,46 @@ void MethodData::clean_weak_method_links() { clean_extra_data(&cl); verify_extra_data_clean(&cl); } + +#if INCLUDE_JBOOSTER +/** + * The "bool* ignored" is just to distinguish it from another constructor. + */ +MethodData::MethodData(const methodHandle& method, const bool* ignored) + : _method(method()), + _extra_data_lock(Mutex::leaf, "MDO extra data lock"), + _compiler_counters(), + _parameters_type_data_di(parameters_uninitialized) { + // part of initialize() + Thread* thread = Thread::current(); + NoSafepointVerifier no_safepoint; + ResourceMark rm(thread); + init(); + set_creation_mileage(mileage_of(method())); + int data_size = 0; + int empty_bc_count = 0; + _data[0] = 0; +} + +MethodData* MethodData::create_instance_for_jbooster(Method* method, int size, char* mem, TRAPS) { + ClassLoaderData* loader_data = method->method_holder()->class_loader_data(); + MethodData* res = new (loader_data, size, MetaspaceObj::MethodDataType, THREAD) + MethodData(methodHandle(THREAD, method), (const bool*) nullptr); + + // backup + char lock_bak[sizeof(res->_extra_data_lock)]; + memcpy((void*) lock_bak, &res->_extra_data_lock, sizeof(res->_extra_data_lock)); + FailedSpeculation* fs_bak = res->_failed_speculations; + + // memcpy + int start = in_bytes(byte_offset_of(MethodData, _method)) + sizeof(res->_method); + memcpy((void*) (((char*) res) + start), mem + start, size); + + // restore + memcpy((void*) &res->_extra_data_lock, lock_bak, sizeof(res->_extra_data_lock)); + res->_failed_speculations = fs_bak; + res->set_size(size); + + return res; +} +#endif // INCLUDE_JBOOSTER diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index 55967e9ee..a83b373a8 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -35,6 +35,10 @@ #include "utilities/align.hpp" #include "utilities/copy.hpp" +#if INCLUDE_JBOOSTER +class JClientSessionData; +#endif // INCLUDE_JBOOSTER + class BytecodeStream; // The MethodData object collects counts and other profile information @@ -476,6 +480,11 @@ public: void print_shared(outputStream* st, const char* name, const char* extra) const; void tab(outputStream* st, bool first = false) const; + +#if INCLUDE_JBOOSTER + virtual void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) {} + virtual void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) {} +#endif // INCLUDE_JBOOSTER }; // BitData @@ -750,6 +759,15 @@ public: void set_profile_data(ProfileData* pd) { _pd = pd; } + +#if INCLUDE_JBOOSTER + virtual void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) { + assert(false, "override it"); + } + virtual void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) { + assert(false, "override it"); + } +#endif // INCLUDE_JBOOSTER }; // Type entries used for arguments passed at a call and parameters on @@ -839,6 +857,12 @@ public: void clean_weak_klass_links(bool always_clean); void print_data_on(outputStream* st) const; + +#if INCLUDE_JBOOSTER + void clean_untrusted_entries(); + void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) override; + void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) override; +#endif // INCLUDE_JBOOSTER }; // Type entry used for return from a call. A single cell to record the @@ -882,6 +906,11 @@ public: void clean_weak_klass_links(bool always_clean); void print_data_on(outputStream* st) const; + +#if INCLUDE_JBOOSTER + void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) override; + void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) override; +#endif // INCLUDE_JBOOSTER }; // Entries to collect type information at a call: contains arguments @@ -1073,6 +1102,25 @@ public: } virtual void print_data_on(outputStream* st, const char* extra = NULL) const; + +#if INCLUDE_JBOOSTER + void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) override { + if (has_arguments()) { + _args.klass_pointer_map_to_server(session_data, thread); + } + if (has_return()) { + _ret.klass_pointer_map_to_server(session_data, thread); + } + } + void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) override { + if (has_arguments()) { + _args.collect_klass(ik_array, ak_array); + } + if (has_return()) { + _ret.collect_klass(ik_array, ak_array); + } + } +#endif // INCLUDE_JBOOSTER }; // ReceiverTypeData @@ -1213,6 +1261,17 @@ public: void print_receiver_data_on(outputStream* st) const; void print_data_on(outputStream* st, const char* extra = NULL) const; + +#if INCLUDE_JBOOSTER + Klass* receiver_no_check(uint row) const { + assert(row < row_limit(), "oob"); + Klass* recv = (Klass*)intptr_at(receiver_cell_index(row)); + return recv; + } + void clean_untrusted_entries(); + void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) override; + void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) override; +#endif // INCLUDE_JBOOSTER }; // VirtualCallData @@ -1784,6 +1843,15 @@ public: static ByteSize type_offset(int i) { return cell_offset(type_local_offset(i)); } + +#if INCLUDE_JBOOSTER + void klass_pointer_map_to_server(JClientSessionData* session_data, Thread* thread) override { + _parameters.klass_pointer_map_to_server(session_data, thread); + } + void collect_klass(GrowableArray* ik_array, GrowableArray* ak_array) override { + _parameters.collect_klass(ik_array, ak_array); + } +#endif // INCLUDE_JBOOSTER }; // SpeculativeTrapData @@ -2476,6 +2544,13 @@ public: void clean_method_data(bool always_clean); void clean_weak_method_links(); Mutex* extra_data_lock() { return &_extra_data_lock; } + +#if INCLUDE_JBOOSTER +private: + MethodData(const methodHandle& method, const bool* ignored); +public: + static MethodData* create_instance_for_jbooster(Method* method, int size, char* mem, TRAPS); +#endif // INCLUDE_JBOOSTER }; #endif // SHARE_OOPS_METHODDATA_HPP diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 9e29c3300..8ceca7cd3 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -104,6 +104,12 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_JBOOSTER +#include "jbooster/client/clientDataManager.hpp" +#include "jbooster/client/clientMessageHandler.hpp" +#include "jbooster/net/serverListeningThread.hpp" +#include "jbooster/server/serverDataManager.hpp" +#endif // INCLUDE_JBOOSTER #include @@ -3850,3 +3856,39 @@ JVM_END JVM_ENTRY_NO_ENV(jint, JVM_FindSignal(const char *name)) return os::get_signal_number(name); JVM_END + +// JBooster //////////////////////////////////////////////////////////////////////// + +JVM_ENTRY(void, JVM_JBoosterInitVM(JNIEnv *env, jint server_port, jint connection_timeout, jint cleanup_timeout, jstring cache_path)) +#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); +#endif // INCLUDE_JBOOSTER +JVM_END + +JVM_ENTRY(void, JVM_JBoosterHandleConnection(JNIEnv *env, jint connection_fd)) +#if INCLUDE_JBOOSTER + ServerDataManager::get().listening_thread()->handle_connection(connection_fd); +#endif // INCLUDE_JBOOSTER +JVM_END + +JVM_ENTRY(void, JVM_JBoosterPrintStoredClientData(JNIEnv *env, jboolean print_all)) +#if INCLUDE_JBOOSTER + ServerDataManager::get().log_all_state(print_all); +#endif // INCLUDE_JBOOSTER +JVM_END + +JVM_ENTRY(void, JVM_JBoosterStartupNativeCallback(JNIEnv *env)) +#if INCLUDE_JBOOSTER + if (!UseJBooster) return; + ThreadToNativeFromVM ttn(THREAD->as_Java_thread()); + log_debug(jbooster, start)("Reached the startup signal point of this program."); + ClientDataManager::get().set_startup_end(); + ClientMessageHandler::trigger_cache_generation_tasks(ClientMessageHandler::TriggerTaskPhase::ON_STARTUP, THREAD); + log_debug(jbooster, start)("End of the startup callback."); +#endif // INCLUDE_JBOOSTER +JVM_END \ No newline at end of file diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 60f639795..a1a737915 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -62,6 +62,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_JBOOSTER +#include "jbooster/jBoosterManager.hpp" +#endif // INCLUDE_JBOOSTER #define DEFAULT_JAVA_LAUNCHER "generic" @@ -3998,6 +4001,21 @@ jint Arguments::apply_ergo() { GCConfig::arguments()->initialize(); +#if INCLUDE_JBOOSTER + // The jbooster server identifies the client based on the client VM flags. + // After identifying the client, the server sends the corresponding caches + // to the client (or no cache to send). + // Depending on whether the caches are present, different flags will be set + // by JBoosterManager, especially for Dynamic CDS. + // So the time for sending client VM flags to the server should be later + // than most VM flags are initialized, but earlier than the time for CDS + // initialization. + if (UseJBooster || AsJBooster) { + result = JBoosterManager::init_phase1(); + if (result != JNI_OK) return result; + } +#endif // INCLUDE_JBOOSTER + result = set_shared_spaces_flags_and_archive_paths(); if (result != JNI_OK) return result; @@ -4318,3 +4336,38 @@ bool Arguments::copy_expand_pid(const char* src, size_t srclen, *b = '\0'; return (p == src_end); // return false if not all of the source was copied } + +#if INCLUDE_JBOOSTER +jint Arguments::init_jbooster_startup_signal_properties(const char* klass_name, + const char* method_name, + const char* method_signature) { + bool added; + int jio_res; + const int buf_len = 4096; + char buffer[buf_len]; + + if (klass_name != nullptr) { + jio_res = jio_snprintf(buffer, buf_len, "jdk.jbooster.startup_klass_name=%s", klass_name); + if (jio_res < 0) return JNI_ENOMEM; + added = add_property(buffer, UnwriteableProperty, InternalProperty); + if (!added) return JNI_ENOMEM; + } + + if (method_name != nullptr) { + jio_res = jio_snprintf(buffer, buf_len, "jdk.jbooster.startup_method_name=%s", method_name); + if (jio_res < 0) return JNI_ENOMEM; + added = add_property(buffer, UnwriteableProperty, InternalProperty); + if (!added) return JNI_ENOMEM; + } + + if (method_signature != nullptr) { + jio_res = jio_snprintf(buffer, buf_len, "jdk.jbooster.startup_method_signature=%s", method_signature); + if (jio_res < 0) return JNI_ENOMEM; + added = add_property(buffer, UnwriteableProperty, InternalProperty); + if (!added) return JNI_ENOMEM; + } + + return JNI_OK; +} + +#endif // INCLUDE_JBOOSTER \ No newline at end of file diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 3876be4a9..7f52419a5 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -641,6 +641,12 @@ class Arguments : AllStatic { assert(Arguments::is_dumping_archive(), "dump time only"); } +#if INCLUDE_JBOOSTER + static jint init_jbooster_startup_signal_properties(const char* klass_name, + const char* method_name, + const char* method_signature); +#endif // INCLUDE_JBOOSTER + DEBUG_ONLY(static bool verify_special_jvm_flags(bool check_globals);) }; diff --git a/src/hotspot/share/runtime/flags/allFlags.hpp b/src/hotspot/share/runtime/flags/allFlags.hpp index 7d644e9a8..b0e7465cb 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_JBOOSTER +#include "jbooster/jbooster_globals.hpp" +#endif // INCLUDE_JBOOSTER // Put LP64/ARCH/JVMCI/COMPILER1/COMPILER2 at the top, // as they are processed by jvmFlag.cpp in that order. @@ -138,7 +141,17 @@ product_pd, \ notproduct, \ range, \ - constraint) + constraint) \ + \ + JBOOSTER_ONLY( \ + JBOOSTER_FLAGS( \ + develop, \ + develop_pd, \ + product, \ + product_pd, \ + notproduct, \ + range, \ + constraint)) #define ALL_CONSTRAINTS(f) \ COMPILER_CONSTRAINTS(f) \ diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 054482383..8eae5e5cc 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2008,6 +2008,11 @@ const intx ObjectAlignmentInBytes = 8; product(ccstr, ArchiveClassesAtExit, NULL, \ "The path and name of the dynamic archive file") \ \ + product(bool, SkipSharedClassPathCheck, false, DIAGNOSTIC, \ + "Skips SharedClassPath check in DynamicCDS, which allows" \ + "non-empty directories to exist in classpath when DynamicDS" \ + "is used") \ + \ product(ccstr, ExtraSharedClassListFile, NULL, \ "Extra classlist for building the CDS archive file") \ \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 5dc47c7c2..d7b1c5ed6 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -93,6 +93,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_JBOOSTER +#include "jbooster/client/clientMessageHandler.hpp" +#endif // INCLUDE_JBOOSTER GrowableArray* collected_profiled_methods; @@ -510,7 +513,7 @@ void before_exit(JavaThread* thread, bool halt) { os::terminate_signal_thread(); #if INCLUDE_CDS - if (DynamicDumpSharedSpaces) { + if (DynamicDumpSharedSpaces JBOOSTER_ONLY(&& !UseJBooster)) { ExceptionMark em(thread); DynamicArchive::dump(); if (thread->has_pending_exception()) { @@ -523,6 +526,14 @@ void before_exit(JavaThread* thread, bool halt) { } #endif +#if INCLUDE_JBOOSTER + if (UseJBooster) { + ThreadToNativeFromVM ttn(thread); + bool should_compile = JBoosterStartupSignal == nullptr; + ClientMessageHandler::trigger_cache_generation_tasks(ClientMessageHandler::TriggerTaskPhase::ON_SHUTDOWN, thread); + } +#endif // INCLUDE_JBOOSTER + print_statistics(); Universe::heap()->print_tracing_info(); diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index ece43e350..c057b1ef5 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -149,6 +149,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_JBOOSTER +#include "jbooster/jBoosterManager.hpp" +#endif // INCLUDE_JBOOSTER // Initialization after module runtime initialization void universe_post_module_init(); // must happen after call_initPhase2 @@ -3150,6 +3153,12 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { ShouldNotReachHere(); } +#if INCLUDE_JBOOSTER + if (UseJBooster || AsJBooster) { + JBoosterManager::init_phase2(CHECK_JNI_ERR); + } +#endif // INCLUDE_JBOOSTER + return JNI_OK; } diff --git a/src/hotspot/share/utilities/concurrentHashTable.hpp b/src/hotspot/share/utilities/concurrentHashTable.hpp index a94d3ed9a..d3ca8acb5 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.hpp @@ -40,6 +40,10 @@ class Mutex; template class ConcurrentHashTable : public CHeapObj { +#if INCLUDE_JBOOSTER + template friend class ConcurrentHashMap; +#endif // INCLUDE_JBOOSTER + typedef typename CONFIG::Value VALUE; private: // This is the internal node structure. diff --git a/src/hotspot/share/utilities/hashtable.cpp b/src/hotspot/share/utilities/hashtable.cpp index 5b99bba8b..ad0ab01f9 100644 --- a/src/hotspot/share/utilities/hashtable.cpp +++ b/src/hotspot/share/utilities/hashtable.cpp @@ -280,6 +280,9 @@ template class BasicHashtable; template class BasicHashtable; template class BasicHashtable; template class BasicHashtable; +#if INCLUDE_JBOOSTER +template class BasicHashtable; +#endif // INCLUDE_JBOOSTER template void BasicHashtable::verify_table(char const*); template void BasicHashtable::verify_table(char const*); diff --git a/src/hotspot/share/utilities/hashtable.hpp b/src/hotspot/share/utilities/hashtable.hpp index 87bd65e05..8bbb20e68 100644 --- a/src/hotspot/share/utilities/hashtable.hpp +++ b/src/hotspot/share/utilities/hashtable.hpp @@ -228,6 +228,7 @@ template< bool (*EQUALS)(K const&, K const&) = primitive_equals > class KVHashtable : public BasicHashtable { +protected: class KVHashtableEntry : public BasicHashtableEntry { public: K _key; @@ -237,7 +238,6 @@ class KVHashtable : public BasicHashtable { } }; -protected: KVHashtableEntry* bucket(int i) const { return (KVHashtableEntry*)BasicHashtable::bucket(i); } diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp index 33ecfe089..fde84e048 100644 --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.hpp @@ -119,6 +119,16 @@ #define NOT_CDS_RETURN_(code) { return code; } #endif // INCLUDE_CDS +#ifndef INCLUDE_JBOOSTER +#define INCLUDE_JBOOSTER 1 +#endif + +#if INCLUDE_JBOOSTER +#define JBOOSTER_ONLY(x) x +#else +#define JBOOSTER_ONLY(x) +#endif // INCLUDE_JBOOSTER + #ifndef INCLUDE_MANAGEMENT #define INCLUDE_MANAGEMENT 1 #endif // INCLUDE_MANAGEMENT diff --git a/src/hotspot/share/utilities/stringUtils.cpp b/src/hotspot/share/utilities/stringUtils.cpp index 21fb7a6e8..04883b4f1 100644 --- a/src/hotspot/share/utilities/stringUtils.cpp +++ b/src/hotspot/share/utilities/stringUtils.cpp @@ -25,6 +25,11 @@ #include "precompiled.hpp" #include "utilities/debug.hpp" #include "utilities/stringUtils.hpp" +#if INCLUDE_JBOOSTER +#include "classfile/javaClasses.hpp" +#include "memory/allocation.hpp" +#include "oops/symbol.hpp" +#endif // INCLUDE_JBOOSTER int StringUtils::replace_no_expand(char* string, const char* from, const char* to) { int replace_count = 0; @@ -65,3 +70,61 @@ double StringUtils::similarity(const char* str1, size_t len1, const char* str2, return 2.0 * (double) hit / (double) total; } + +#if INCLUDE_JBOOSTER + +uint32_t StringUtils::hash_code(const char* str) { + if (str == nullptr) return 0u; + return hash_code(str, strlen(str) + 1); +} + +uint32_t StringUtils::hash_code(const char* str, int len) { + assert(str != nullptr && str[len - 1] == '\0', "sanity"); + return java_lang_String::hash_code((const jbyte*) str, len - 1); +} + +uint32_t StringUtils::hash_code(Symbol* sym) { + if (sym == nullptr) return 0u; + return java_lang_String::hash_code((const jbyte*) sym->base(), sym->utf8_length()); +} + +int StringUtils::compare(const char* str1, const char* str2) { + if (str1 == nullptr) { + return str2 == nullptr ? 0 : -1; + } + if (str2 == nullptr) { + return str1 == nullptr ? 0 : +1; + } + return strcmp(str1, str2); +} + +char* StringUtils::copy_to_resource(const char* str) { + if (str == nullptr) return nullptr; + return copy_to_resource(str, strlen(str) + 1); +} + +char* StringUtils::copy_to_resource(const char* str, int len) { + assert(str != nullptr && str[len - 1] == '\0', "sanity"); + return (char*) memcpy(NEW_RESOURCE_ARRAY(char, len), str, len); +} + +char* StringUtils::copy_to_heap(const char* str, MEMFLAGS mt) { + if (str == nullptr) return nullptr; + return copy_to_heap(str, strlen(str) + 1, mt); +} + +char* StringUtils::copy_to_heap(const char* str, int len, MEMFLAGS mt) { + assert(str != nullptr && str[len - 1] == '\0', "sanity"); + return (char*) memcpy(NEW_C_HEAP_ARRAY(char, len, mt), str, len); +} + +void StringUtils::free_from_heap(const char* str) { + if (str == nullptr) return; + FREE_C_HEAP_ARRAY(char, str); +} + +const char* StringUtils::str(Symbol* sym) { + return sym == nullptr ? "" : sym->as_C_string(); +} + +#endif // INCLUDE_JBOOSTER diff --git a/src/hotspot/share/utilities/stringUtils.hpp b/src/hotspot/share/utilities/stringUtils.hpp index 372222d7c..abc7ef361 100644 --- a/src/hotspot/share/utilities/stringUtils.hpp +++ b/src/hotspot/share/utilities/stringUtils.hpp @@ -27,6 +27,10 @@ #include "memory/allocation.hpp" +#if INCLUDE_JBOOSTER +class Symbol; +#endif // INCLUDE_JBOOSTER + class StringUtils : AllStatic { public: // Replace the substring with another string . must be @@ -40,6 +44,30 @@ public: // Compute string similarity based on Dice's coefficient static double similarity(const char* str1, size_t len1, const char* str2, size_t len2); + +#if INCLUDE_JBOOSTER + // A null-pointer-supported version of strcmp. + static int compare(const char* str1, const char* str2); + + // Return 0 if str is null. + static uint32_t hash_code(const char* str); + static uint32_t hash_code(const char* str, int len); + static uint32_t hash_code(Symbol* sym); + + // Do nothing and return null if the str is null. + static char* copy_to_resource(const char* str); + static char* copy_to_resource(const char* str, int len); + + // Do nothing and return null if the str is null. + static char* copy_to_heap(const char* str, MEMFLAGS mt); + static char* copy_to_heap(const char* str, int len, MEMFLAGS mt); + + // Do nothing if the str is null. + static void free_from_heap(const char* str); + + // Return "" if sym is null. + static const char* str(Symbol* sym); +#endif // INCLUDE_JBOOSTER }; #endif // SHARE_UTILITIES_STRINGUTILS_HPP diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 03b7afff8..560a0c05f 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -177,6 +177,7 @@ module java.base { java.logging; exports jdk.internal.org.objectweb.asm to jdk.jartool, + jdk.jbooster, jdk.jfr, jdk.jlink; exports jdk.internal.org.objectweb.asm.tree to diff --git a/src/java.base/share/lib/security/default.policy b/src/java.base/share/lib/security/default.policy index af5df34b5..1c58b7797 100644 --- a/src/java.base/share/lib/security/default.policy +++ b/src/java.base/share/lib/security/default.policy @@ -185,6 +185,15 @@ grant codeBase "jrt:/jdk.internal.vm.compiler.management" { permission java.lang.RuntimePermission "accessClassInPackage.org.graalvm.compiler.serviceprovider"; }; +grant codeBase "jrt:/jdk.jbooster" { + permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.org.objectweb.asm"; + permission java.lang.RuntimePermission "accessClassInPackage.jdk.tools.jaotc"; + permission java.lang.RuntimePermission "accessClassInPackage.jdk.tools.jaotc.collect"; + permission java.lang.RuntimePermission "accessClassInPackage.jdk.tools.jaotc.collect.module"; + permission java.lang.RuntimePermission "accessClassInPackage.jdk.vm.ci.hotspot"; + permission java.lang.RuntimePermission "accessClassInPackage.jdk.vm.ci.jbooster"; +}; + grant codeBase "jrt:/jdk.jsobject" { permission java.security.AllPermission; }; 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 new file mode 100644 index 000000000..aa18682e9 --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/JBoosterCompilationContext.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 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. + */ + +package jdk.vm.ci.jbooster; + +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Compilation context interface for JBooster. + * Defined in jdk.vm.ci for all modules to access. + */ +public interface JBoosterCompilationContext { + /** + * The context is thread-local. + * Please access it through get() & set(). + */ + ThreadLocal CTX = new ThreadLocal<>(); + + /** + * Get thread-local context. + * + * @return JBoosterCompilationContext + */ + static JBoosterCompilationContext get() { + return CTX.get(); + } + + /** + * Set thread-local context. + * + * @param ctx JBoosterCompilationContext + */ + static void set(JBoosterCompilationContext ctx) { + CTX.set(ctx); + } + + /** + * Get the ID of the client session. + */ + int getSessionId(); + + /** + * Get the file path to store the compiled AOT lib. + */ + String getFilePath(); + + /** + * Get the classes to compile. + */ + Set> getClassesToCompile(); + + /** + * Get the methods to compile. + * The methods should be in getClassesToCompile(). + * + * @return names & signatures of the methods + */ + Set getMethodsToCompile(); + + /** + * Get the methods that should never be compiled. + * These methods have special invoke handles in them so they are not + * compatible with PGO. + * + * @return names & signatures of the methods + */ + Set getMethodsNotToCompile(); + + /** + * return true if this context use PGO + */ + boolean usePGO(); + + /** + * Get methodCount of CompiledMethodInfo. + * (To support multi-task concurrent compilation of AOT) + */ + AtomicInteger getCompiledMethodInfoMethodsCount(); + + /** + * Get dynoStore of AOTCompiledClass. + * (To support multi-task concurrent compilation of AOT) + * + * @return a AOTDynamicTypeStore + * (AOTDynamicTypeStore is not visible here) + */ + Object getAOTCompiledClassAOTDynamicTypeStore(); + + /** + * Set dynoStore of AOTCompiledClass. + * (To support multi-task concurrent compilation of AOT) + */ + void setAotCompiledClassAOTDynamicTypeStore(Object dynoStore); + + /** + * Get classesCount of AOTCompiledClass. + * (To support multi-task concurrent compilation of AOT) + */ + AtomicInteger getAOTCompiledClassClassesCount(); + + /** + * Get klassData of AOTCompiledClass. + * (To support multi-task concurrent compilation of AOT) + * + * @return a HashMap + * (AOTKlassData is not visible here) + */ + HashMap getAOTCompiledClassKlassData(); + + /** + * Get sectNameTab of ElfSection. + * (To support multi-task concurrent compilation of AOT) + */ + StringBuilder getElfSectionSectNameTab(); + + /** + * Get shStrTabNrOfBytes of ElfSection. + * (To support multi-task concurrent compilation of AOT) + */ + AtomicInteger getElfSectionShStrTabNrOfBytes(); + + /** + * Get the number of remaining tasks that have not been compiled. + * (To support concurrent compilation for different tasks) + */ + CountDownLatch getAOTCompilerRemainingTaskCount(); + + /** + * Set the number of tasks that are successfully enqueued. + * (To support concurrent compilation for different tasks) + */ + void setAOTCompilerTotalTaskCount(int taskCnt); + + /** + * Get successfulMethodCount of AOTCompiler#CompileQueue. + * (To support concurrent compilation for different tasks) + */ + AtomicInteger getCompileQueueSuccessfulMethodCount(); + + /** + * Get failedMethodCount of AOTCompiler#CompileQueue. + * (To support concurrent compilation for different tasks) + */ + AtomicInteger getCompileQueueFailedMethodCount(); + + /** + * Should the method be excluded for inline. + * (To support PGO) + * + * @param methodName method name + * @return is excluded for inline + */ + boolean isInlineExcluded(String methodName); + + /** + * Get the real metaspace method data of client session. + * (To support PGO) + * + * @param metaspaceMethod the metaspace method + * @return the address of method data + */ + long getMetaspaceMethodData(long metaspaceMethod); +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/package-info.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/package-info.java new file mode 100644 index 000000000..e1e0bad5f --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/jbooster/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + */ + +/** + * The JBooster of the JVMCI API + */ +package jdk.vm.ci.jbooster; \ No newline at end of file diff --git a/src/jdk.internal.vm.ci/share/classes/module-info.java b/src/jdk.internal.vm.ci/share/classes/module-info.java index ed1976957..631cc8af9 100644 --- a/src/jdk.internal.vm.ci/share/classes/module-info.java +++ b/src/jdk.internal.vm.ci/share/classes/module-info.java @@ -33,6 +33,7 @@ module jdk.internal.vm.ci { exports jdk.vm.ci.meta to jdk.internal.vm.compiler; exports jdk.vm.ci.code to jdk.internal.vm.compiler; exports jdk.vm.ci.hotspot to jdk.internal.vm.compiler; + exports jdk.vm.ci.jbooster to jdk.jbooster, jdk.internal.vm.compiler; uses jdk.vm.ci.services.JVMCIServiceLocator; uses jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory; diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/Commands.java b/src/jdk.jbooster/share/classes/jdk/jbooster/Commands.java new file mode 100644 index 000000000..1333a5907 --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/Commands.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 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. + */ + +package jdk.jbooster; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Scanner; + +import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.WARNING; +import static jdk.jbooster.JBooster.LOGGER; + +/** + * The interactive commands of the JBooster server. + */ +public final class Commands { + private static final Command[] COMMANDS = {new Command(of("h", "help"), + "Print this help message of JBooster interactive commands.") { + @Override + void process(Commands commands, String[] args) { + printHelp(); + } + }, new Command(of("q", "quit", "exit"), + "Exit the JBooster server.") { + @Override + void process(Commands commands, String[] args) { + commands.exit = true; + } + }, new Command(of("c", "conn", "connection"), + "Print network connections with the clients.") { + @Override + void process(Commands commands, String[] args) { + int workingThreads = JBooster.getConnectionPool().getExecutor().getActiveCount(); + LOGGER.log(INFO, "Working threads for connections: {0}", workingThreads); + } + }, new Command(of("d", "data"), + "Print managed client data and cache state (add arg \"all\" for more details).") { + @Override + void process(Commands commands, String[] args) { + boolean printAll = (args.length == 1 && "all".equals(args[0])); + JBooster.printStoredClientData(printAll); + } + }}; + + private boolean exit = false; + + /** + * Open the scanner and receive the interactive commands. + */ + public boolean interact() { + try (Scanner scanner = new Scanner(System.in)) { + do { + String cmdLine = scanner.nextLine(); + parseLine(cmdLine); + } while (!exit); + } catch (NoSuchElementException e) { + LOGGER.log(WARNING, "Looks like jbooster is running in the background so the interaction " + + "is unsupported here. Add \"-b\" or \"--background\" to the command line to " + + "suppress this warning."); + return false; + } + return true; + } + + private void parseLine(String cmdLine) { + if (cmdLine.isEmpty()) { + return; + } + String[] cmdAndArgs = Arrays.stream(cmdLine.split(" ")).filter(s -> !s.isEmpty()).toArray(String[]::new); + if (cmdAndArgs.length == 0) { + return; + } + String cmd = cmdAndArgs[0]; + String[] args = Arrays.stream(cmdAndArgs).skip(1).toArray(String[]::new); + + Optional command = findCommand(cmd); + if (command.isPresent()) { + command.get().process(this, args); + } else { + LOGGER.log(WARNING, "Unknown command: \"{0}\"", cmd); + printHelp(); + } + } + + private static Optional findCommand(String cmd) { + for (Command command : COMMANDS) { + if (command.matches(cmd)) { + return Optional.of(command); + } + } + return Optional.empty(); + } + + private static void printHelp() { + int maxLenOfAliases = 0; + for (Command command : COMMANDS) { + int lenAliases = Arrays.stream(command.aliases).mapToInt(String::length).sum() + + (2 * (command.aliases.length - 1)); + maxLenOfAliases = Math.max(maxLenOfAliases, lenAliases); + } + + final int margin = 4; + final String sep = System.lineSeparator(); + StringBuilder sbAll = new StringBuilder("Help of JBooster commands:" + sep); + for (Command command : COMMANDS) { + sbAll.append(" "); + StringBuilder sb = new StringBuilder(); + for (String alias : command.aliases) { + sb.append(alias).append(", "); + } + sb.setLength(sb.length() - 2); + sbAll.append(sb); + sbAll.append(" ".repeat(maxLenOfAliases + margin - sb.length())); + sbAll.append(command.comment); + sbAll.append(sep); + } + LOGGER.log(INFO, sbAll); + } + + private static String[] of(String... aliases) { + return aliases; + } + + abstract static class Command { + final String[] aliases; + final String comment; + + protected Command(String[] aliases, String comment) { + this.aliases = aliases; + this.comment = comment; + } + + boolean matches(String cmd) { + for (String alias : aliases) { + if (alias.equals(cmd)) { + return true; + } + } + return false; + } + + abstract void process(Commands commands, String[] args); + } +} diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/ConnectionPool.java b/src/jdk.jbooster/share/classes/jdk/jbooster/ConnectionPool.java new file mode 100644 index 000000000..3d703de1c --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/ConnectionPool.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package jdk.jbooster; + +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static java.lang.System.Logger.Level.INFO; +import static jdk.jbooster.JBooster.LOGGER; + +/** + * The network connection thread pool of the JBooster server. + */ +public final class ConnectionPool { + private static final int CORE_POOL_SIZE = Math.min(25, Math.max(8, 2 * Runtime.getRuntime().availableProcessors())); + private static final int MAX_POOL_SIZE = 200; + private static final long KEEP_ALIVE_SECONDS = 60L; + + private final ThreadPoolExecutor executor; + + ConnectionPool() { + executor = new ConnectionPoolExecutor(); + } + + public ThreadPoolExecutor getExecutor() { + return executor; + } + + public boolean execute(Runnable command) { + try { + executor.execute(command); + } catch (RejectedExecutionException e) { + return false; + } + return true; + } + + private static class ConnectionPoolExecutor extends ThreadPoolExecutor { + + public ConnectionPoolExecutor() { + super(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, + new SynchronousQueue<>(), new ThreadPoolExecutor.AbortPolicy()); + } + + @Override + protected void terminated() { + LOGGER.log(INFO, "JBooster connection pool terminated."); + } + } +} diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java new file mode 100644 index 000000000..49cdbb28a --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBooster.java @@ -0,0 +1,231 @@ +/* + * 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. + */ + +package jdk.jbooster; + +import java.lang.System.Logger; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.Option; +import sun.misc.Signal; +import sun.misc.SignalHandler; + +import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.WARNING; + +/** + * The entry of the JBooster server. + */ +public final class JBooster { + static { + System.setProperty("java.util.logging.SimpleFormatter.format", + "[%1$tF %1$tT][%4$-7s][%2$s] %5$s%n"); + System.loadLibrary("jbooster"); + } + + public static final Logger LOGGER = System.getLogger("jbooster"); + + private static final Options options = new Options(); + + private static final Commands commands = new Commands(); + + private static ConnectionPool connectionPool; + + public static void main(String[] args) { + try { + options.parseArgs(args); + } catch (IllegalArgumentException e) { + if (LOGGER.isLoggable(ERROR)) { + LOGGER.log(ERROR, "Failed to start because of the bad CMD args:"); + LOGGER.log(ERROR, "{0}", e.getMessage()); + } else { + System.err.println("Failed to start because of the bad CMD args:"); + System.err.println(e.getMessage()); + } + Options.printHelp(); + return; + } + + init(); + LOGGER.log(INFO, "The JBooster server is ready!"); + + try { + if (!options.isInteractive() || !commands.interact()) { + loop(); + } + } finally { + terminateAndJoin(); + } + } + + static Options getOptions() { + return options; + } + + static Commands getCommands() { + return commands; + } + + static ConnectionPool getConnectionPool() { + return connectionPool; + } + + private static void init() { + BackgroundScannerSignalHandler.register(); + connectionPool = new ConnectionPool(); + Main.initForJBooster(); + initInVM(options.getServerPort(), options.getConnectionTimeout(), + options.getCleanupTimeout(), options.getCachePath()); + } + + private static void loop() { + while (true) { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + return; + } + } + } + + private static void terminateAndJoin() { + final long maxTimeForWaiting = 8000L; + final long maxTimeForInterrupting = 2000L; + + LOGGER.log(INFO, "Shutting down the connection pools (it may take some time)...", + maxTimeForWaiting); + ThreadPoolExecutor pool1 = getConnectionPool().getExecutor(); + ThreadPoolExecutor pool2 = Main.getJBoosterGlobalCompileQueue(); + List pools = List.of(pool1, pool2); + try { + LOGGER.log(INFO, "Waiting (up to {0} ms) for the existing pool tasks to finish.", maxTimeForWaiting); + for (ThreadPoolExecutor pool : pools) { + pool.shutdown(); + } + if (!waitThreadPoolsToTerminate(pools, maxTimeForWaiting)) { + LOGGER.log(INFO, "Waiting (up to {0} ms) for interrupting pool tasks.", maxTimeForInterrupting); + for (ThreadPoolExecutor pool : pools) { + pool.shutdownNow(); + } + if (!waitThreadPoolsToTerminate(pools, maxTimeForInterrupting)) { + LOGGER.log(WARNING, "Failed to stop the pool properly. Force exit. Bye~"); + System.exit(1); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + LOGGER.log(INFO, "Bye~"); + } + + /** + * Wait for all thread pools to stop working. + * The total wait time does not exceed waitMillis. + */ + private static boolean waitThreadPoolsToTerminate(List pools, long waitMillis) + throws InterruptedException { + boolean allTerminated = true; + long timeBegin = System.currentTimeMillis(); + for (ThreadPoolExecutor pool : pools) { + if (pool.isTerminated()) { + continue; + } + long timeToWait = Math.max(0, waitMillis - (System.currentTimeMillis() - timeBegin)); + if (!pool.awaitTermination(timeToWait, TimeUnit.MILLISECONDS)) { + allTerminated = false; + LOGGER.log(INFO, "The pool {0} is still running after {1} ms.", + pool.getClass().getSimpleName(), waitMillis); + } + } + return allTerminated; + } + + /** + * This method is invoked only in C++. + */ + private static boolean receiveConnection(int connectionFd) { + return connectionPool.execute(() -> handleConnection(connectionFd)); + } + + /** + * This method is invoked only in C++. + */ + private static boolean compileClasses(int sessionId, String filePath, Set> classes) { + return compileMethods(sessionId, filePath, classes, null, null); + } + + /** + * This method is invoked only in C++. + */ + private static boolean compileMethods(int sessionId, String filePath, Set> classes, + Set methodsToCompile, Set methodsNotToCompile) { + 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())), + (methodsNotToCompile == null ? "none" : String.valueOf(methodsNotToCompile.size())), + sessionId); + try { + // [JBOOSTER TODO] jaotc logic + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private static native void initInVM(int serverPort, int connectionTimeout, int cleanupTimeout, String cachePath); + + private static native void handleConnection(int connectionFd); + + static native void printStoredClientData(boolean printAll); + + private static final class BackgroundScannerSignalHandler implements SignalHandler { + public static void register() { + // The returned value (i.e. the old, default handler) is ignored because + // the default handler will make the program "stop" instead of "exit". + // So do not use it. + Signal.handle(new Signal("TTIN"), new BackgroundScannerSignalHandler()); + } + + private boolean handled; + + private BackgroundScannerSignalHandler() { + handled = false; + } + + @Override + public void handle(Signal sig) { + if (!handled) { + handled = true; + LOGGER.log(ERROR, "A SIGTTIN is detected! Looks like this program is running in background " + + "so Scanner is not supported here. Add \"-b\" or \"--background\" to the command " + + "line of jbooster to disable interactive commands."); + System.exit(1); + } + } + } +} diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterClassLoader.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterClassLoader.java new file mode 100644 index 000000000..efbf6f673 --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterClassLoader.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package jdk.jbooster; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static java.lang.System.Logger.Level.INFO; +import static jdk.jbooster.JBooster.LOGGER; + +public class JBoosterClassLoader extends ClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * To keep the classes from being GC'ed. + */ + public static final Map> loaders = new ConcurrentHashMap<>(); + + /** + * This method is only invoked in Hotspot C++ side. + */ + private static JBoosterClassLoader create(int programId, + String loaderClassName, String loaderName, ClassLoader parent) { + String name = loaderClassName + ":" + loaderName; + JBoosterClassLoader newLoader = new JBoosterClassLoader(name, parent); + Collection que = loaders.computeIfAbsent(programId, id -> new ConcurrentLinkedQueue<>()); + que.add(newLoader); + return newLoader; + } + + /** + * This method is only invoked in Hotspot C++ side. + */ + private static void destroy(int programId) { + Collection que = loaders.remove(programId); + LOGGER.log(INFO, "Class loaders and classes that belong to the program will be GCed later: " + + "program_id={0}, loader_cnt={1}.", programId, (que == null ? -1 : que.size())); + } + + private JBoosterClassLoader(String name, ClassLoader parent) { + super(name, parent); + } +} diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java new file mode 100644 index 000000000..65f28e141 --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/JBoosterCompilationContextImpl.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 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. + */ + +package jdk.jbooster; + +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.vm.ci.jbooster.JBoosterCompilationContext; + +/** + * The only implement of JBoosterCompilationContext. + */ +public class JBoosterCompilationContextImpl implements JBoosterCompilationContext { + // These values are related to JBooster compilation. + private final int sessionId; + private final String filePath; + private final Set> classesToCompile; + private final Set methodsToCompile; + private final Set methodsNotToCompile; + private final boolean usePGO; + + // These values are used to replace the static values in AOT classes. + private final AtomicInteger compiledMethodInfoMethodsCount = new AtomicInteger(0); + private Object aotCompiledClassAOTDynamicTypeStore = null; + private final AtomicInteger aotCompiledClassClassesCount = new AtomicInteger(0); + private final HashMap aotCompiledClassKlassData = new HashMap<>(); + private final StringBuilder elfSectionSectNameTab = new StringBuilder(); + private final AtomicInteger elfSectionShStrTabNrOfBytes = new AtomicInteger(0); + private final AtomicInteger compileQueueSuccessfulMethodCount = new AtomicInteger(0); + private final AtomicInteger compileQueueFailedMethodCount = new AtomicInteger(0); + + private CountDownLatch aotCompilerRemainingTaskCount = null; + + public JBoosterCompilationContextImpl( + int sessionId, + String filePath, + Set> classesToCompile, + Set methodsToCompile, + Set methodsNotCompile, + boolean usePGO) { + this.sessionId = sessionId; + this.filePath = filePath; + this.classesToCompile = classesToCompile; + this.methodsToCompile = methodsToCompile; + this.methodsNotToCompile = methodsNotCompile; + this.usePGO = usePGO; + } + + @Override + public int getSessionId() { + return sessionId; + } + + @Override + public String getFilePath() { + return filePath; + } + + @Override + public Set> getClassesToCompile() { + return classesToCompile; + } + + @Override + public Set getMethodsToCompile() { + return methodsToCompile; + } + + @Override + public Set getMethodsNotToCompile() { + return methodsNotToCompile; + } + + @Override + public boolean usePGO() { + return usePGO; + } + + @Override + public AtomicInteger getCompiledMethodInfoMethodsCount() { + return compiledMethodInfoMethodsCount; + } + + @Override + public Object getAOTCompiledClassAOTDynamicTypeStore() { + return aotCompiledClassAOTDynamicTypeStore; + } + + @Override + public void setAotCompiledClassAOTDynamicTypeStore(Object dynoStore) { + this.aotCompiledClassAOTDynamicTypeStore = dynoStore; + } + + @Override + public AtomicInteger getAOTCompiledClassClassesCount() { + return aotCompiledClassClassesCount; + } + + @Override + public HashMap getAOTCompiledClassKlassData() { + return aotCompiledClassKlassData; + } + + @Override + public StringBuilder getElfSectionSectNameTab() { + return elfSectionSectNameTab; + } + + @Override + public AtomicInteger getElfSectionShStrTabNrOfBytes() { + return elfSectionShStrTabNrOfBytes; + } + + @Override + public CountDownLatch getAOTCompilerRemainingTaskCount() { + return aotCompilerRemainingTaskCount; + } + + @Override + public void setAOTCompilerTotalTaskCount(int taskCnt) { + aotCompilerRemainingTaskCount = new CountDownLatch(taskCnt); + } + + @Override + public AtomicInteger getCompileQueueSuccessfulMethodCount() { + return compileQueueSuccessfulMethodCount; + } + + @Override + public AtomicInteger getCompileQueueFailedMethodCount() { + return compileQueueFailedMethodCount; + } + + @Override + public boolean isInlineExcluded(String methodName) { + return methodsNotToCompile.contains(methodName); + } + + @Override + public long getMetaspaceMethodData(long metaspaceMethod) { + return getMetaspaceMethodData(sessionId, metaspaceMethod); + } + + private static native long getMetaspaceMethodData(int sessionId, long metaspaceMethod); +} diff --git a/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java b/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java new file mode 100644 index 000000000..869e5ca97 --- /dev/null +++ b/src/jdk.jbooster/share/classes/jdk/jbooster/Options.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 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. + */ + +package jdk.jbooster; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Properties; + +import static java.lang.System.Logger.Level.INFO; +import static jdk.jbooster.JBooster.LOGGER; + +/** + * Options of the JBooster server. + */ +public final class Options { + private static final int UNSET_PORT = 0; + private static final int CONNECTION_TIMEOUT = 32 * 1000; + private static final int CLEANUP_TIMEOUT = 2 * 24 * 60 * 60 * 1000; + + private static final Option[] OPTIONS = {new Option(of("-h", "-?", "-help", "--help"), null, + "Print this help message of the JBooster server.") { + @Override + protected void process(Options options, String arg) { + printHelp(); + System.exit(0); + } + }, new Option(of("-p", "--server-port"), "port-num", + "The listening port of JBooster server (1024~65535). No default value.") { + @Override + protected void process(Options options, String arg) { + int port = -1; + try { + port = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Failed to convert the arg \"" + arg + "\" to a int!"); + } + if (port < 1024 || port > 65535) { + throw new IllegalArgumentException("Port should be in 1024~65535!"); + } + options.serverPort = port; + } + }, new Option(of("-t", "--connection-timeout"), "timeout-ms", + "The connection timeout of JBooster server. Default: " + CONNECTION_TIMEOUT + " ms.") { + @Override + protected void process(Options options, String arg) { + int timeout = Integer.parseInt(arg); + if (timeout <= 0) { + throw new IllegalArgumentException("Timeout should be greater than 0!"); + } + options.connectionTimeout = timeout; + } + }, new Option(of("--unused-cleanup-timeout"), "timeout-ms", + "The cleanup timeout for unused shared data. Default: " + CLEANUP_TIMEOUT + " ms (2 days).") { + @Override + protected void process(Options options, String arg) { + int timeout = Integer.parseInt(arg); + if (timeout <= 0) { + throw new IllegalArgumentException("Timeout should be greater than 0!"); + } + options.cleanupTimeout = timeout; + } + }, new Option(of("--cache-path"), "dir-path", + "The directory path for JBooster caches. Default: \"$HOME/.jbooster/server\".") { + @Override + protected void process(Options options, String arg) { + // verification is on c++ side + options.cachePath = arg; + } + }, new Option(of("-b", "--background"), null, + "Disable the scanner for inputting commands (use it if running in the background).") { + @Override + protected void process(Options options, String arg) { + options.interactive = false; + } + }}; + + private int serverPort = UNSET_PORT; + private int connectionTimeout = CONNECTION_TIMEOUT; + private int cleanupTimeout = CLEANUP_TIMEOUT; + private String cachePath = null; // set on C++ side + private boolean interactive = true; + + public int getServerPort() { + return serverPort; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public int getCleanupTimeout() { + return cleanupTimeout; + } + + public String getCachePath() { + return cachePath; + } + + public boolean isInteractive() { + return interactive; + } + + /** + * Parse the args of main(). + * + * @param args args of main() + */ + public void parseArgs(String[] args) { + for (int i = 0; i < args.length; ++i) { + String[] tmp = args[i].split("=", 2); + String op = tmp[0]; + String arg = tmp.length == 2 ? tmp[1] : null; + Optional