diff --git a/make/autoconf/hotspot.m4 b/make/autoconf/hotspot.m4 index b1ac442a325..9f908d1f3b6 100644 --- a/make/autoconf/hotspot.m4 +++ b/make/autoconf/hotspot.m4 @@ -26,7 +26,7 @@ # All valid JVM features, regardless of platform VALID_JVM_FEATURES="compiler1 compiler2 zero minimal dtrace jvmti jvmci \ graal vm-structs jni-check services management cmsgc epsilongc g1gc parallelgc serialgc shenandoahgc zgc nmt cds \ - static-build link-time-opt aot jfr" + static-build link-time-opt aot jfr aiext" # Deprecated JVM features (these are ignored, but with a warning) DEPRECATED_JVM_FEATURES="trace" @@ -357,6 +357,10 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_JVM_FEATURES], AC_MSG_ERROR([Specified JVM feature 'cmsgc' requires feature 'serialgc']) fi + if HOTSPOT_CHECK_JVM_FEATURE(aiext) && ! HOTSPOT_CHECK_JVM_FEATURE(compiler2); then + AC_MSG_ERROR([Specified JVM feature 'aiext' requires feature 'compiler2']) + fi + # Enable JFR by default, except for Zero, linux-sparcv9 and on minimal. if ! HOTSPOT_CHECK_JVM_VARIANT(zero); then if test "x$OPENJDK_TARGET_OS" != xaix; then @@ -518,6 +522,26 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_JVM_FEATURES], AC_SUBST(ENABLE_AOT) + AC_MSG_CHECKING([if aiext should be built]) + if HOTSPOT_IS_JVM_FEATURE_DISABLED(aiext); then + AC_MSG_RESULT([no, forced]) + JVM_FEATURES_aiext="" + else + # Only enable aiext on x86_64/aarch64 linux + if test "x$OPENJDK_TARGET_OS" = xlinux && \ + (test "x$OPENJDK_TARGET_CPU" = "xx86_64" || \ + test "x$OPENJDK_TARGET_CPU" = "xaarch64"); then + AC_MSG_RESULT([yes]) + JVM_FEATURES_aiext="aiext" + else + AC_MSG_RESULT([no]) + JVM_FEATURES_aiext="" + if HOTSPOT_CHECK_JVM_FEATURE(aiext); then + AC_MSG_ERROR([aiext is currently not supported on this platform]) + fi + fi + fi + if test "x$OPENJDK_TARGET_CPU" = xarm ; then # Default to use link time optimizations on minimal on arm JVM_FEATURES_link_time_opt="link-time-opt" @@ -545,7 +569,7 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_JVM_FEATURES], fi # Enable features depending on variant. - JVM_FEATURES_server="compiler1 compiler2 $NON_MINIMAL_FEATURES $JVM_FEATURES $JVM_FEATURES_jvmci $JVM_FEATURES_aot $JVM_FEATURES_graal" + JVM_FEATURES_server="compiler1 compiler2 $NON_MINIMAL_FEATURES $JVM_FEATURES $JVM_FEATURES_jvmci $JVM_FEATURES_aot $JVM_FEATURES_graal $JVM_FEATURES_aiext" JVM_FEATURES_client="compiler1 $NON_MINIMAL_FEATURES $JVM_FEATURES" JVM_FEATURES_core="$NON_MINIMAL_FEATURES $JVM_FEATURES" JVM_FEATURES_minimal="compiler1 minimal serialgc $JVM_FEATURES $JVM_FEATURES_link_time_opt" diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 7b86f83cc98..049d6352cb1 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -176,6 +176,13 @@ ifneq ($(call check-jvm-feature, jfr), true) JVM_EXCLUDE_PATTERNS += jfr endif +ifeq ($(call check-jvm-feature, aiext), true) + JVM_CFLAGS_FEATURES += -DINCLUDE_AIEXT=1 +else + JVM_CFLAGS_FEATURES += -DINCLUDE_AIEXT=0 + JVM_EXCLUDE_FILES += aiext.cpp aiExtension.cpp +endif + ################################################################################ ifeq ($(call check-jvm-feature, link-time-opt), true) diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index 1b10c56dd6d..9abf77f2f01 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -53,6 +53,9 @@ #include "ci/ciTypeFlow.hpp" #include "oops/method.hpp" #endif +#if INCLUDE_AIEXT +#include "opto/aiExtension.hpp" +#endif // ciMethod // @@ -100,6 +103,11 @@ ciMethod::ciMethod(const methodHandle& h_m, ciInstanceKlass* holder) : _bcea = NULL; #endif // COMPILER2 +#if INCLUDE_AIEXT + // Get entry of accelerated call. + _accel_call_entry = AIExt::find(h_m->klass_name(), h_m->name(), h_m->signature()); +#endif // INCLUDE_AIEXT + ciEnv *env = CURRENT_ENV; if (env->jvmti_can_hotswap_or_post_breakpoint()) { // 6328518 check hotswap conditions under the right lock. @@ -169,6 +177,9 @@ ciMethod::ciMethod(ciInstanceKlass* holder, _holder( holder), _intrinsic_id( vmIntrinsics::_none), _liveness( NULL), +#if INCLUDE_AIEXT + _accel_call_entry( NULL), +#endif // INCLUDE_AIEXT _can_be_statically_bound(false), _method_blocks( NULL), _method_data( NULL) diff --git a/src/hotspot/share/ci/ciMethod.hpp b/src/hotspot/share/ci/ciMethod.hpp index 4805204574e..3ae8aa6b70d 100644 --- a/src/hotspot/share/ci/ciMethod.hpp +++ b/src/hotspot/share/ci/ciMethod.hpp @@ -38,6 +38,9 @@ class MethodLiveness; class Arena; class BCEscapeAnalyzer; class InlineTree; +#if INCLUDE_AIEXT +class AccelCallEntry; +#endif // INCLUDE_AIEXT // Whether profiling found an oop to be always, never or sometimes // null @@ -82,6 +85,11 @@ class ciMethod : public ciMetadata { int _instructions_size; int _size_of_parameters; +#if INCLUDE_AIEXT + // Native acceleration. + const AccelCallEntry* _accel_call_entry; +#endif // INCLUDE_AIEXT + bool _uses_monitors; bool _balanced_monitors; bool _is_c1_compilable; @@ -188,6 +196,11 @@ class ciMethod : public ciMetadata { // Should the method be compiled with an age counter? bool profile_aging() const; +#if INCLUDE_AIEXT + // Native acceleration. + const AccelCallEntry* accel_call_entry() const { check_is_loaded(); return _accel_call_entry; } +#endif // INCLUDE_AIEXT + // Code size for inlining decisions. int code_size_for_inlining(); diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 4190406a282..c0ea176e6ae 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -34,6 +34,7 @@ #define LOG_TAG_LIST \ LOG_TAG(add) \ LOG_TAG(age) \ + AIEXT_ONLY(LOG_TAG(aiext)) \ LOG_TAG(alloc) \ LOG_TAG(aot) \ LOG_TAG(annotation) \ diff --git a/src/hotspot/share/opto/aiExtension.cpp b/src/hotspot/share/opto/aiExtension.cpp new file mode 100644 index 00000000000..5c8dab8fbe7 --- /dev/null +++ b/src/hotspot/share/opto/aiExtension.cpp @@ -0,0 +1,684 @@ +/* + * Copyright (c) 2025 Alibaba Group Holding Limited. 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 "opto/aiExtension.hpp" + +#include + +#include "classfile/symbolTable.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "opto/callnode.hpp" +#include "opto/graphKit.hpp" +#include "opto/multnode.hpp" +#include "opto/type.hpp" +#include "runtime/arguments.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.hpp" +#include "utilities/debug.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/ostream.hpp" + +// Declared in `aiext.cpp`. +extern const aiext_env_t GLOBAL_AIEXT_ENV; + +// Map for loaded AI-Extension unit. +// +// This map is initialized during startup, and will never be modified, so it +// does not need to be protected by locks. +static GrowableArray* loaded_units = NULL; + +// The acceleration table, which is sorted by the class name, method name +// and signature. +// +// This map should be locked properly when accessing it. +static GrowableArray* accel_table = NULL; + +// Mutex for acceleration table. +static Mutex* accel_table_lock = NULL; + +int AIExtUnit::compare(AIExtUnit* const& u1, AIExtUnit* const& u2) { + // Skip parameter list, and multiple versions for same feature is not allowed. + return strcmp(u1->_feature, u2->_feature); +} + +// Max length of feature name and version string. +#define MAX_UNIT_COMPONENT_LEN 64 +// Max length of parameter list. +#define MAX_UNIT_PARAM_LIST_LEN 1024 + +// Parses feature and version from the given string with the given length, +// and stores them into the given buffers. Returns `false` on failure. +static bool parse_feature_and_version(const char* str, size_t str_len, + char* feature, char* version) { + // Find '_' in the string. + const char* pos = strchr(str, '_'); + if (pos == NULL) { + return false; + } + + // Check and copy the feature name. + size_t feature_len = pos - str; + if (feature_len > MAX_UNIT_COMPONENT_LEN || feature_len >= str_len) { + return false; + } else { + strncpy(feature, str, feature_len); + feature[feature_len] = 0; + } + + // Check the length of the version string. + const size_t ver_len = str_len - feature_len - 1; + const char* ver_str = str + feature_len + 1; + if (ver_len > MAX_UNIT_COMPONENT_LEN) { + return false; + } + + // Check for format and copy the version string. + size_t i = 0; + bool has_dot = false; + for (; i < ver_len; i++) { + char c = ver_str[i]; + if (c == '.') { + if (!has_dot) { + has_dot = true; + } else { + // Found multiple dots. + return false; + } + } else if (c < '0' || c > '9') { + // Found non-digit character. + return false; + } + version[i] = c; + } + version[i] = 0; + + assert(strlen(feature) <= MAX_UNIT_COMPONENT_LEN && + strlen(version) <= MAX_UNIT_COMPONENT_LEN, + "sanity"); + return true; +} + +// Parses the parameter list from the given string, and stores it +// in the given buffer. Returns `false` on failure. +static bool parse_param_list(const char* str, char* param_list) { + // Check length and copy to the buffer. + if (strlen(str) > MAX_UNIT_PARAM_LIST_LEN) { + return false; + } + strcpy(param_list, str); + + // Check for parameter format. + const char* colon_pos; + const char* param_group = str; + while ((colon_pos = strchr(param_group, ':')) != NULL) { + const char* delim = strchr(param_group, '='); + if (delim == NULL || delim >= colon_pos) { + return false; // TODO: check duplicate '=' in one group + } + param_group = colon_pos + 1; + } + + // Check format for the last group. + const char* delim = strchr(param_group, '='); + if (delim == NULL) { + return false; // TODO: check duplicate '=' in one group + } + return true; +} + +AIExtUnit* AIExtUnit::parse_from_arg(const char* arg) { + // The argument option has pattern `feature_version?param1=val1:param2=val2`. + char feature[MAX_UNIT_COMPONENT_LEN + 1]; + char version[MAX_UNIT_COMPONENT_LEN + 1]; + char param_list_buf[MAX_UNIT_PARAM_LIST_LEN + 1]; + char* param_list = NULL; + + const char* pos; + if ((pos = strchr(arg, '?')) != NULL) { + param_list = param_list_buf; + if (!parse_feature_and_version(arg, pos - arg, feature, version)) { + return NULL; + } + } else { + if (!parse_feature_and_version(arg, strlen(arg), feature, version)) { + return NULL; + } + } + + if (param_list != NULL && !parse_param_list(pos + 1, param_list)) { + return NULL; + } + + static aiext_handle_t next_handle = 0; + return new AIExtUnit(feature, version, param_list, next_handle++); +} + +#undef MAX_UNIT_COMPONENT_LEN +#undef MAX_UNIT_PARAM_LIST_LEN + +// Utility helper to load an AI-Extension unit library from the given path. +// Returns handle of loaded library, `NULL` for failure. +static void* load_unit(const char* path, aiext_handle_t aiext_handle, + bool silent) { + // Try to load the library. + char ebuf[1024]; + void* handle = os::dll_load(path, ebuf, sizeof(ebuf)); + if (handle == NULL) { + if (!silent) { + tty->print_cr("Error: Could not load AI-Extension unit `%s`", path); + tty->print_cr("Error: %s", ebuf); + } + return NULL; + } + + // Get the entry point. + aiext_init_t init = (aiext_init_t)os::dll_lookup(handle, "aiext_init"); + if (init == NULL) { + if (!silent) { + tty->print_cr( + "Error: Could not find `aiext_init` in AI-Extension unit `%s`", path); + } + return NULL; + } + + // Initialize the AI-Extension unit. + aiext_result_t result = init(&GLOBAL_AIEXT_ENV, aiext_handle); + if (result != AIEXT_OK) { + if (!silent) { + tty->print_cr("Error: Could not initialize AI-Extension unit `%s`", path); + } + return NULL; + } + return handle; +} + +bool AIExtUnit::load() { +#if defined(AMD64) +#define CPU_ARCH "x86-64" +#elif defined(AARCH64) +#define CPU_ARCH "aarch64" +#else +#error "Support only x86_64 and AArch64" +#endif +#define CPU_ARCH_LEN (sizeof(CPU_ARCH) - 1) + + const char* java_home = Arguments::get_java_home(); + + // Check for `DRAGONWELL_AIEXT_HOME`, used for testing purpose. + size_t lib_path_len; + const char* aiext_home = ::getenv("DRAGONWELL_AIEXT_HOME"); + if (aiext_home != NULL) { + // Library path: `$DRAGONWELL_AIEXT_HOME/feature_ver_arch.so`. + lib_path_len = strlen(aiext_home) + strlen(_feature) + strlen(_version) + + CPU_ARCH_LEN + sizeof("/__.so") - 1; + } else { + // Library path: `$JAVA_HOME/lib/ai-ext/feature_ver_arch.so`. + lib_path_len = strlen(java_home) + strlen(_feature) + strlen(_version) + + CPU_ARCH_LEN + sizeof("/lib/ai-ext/__.so") - 1; + } + + // Construct library path. + char* buf = (char*)os::malloc(lib_path_len + 1, mtCompiler); + assert(buf != NULL, "OOM on native malloc"); + if (aiext_home != NULL) { + snprintf(buf, lib_path_len + 1, "%s/%s_%s_%s.so", aiext_home, _feature, + _version, CPU_ARCH); + } else { + snprintf(buf, lib_path_len + 1, "%s/lib/ai-ext/%s_%s_%s.so", java_home, + _feature, _version, CPU_ARCH); + } + + // Length of library path not including the trailing `_arch.so`. + size_t prefix_len = lib_path_len - CPU_ARCH_LEN - sizeof("_.so") + 1; + + // Try to load with `arch` in path. + void* lib_handle = load_unit(buf, _aiext_handle, true); + if (lib_handle == NULL) { + // Try without `arch`. + buf[prefix_len] = 0; + strcat(buf, ".so"); + lib_handle = load_unit(buf, _aiext_handle, false); + } + _handle = lib_handle; + + // Free the buffer. + os::free(buf); + return lib_handle != NULL; + +#undef CPU_ARCH +#undef CPU_ARCH_LEN +} + +int AccelCallEntry::compare(AccelCallEntry* const& e1, + AccelCallEntry* const& e2) { + return e1->_klass < e2->_klass ? -1 + : e1->_klass > e2->_klass ? 1 + : e1->_method < e2->_method ? -1 + : e1->_method > e2->_method ? 1 + : e1->_signature < e2->_signature ? -1 + : e1->_signature > e2->_signature ? 1 + : 0; +} + +void* AccelCallEntry::get_native_func() const { + if (_provider == NULL) { + return _func_or_data; + } else { + return _provider(&GLOBAL_AIEXT_ENV, _native_func_name, _func_or_data); + } +} + +// Adds the given native acceleration unit to table. +// Returns `false` if the unit is already added. +static bool add_unit(AIExtUnit* unit) { + assert(loaded_units != NULL, "must be initialized"); + bool found; + int index = + loaded_units->find_sorted(unit, found); + if (found) { + return false; + } + loaded_units->insert_before(index, unit); + return true; +} + +bool AIExt::init() { + // Quit if AI extension is not enabled. + if (!UseAIExtension) { + return true; + } + + // Create tables. + // We can not initialize locks now, because mutex is initialized in + // `os::init_2`, which is called after this. Just leave them null. + assert(loaded_units == NULL && accel_table == NULL, + "init should only be called once"); + loaded_units = + new (ResourceObj::C_HEAP, mtCompiler) GrowableArray(0, true); + accel_table = new (ResourceObj::C_HEAP, mtCompiler) + GrowableArray(0, true); + + // Parse AI-Extension units. + char* args = os::strdup(AIExtensionUnit); + size_t args_len = strlen(args); + char* arg = args; + while (arg < args + args_len) { + // Find the next unit. + char* p = arg; + while (*p != '\n' && *p != '\0') { + ++p; + } + *p = '\0'; + + // Parse the current unit. + AIExtUnit* unit = AIExtUnit::parse_from_arg(arg); + if (unit == NULL) { + tty->print_cr("Error: Invalid AI-Extension option: %s", arg); + os::free(args); + return false; + } + + // Add to the table. + if (!add_unit(unit)) { + warning("Ignoring duplicate AI-Extension unit `%s_%s`", unit->feature(), + unit->version()); + delete unit; + } + + arg = p + 1; + } + os::free(args); + + // Check if there are any units. + if (loaded_units->is_empty()) { + warning("AI-Extension unit is not provided in JVM arguments"); + return true; + } + + // Load AI-Extension units. + for (int i = 0; i < loaded_units->length(); ++i) { + AIExtUnit* e = loaded_units->at(i); + if (!e->load()) { + tty->print_cr("Error: Failed to load AI-Extension unit `%s_%s`", + e->_feature, e->_version); + return false; + }; + } + return true; +} + +bool AIExt::post_init() { + if (!UseAIExtension) { + return true; + } + + // Create locks. + assert(accel_table_lock == NULL, "post init should only be called once"); + accel_table_lock = new Mutex(Mutex::leaf /* Higher than tty is enough. */, + "Native acceleration table lock", true, + Monitor::_safepoint_check_never); + + // Invoke post initialization. + for (int i = 0; i < loaded_units->length(); ++i) { + AIExtUnit* u = loaded_units->at(i); + assert(u->_handle != NULL, "handle should be set"); + aiext_post_init_t post_init = + (aiext_post_init_t)os::dll_lookup(u->_handle, "aiext_post_init"); + if (post_init != NULL) { + aiext_result_t result = post_init(&GLOBAL_AIEXT_ENV, u->_aiext_handle); + if (result != AIEXT_OK) { + tty->print_cr( + "Error: Could not initialize AI-Extension unit after JVM " + "initialization: `%s_%s`", + u->_feature, u->_version); + return false; + } + } + } + return true; +} + +bool AIExt::add_entry(const char* klass, const char* method, + const char* signature, const char* native_func_name, + void* func_or_data, aiext_naccel_provider_t provider, + TRAPS) { + if (klass == NULL || method == NULL || signature == NULL || + native_func_name == NULL || native_func_name[0] == '\0' || + (func_or_data == NULL && provider == NULL)) { + log_error(aiext)("Invalid entry information"); + return false; + } + + if ((int)strlen(klass) > Symbol::max_length() || + (int)strlen(method) > Symbol::max_length() || + (int)strlen(signature) > Symbol::max_length()) { + log_error(aiext)("Symbol is too long"); + return false; + } + + // Create symbols. + Symbol* klass_sym = SymbolTable::new_permanent_symbol(klass, THREAD); + Symbol* method_sym = SymbolTable::new_permanent_symbol(method, THREAD); + Symbol* sig_sym = SymbolTable::new_permanent_symbol(signature, THREAD); + if (THREAD->has_pending_exception()) { + log_error(aiext)("Failed to create symbols"); + return false; + } + + // Lock the acceleration table. + MutexLockerEx ml(accel_table_lock, Mutex::_no_safepoint_check_flag); + + // Check if the entry presents. + bool found; + AccelCallEntry key(klass_sym, method_sym, sig_sym); + int index = + accel_table->find_sorted(&key, + found); + if (found) { + tty->print_cr("Error: Duplicate native acceleration entry found"); + return false; + } + + // Create entry and add to table. + AccelCallEntry* entry = + new AccelCallEntry(klass_sym, method_sym, sig_sym, + os::strdup(native_func_name), func_or_data, provider); + accel_table->insert_before(index, entry); + return true; +} + +void AIExt::destroy() { + if (!UseAIExtension) { + return; + } + + assert( + loaded_units != NULL && accel_table != NULL && accel_table_lock != NULL, + "should be initialized"); + + // Close all loaded libraries and free related resources. + for (int i = 0; i < loaded_units->length(); ++i) { + AIExtUnit* u = loaded_units->at(i); + assert(u->_handle != NULL, "handle should be set"); + // Call the finalize function if present. + aiext_finalize_t finalize = + (aiext_finalize_t)os::dll_lookup(u->_handle, "aiext_finalize"); + if (finalize != NULL) { + finalize(&GLOBAL_AIEXT_ENV, u->_aiext_handle); + } + + // Free the unit. + delete u; + } + + // Free entries. + for (int i = 0; i < accel_table->length(); ++i) { + delete accel_table->at(i); + } + + // Delete tables and locks. + delete loaded_units; + delete accel_table; + delete accel_table_lock; +} + +const AccelCallEntry* AIExt::find(Symbol* klass, Symbol* method, + Symbol* signature) { + if (!UseAIExtension) { + return NULL; + } + + // Lock the acceleration table. + MutexLockerEx ml(accel_table_lock, Mutex::_no_safepoint_check_flag); + + assert(accel_table != NULL, "must be initialized"); + if (accel_table->is_empty()) { + return NULL; + } + + bool found; + AccelCallEntry key(klass, method, signature); + int index = + accel_table->find_sorted(&key, + found); + + if (found) { + return accel_table->at(index); + } + return NULL; +} + +#ifdef ASSERT +bool AIExt::is_accel_native_call(CallNode* call) { + if (!UseAIExtension) { + return false; + } + + assert(accel_table != NULL, "must be initialized"); + if (accel_table->is_empty()) { + return false; + } + + CallLeafNode* cl = call->as_CallLeaf(); + if (cl == NULL) { + return false; + } + + for (int i = 0; i < accel_table->length(); ++i) { + AccelCallEntry* e = accel_table->at(i); + if (strcmp(e->_native_func_name, cl->_name) == 0) { + return true; + } + } + return false; +} +#endif // ASSERT + +const AIExtUnit* AIExt::find_unit(aiext_handle_t handle) { + if (!UseAIExtension) { + return NULL; + } + + assert(loaded_units != NULL, "must be initialized"); + for (int i = 0; i < loaded_units->length(); ++i) { + AIExtUnit* u = loaded_units->at(i); + if (u->_aiext_handle == handle) { + return u; + } + } + return NULL; +} + +// Fills the given type field(s) by the given CI type. +static void fill_type_field(const Type**& field, ciType* type, bool is_arg, + bool& has_fp_type) { + switch (type->basic_type()) { + case T_BOOLEAN: + *field++ = TypeInt::BOOL; + break; + case T_CHAR: + *field++ = TypeInt::CHAR; + break; + case T_FLOAT: + *field++ = Type::FLOAT; + has_fp_type = true; + break; + case T_DOUBLE: + *field++ = Type::DOUBLE; + *field++ = Type::HALF; + has_fp_type = true; + break; + case T_BYTE: + *field++ = TypeInt::BYTE; + break; + case T_SHORT: + *field++ = TypeInt::SHORT; + break; + case T_INT: + *field++ = TypeInt::INT; + break; + case T_LONG: + *field++ = TypeLong::LONG; + *field++ = Type::HALF; + break; + case T_OBJECT: + *field++ = TypeInstPtr::BOTTOM; + break; + case T_ARRAY: + *field++ = TypeOopPtr::BOTTOM; + break; + case T_VOID: + assert(!is_arg, "void argument?"); + break; + default: + // Other basic types can't be represented by method signatures. + ShouldNotReachHere(); + } +} + +JVMState* AccelCallGenerator::generate(JVMState* jvms) { + GraphKit kit(jvms); + ciMethod* callee = method(); + ciSignature* signature = callee->signature(); + + // Get number of stack slots required for arguments. + int arg_size = callee->arg_size(); + + // Create argument types. + bool has_fp_type = false; + const Type** fields = TypeTuple::fields(arg_size); + const Type** field = fields + TypeFunc::Parms; + if (!callee->is_static()) { + // `this` pointer. + *field++ = TypeInstPtr::NOTNULL; + } + int arg_index = 0; + while (field < fields + TypeFunc::Parms + arg_size) { + fill_type_field(field, signature->type_at(arg_index++), true, has_fp_type); + } + const TypeTuple* args_tuple = + TypeTuple::make(TypeFunc::Parms + arg_size, fields); + + // Create return type. + ciType* ret_type = signature->return_type(); + fields = TypeTuple::fields(ret_type->size()); + field = fields + TypeFunc::Parms; + fill_type_field(field, ret_type, false, has_fp_type); + const TypeTuple* ret_tuple = + TypeTuple::make(TypeFunc::Parms + ret_type->size(), fields); + + // Create function type. + const TypeFunc* func_type = TypeFunc::make(args_tuple, ret_tuple); + + // Create call node. + const char* name = callee->accel_call_entry()->native_func_name(); + CallNode* call; + if (has_fp_type) { + call = new CallLeafNode(func_type, (address)_native_func, name, + TypePtr::BOTTOM); + } else { + call = new CallLeafNoFPNode(func_type, (address)_native_func, name, + TypePtr::BOTTOM); + } + + // Setup inputs and arguments. + kit.set_predefined_input_for_runtime_call(call); + arg_index = 0; + int req_index = TypeFunc::Parms; + if (!callee->is_static()) { + // `this` pointer. + call->init_req(req_index++, kit.argument(arg_index++)); + } + for (int i = 0; i < signature->count(); ++i) { + // Push argument. + Node* arg = kit.argument(arg_index++); + call->init_req(req_index++, arg); + + // Push top for double/long types. + BasicType bt = signature->type_at(i)->basic_type(); + if (bt == T_DOUBLE || bt == T_LONG) { + Node* top = kit.argument(arg_index++); + assert(top == kit.top(), "must be top"); + call->init_req(req_index++, top); + } + } + + // Try to optimize. + Node* c = kit.gvn().transform(call); + assert(c == call, "cannot disappear"); + + // Setup outputs. + kit.set_predefined_output_for_runtime_call(call); + + // Setup return value (if presents). + if (!ret_type->is_void()) { + Node* result = kit.gvn().transform(new ProjNode(call, TypeFunc::Parms)); + kit.push_node(ret_type->basic_type(), result); + } + + // Done. + return kit.transfer_exceptions_into_jvms(); +} diff --git a/src/hotspot/share/opto/aiExtension.hpp b/src/hotspot/share/opto/aiExtension.hpp new file mode 100644 index 00000000000..4ccc665412f --- /dev/null +++ b/src/hotspot/share/opto/aiExtension.hpp @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 Alibaba Group Holding Limited. 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_OPTO_AIEXTENSION_HPP +#define SHARE_OPTO_AIEXTENSION_HPP + +#include "aiext.h" +#include "oops/symbol.hpp" +#include "opto/callGenerator.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/macros.hpp" + +#if !INCLUDE_AIEXT +#error "This file should not be included if `aiext` is not enabled" +#endif // !INCLUDE_AIEXT + +class AIExt; + +// Entry for loaded AI-Extension units. +class AIExtUnit : public CHeapObj { + private: + friend class AIExt; + + // Strings parsed from argument option: + // Feature name. + const char* _feature; + // Version string. + const char* _version; + // Optional parameter list. + const char* _param_list; + + // Handle of the loaded AI-Extension unit library. + void* _handle; + + // Handle for identifying AI-Extension units. + aiext_handle_t _aiext_handle; + + // Comparator for the AI-Extension unit library entry. + static int compare(AIExtUnit* const& u1, AIExtUnit* const& u2); + + // Parses the given argument string to construct a unit. + // The returned unit should be properly deleted before the VM exits. + static AIExtUnit* parse_from_arg(const char* arg); + + AIExtUnit(const char* feature, const char* version, const char* param_list, + aiext_handle_t aiext_handle) + : _handle(NULL), _aiext_handle(aiext_handle) { + assert(feature != NULL && version != NULL, "sanity"); + _feature = os::strdup(feature); + _version = os::strdup(version); + if (param_list != NULL) { + _param_list = os::strdup(param_list); + } else { + _param_list = NULL; + } + } + + // Loads the extension unit. + bool load(); + + public: + ~AIExtUnit() { + os::free((void*)_feature); + os::free((void*)_version); + if (_param_list != NULL) { + os::free((void*)_param_list); + } + if (_handle != NULL) { + os::dll_unload(_handle); + } + } + + // Returns the feature name. + const char* feature() const { return _feature; } + + // Returns the version string. + const char* version() const { return _version; } + + // Returns the parameter list (optional, can be `NULL`). + const char* param_list() const { return _param_list; } +}; + +// Entry for accelerated Java method calls. +class AccelCallEntry : public CHeapObj { + private: + friend class AIExt; + + Symbol* _klass; + Symbol* _method; + Symbol* _signature; + const char* _native_func_name; + void* _func_or_data; + aiext_naccel_provider_t _provider; + + // Comparator for the acceleration table entry. + static int compare(AccelCallEntry* const& e1, AccelCallEntry* const& e2); + + // For finding entries. + AccelCallEntry(Symbol* klass, Symbol* method, Symbol* signature) + : _klass(klass), + _method(method), + _signature(signature), + _native_func_name(NULL), + _func_or_data(NULL), + _provider(NULL) {} + + AccelCallEntry(Symbol* klass, Symbol* method, Symbol* signature, + const char* native_func_name, void* func_or_data, + aiext_naccel_provider_t provider) + : _klass(klass), + _method(method), + _signature(signature), + _native_func_name(native_func_name), + _func_or_data(func_or_data), + _provider(provider) {} + + ~AccelCallEntry() { + if (_native_func_name != NULL) { + os::free((void*)_native_func_name); + } + } + + public: + // Returns the native function pointer. + // This method may call the provider function. + void* get_native_func() const; + + // Returns the native function name. + const char* native_func_name() const { return _native_func_name; } +}; + +// The AI-Extension feature. +class AIExt : public AllStatic { + public: + // Loads AI-Extension units from parsed unit list. + // Returns `false` on error. + static bool init(); + + // Initializes AI-Extension after Java VM initialization. + static bool post_init(); + + // Deletes tables and frees all related resources. + static void destroy(); + + // Adds a new acceleration entry to table. + static bool add_entry(const char* klass, const char* method, + const char* signature, const char* native_func_name, + void* func_or_data, aiext_naccel_provider_t provider, + TRAPS); + + // Finds the acceleration entry for a given method. + static const AccelCallEntry* find(Symbol* klass, Symbol* method, + Symbol* signature); + +#ifdef ASSERT + // Returns `true` if the given call is a accelerated native call. + static bool is_accel_native_call(CallNode* call); +#endif // ASSERT + + // Finds AI-Extension unit by handle. + static const AIExtUnit* find_unit(aiext_handle_t handle); +}; + +// Call generator for accelerated Java method calls. +class AccelCallGenerator : public InlineCallGenerator { + private: + bool _is_virtual; + void* _native_func; + + public: + AccelCallGenerator(ciMethod* m, bool is_virtual, void* native_func) + : InlineCallGenerator(m), + _is_virtual(is_virtual), + _native_func(native_func) {} + + bool is_virtual() const { return _is_virtual; } + + JVMState* generate(JVMState* jvms); +}; + +#endif // SHARE_OPTO_AIEXTENSION_HPP diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 60e8a7fe437..aeb9486063d 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -40,6 +40,9 @@ #include "opto/subnode.hpp" #include "prims/nativeLookup.hpp" #include "runtime/sharedRuntime.hpp" +#if INCLUDE_AIEXT +#include "opto/aiExtension.hpp" +#endif void trace_type_profile(Compile* C, ciMethod *method, int depth, int bci, ciMethod *prof_method, ciKlass *prof_klass, int site_count, int receiver_count) { if (TraceTypeProfile || C->print_inlining()) { @@ -110,6 +113,17 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool log->end_elem(); } +#if INCLUDE_AIEXT + // Handle native acceleration before intrinsic, to make sure we can always + // call the replaced version of the current method. + if (callee->accel_call_entry() != NULL) { + void* native_func = callee->accel_call_entry()->get_native_func(); + if (native_func != NULL) { + return new AccelCallGenerator(callee, call_does_dispatch, native_func); + } + } +#endif // INCLUDE_AIEXT + // Special case the handling of certain common, profitable library // methods. If these methods are replaced with specialized code, // then we return it as the inlined version of the call. diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 016f555c84e..75f627bed52 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -46,6 +46,10 @@ #include "gc/shenandoah/c2/shenandoahBarrierSetC2.hpp" #endif +#if INCLUDE_AIEXT && defined(ASSERT) +#include "opto/aiExtension.hpp" +#endif // INCLUDE_AIEXT && defined(ASSERT) + ConnectionGraph::ConnectionGraph(Compile * C, PhaseIterGVN *igvn) : _nodes(C->comp_arena(), C->unique(), C->unique(), NULL), _in_worklist(C->comp_arena()), @@ -1048,6 +1052,9 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { #ifdef ASSERT if (!(is_arraycopy || BarrierSet::barrier_set()->barrier_set_c2()->is_gc_barrier_node(call) || +#if INCLUDE_AIEXT + AIExt::is_accel_native_call(call) || +#endif // INCLUDE_AIEXT (call->as_CallLeaf()->_name != NULL && (strcmp(call->as_CallLeaf()->_name, "updateBytesCRC32") == 0 || strcmp(call->as_CallLeaf()->_name, "updateBytesCRC32C") == 0 || diff --git a/src/hotspot/share/prims/aiext.cpp b/src/hotspot/share/prims/aiext.cpp new file mode 100644 index 00000000000..ffd7f21623f --- /dev/null +++ b/src/hotspot/share/prims/aiext.cpp @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2025 Alibaba Group Holding Limited. 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 "aiext.h" + +#include + +#include "precompiled.hpp" +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/dictionary.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "logging/log.hpp" +#include "memory/universe.hpp" +#include "oops/oop.inline.hpp" +#include "opto/aiExtension.hpp" +#include "runtime/fieldDescriptor.inline.hpp" +#include "runtime/fieldType.hpp" +#include "runtime/flags/jvmFlag.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/thread.hpp" + +static const unsigned int CURRENT_VERSION = AIEXT_VERSION_2; + +// Returns JVM version string. +static aiext_result_t get_jvm_version(char* buf, size_t buf_size) { + if (buf == NULL || buf_size == 0) { + log_info(aiext)("No output buffer for return value"); + return AIEXT_ERROR; + } + snprintf(buf, buf_size, "%s", Abstract_VM_Version::vm_release()); + return AIEXT_OK; +} + +// Returns current AI-Extension version. +static unsigned int get_aiext_version() { return CURRENT_VERSION; } + +#define DEF_GET_JVM_FLAG(n, t) \ + static aiext_result_t get_jvm_flag_##n(const char* name, t* value) { \ + if (name == NULL) { \ + log_info(aiext)("Invalid flag name"); \ + return AIEXT_ERROR; \ + } \ + JVMFlag* flag = JVMFlag::find_flag(name); \ + if (flag == NULL || !flag->is_##n()) { \ + log_info(aiext)("Flag %s not found or type mismatch", name); \ + return AIEXT_ERROR; \ + } \ + if (value == NULL) { \ + log_info(aiext)("Invalid value pointer"); \ + return AIEXT_ERROR; \ + } \ + *value = flag->get_##n(); \ + return AIEXT_OK; \ + } + +DEF_GET_JVM_FLAG(bool, int) +DEF_GET_JVM_FLAG(int, int) +DEF_GET_JVM_FLAG(uint, unsigned int) +DEF_GET_JVM_FLAG(intx, intptr_t) +DEF_GET_JVM_FLAG(uintx, uintptr_t) +DEF_GET_JVM_FLAG(uint64_t, uint64_t) +DEF_GET_JVM_FLAG(size_t, size_t) +DEF_GET_JVM_FLAG(double, double) + +static aiext_result_t get_jvm_flag_ccstr(const char* name, char* buf, + size_t buf_size) { + if (name == NULL) { + log_info(aiext)("Invalid flag name"); + return AIEXT_ERROR; + } + JVMFlag* flag = JVMFlag::find_flag(name); + if (flag == NULL || !flag->is_ccstr()) { + log_info(aiext)("Flag %s not found or type mismatch", name); + return AIEXT_ERROR; + } + if (buf == NULL || buf_size == 0) { + log_info(aiext)("No output buffer for return value"); + return AIEXT_ERROR; + } + snprintf(buf, buf_size, "%s", flag->get_ccstr()); + return AIEXT_OK; +} + +#undef DEF_GET_JVM_FLAG + +#define DEF_SET_JVM_FLAG(n, t) \ + static aiext_result_t set_jvm_flag_##n(const char* name, t value) { \ + if (name == NULL) { \ + log_info(aiext)("Invalid flag name"); \ + return AIEXT_ERROR; \ + } \ + JVMFlag* flag = JVMFlag::find_flag(name); \ + if (flag == NULL || !flag->is_##n()) { \ + log_info(aiext)("Flag %s not found or type mismatch", name); \ + return AIEXT_ERROR; \ + } \ + JVMFlag::Error result = \ + JVMFlag::n##AtPut(flag, &value, JVMFlag::INTERNAL); \ + return result == JVMFlag::SUCCESS ? AIEXT_OK : AIEXT_ERROR; \ + } + +static aiext_result_t set_jvm_flag_bool(const char* name, int value) { + if (name == NULL) { + log_info(aiext)("Invalid flag name"); + return AIEXT_ERROR; + } + JVMFlag* flag = JVMFlag::find_flag(name); + if (flag == NULL || !flag->is_bool()) { + log_info(aiext)("Flag %s not found or type mismatch", name); + return AIEXT_ERROR; + } + bool b = !!value; + JVMFlag::Error result = JVMFlag::boolAtPut(flag, &b, JVMFlag::INTERNAL); + return result == JVMFlag::SUCCESS ? AIEXT_OK : AIEXT_ERROR; +} + +DEF_SET_JVM_FLAG(int, int) +DEF_SET_JVM_FLAG(uint, unsigned int) +DEF_SET_JVM_FLAG(intx, intptr_t) +DEF_SET_JVM_FLAG(uintx, uintptr_t) +DEF_SET_JVM_FLAG(uint64_t, uint64_t) +DEF_SET_JVM_FLAG(size_t, size_t) +DEF_SET_JVM_FLAG(double, double) + +static aiext_result_t set_jvm_flag_ccstr(const char* name, const char* value) { + if (name == NULL) { + log_info(aiext)("Invalid flag name"); + return AIEXT_ERROR; + } + JVMFlag::Error result = JVMFlag::ccstrAtPut(name, &value, JVMFlag::INTERNAL); + FREE_C_HEAP_ARRAY(char, value); + if (result == JVMFlag::INVALID_FLAG || result == JVMFlag::WRONG_FORMAT) { + log_info(aiext)("Flag %s not found or type mismatch", name); + return AIEXT_ERROR; + } + return result == JVMFlag::SUCCESS ? AIEXT_OK : AIEXT_ERROR; +} + +#undef DEF_SET_JVM_FLAG + +// Gets the current Java thread. +static JavaThread* get_current_java_thread() { + Thread* thread = Thread::current(); + if (!thread->is_Java_thread()) { + log_info(aiext)("Current thread is not a Java thread"); + return NULL; + } + return (JavaThread*)thread; +} + +// Guard for restoring pending exceptions. +struct PendingExceptionGuard { + Thread* thread; + Handle pending_exception; + const char* exception_file; + int exception_line; + + PendingExceptionGuard(TRAPS) { + thread = THREAD; + pending_exception = Handle(THREAD, PENDING_EXCEPTION); + exception_file = THREAD->exception_file(); + exception_line = THREAD->exception_line(); + CLEAR_PENDING_EXCEPTION; + } + + ~PendingExceptionGuard() { + // Restore pending exception. + if (pending_exception.not_null()) { + thread->set_pending_exception(pending_exception(), exception_file, + exception_line); + } + } + + void log(const char* op) const { + if (log_is_enabled(Info, aiext)) { + oop ex = thread->pending_exception(); + log_info(aiext)( + "Exception while %s, %s: %s", op, ex->klass()->external_name(), + java_lang_String::as_utf8_string(java_lang_Throwable::message(ex))); + } + } +}; + +// Registers native acceleration provider for specific Java method. +static aiext_result_t register_naccel_provider( + const char* klass, const char* method, const char* sig, + const char* native_func_name, void* func_or_data, + aiext_naccel_provider_t provider) { + JavaThread* thread = get_current_java_thread(); + ThreadInVMfromNative state_guard(thread); + ResetNoHandleMark rnhm; + HandleMark hm(thread); + PendingExceptionGuard except_guard(thread); + + bool result = AIExt::add_entry(klass, method, sig, native_func_name, + func_or_data, provider, thread); + if (thread->has_pending_exception()) { + except_guard.log("adding native acceleration entry"); + } + + return result ? AIEXT_OK : AIEXT_ERROR; +} + +// Gets unit info, including feature name, version and parameter list. +static aiext_result_t get_unit_info(aiext_handle_t handle, char* feature_buf, + size_t feature_buf_size, char* version_buf, + size_t version_buf_size, + char* param_list_buf, + size_t param_list_buf_size) { + // Find the given unit. + const AIExtUnit* unit = AIExt::find_unit(handle); + if (unit == NULL) { + return AIEXT_ERROR; + } + + // Copy to buffers. + if (feature_buf != NULL && feature_buf_size > 0) { + snprintf(feature_buf, feature_buf_size, "%s", unit->feature()); + } + if (version_buf != NULL && version_buf_size > 0) { + snprintf(version_buf, version_buf_size, "%s", unit->version()); + } + if (param_list_buf != NULL && param_list_buf_size > 0) { + const char* param_list = unit->param_list(); + if (param_list == NULL) { + param_list = ""; + } + snprintf(param_list_buf, param_list_buf_size, "%s", param_list); + } + return AIEXT_OK; +} + +// Gets JNI environment. +static JNIEnv* get_jni_env() { + return JavaThread::current()->jni_environment(); +} + +// Converts `aiext_value_type_t` to `BasicType`. +static aiext_result_t to_basic_type(aiext_value_type_t type, BasicType& bt) { + switch (type) { + case AIEXT_TYPE_BOOLEAN: + bt = T_BOOLEAN; + break; + case AIEXT_TYPE_CHAR: + bt = T_CHAR; + break; + case AIEXT_TYPE_FLOAT: + bt = T_FLOAT; + break; + case AIEXT_TYPE_DOUBLE: + bt = T_DOUBLE; + break; + case AIEXT_TYPE_BYTE: + bt = T_BYTE; + break; + case AIEXT_TYPE_SHORT: + bt = T_SHORT; + break; + case AIEXT_TYPE_INT: + bt = T_INT; + break; + case AIEXT_TYPE_LONG: + bt = T_LONG; + break; + case AIEXT_TYPE_OBJECT: + bt = T_OBJECT; + break; + case AIEXT_TYPE_ARRAY: + bt = T_ARRAY; + break; + default: + log_info(aiext)("Invalid value type %d", type); + return AIEXT_ERROR; + } + return AIEXT_OK; +} + +// Gets Java array layout. +static aiext_result_t get_array_layout(aiext_value_type_t elem_type, + size_t* length_offset, + size_t* data_offset, size_t* elem_size) { + BasicType bt; + aiext_result_t result = to_basic_type(elem_type, bt); + if (result != AIEXT_OK) { + return result; + } + + if (length_offset != NULL) { + *length_offset = arrayOopDesc::length_offset_in_bytes(); + } + if (data_offset != NULL) { + *data_offset = arrayOopDesc::base_offset_in_bytes(bt); + } + if (elem_size != NULL) { + *elem_size = type2aelembytes(bt); + } + + return AIEXT_OK; +} + +// Gets the layout of narrow oop. +static aiext_result_t get_narrow_oop_layout(uint32_t* null, uintptr_t* base, + size_t* shift) { + if (null != NULL) { + *null = 0; + } + if (base != NULL) { + *base = (uintptr_t)Universe::narrow_oop_base(); + } + if (shift != NULL) { + *shift = Universe::narrow_oop_shift(); + } + return AIEXT_OK; +} + +// Finds the given class in the given class loader. +static Klass* find_class(Symbol* class_name, Handle class_loader, + Handle protection_domain, TRAPS) { + if (FieldType::is_array(class_name) || FieldType::is_obj(class_name)) { + return NULL; + } + + class_loader = Handle( + THREAD, + java_lang_ClassLoader::non_reflection_class_loader(class_loader())); + ClassLoaderData* loader_data = + class_loader() == NULL + ? ClassLoaderData::the_null_class_loader_data() + : ClassLoaderDataGraph::find_or_create(class_loader); + + Dictionary* dictionary = loader_data->dictionary(); + unsigned int d_hash = dictionary->compute_hash(class_name); + return dictionary->find(d_hash, class_name, protection_domain); +} + +// Gets the field descriptor of the given field. +// Returns `false` on failure. +static bool get_field_descriptor(const char* klass, const char* field, + const char* sig, bool is_static, + fieldDescriptor& fd, TRAPS) { + JavaThread* thread = (JavaThread*)THREAD; + + // Check class name symbol. + if (klass == NULL || (int)strlen(klass) > Symbol::max_length()) { + log_info(aiext)("Invalid class name %s", klass == NULL ? "" : klass); + return false; + } + + // Extract pending exception. + PendingExceptionGuard except_guard(THREAD); + + // Get class loader. + Handle protection_domain; + Handle loader(THREAD, SystemDictionary::java_system_loader()); + Klass* k = thread->security_get_caller_class(0); + if (k != NULL) { + loader = Handle(THREAD, k->class_loader()); + } + + // Find class from the class loader. + TempNewSymbol class_name = SymbolTable::new_symbol(klass, THREAD); + k = find_class(class_name, loader, protection_domain, THREAD); + if (HAS_PENDING_EXCEPTION) { + except_guard.log("resolving class"); + k = NULL; + } + if (k == NULL) { + log_info(aiext)("Class %s not found", klass); + return false; + } + if (!k->is_instance_klass()) { + log_info(aiext)("Class %s is not an instance class", klass); + return false; + } + InstanceKlass* ik = InstanceKlass::cast(k); + if (!ik->is_initialized()) { + log_info(aiext)("Class %s is not initialized", klass); + return false; + } + + // The class should have been loaded, so the field and signature + // should already be in the symbol table. + // If they're not there, the field doesn't exist. + TempNewSymbol field_name = SymbolTable::probe(field, (int)strlen(field)); + TempNewSymbol sig_name = SymbolTable::probe(sig, (int)strlen(sig)); + if (field_name == NULL || sig_name == NULL || + ik->find_field(field_name, sig_name, is_static, &fd) == NULL) { + log_info(aiext)("Field %s.%s not found in class %s", field, sig, klass); + return false; + } + + // Done. + return true; +} + +// Gets field offset in a Java class, returns `-1` on failure. +static int get_field_offset(const char* klass, const char* field, + const char* sig) { + // Get the current Java thread. + JavaThread* thread = get_current_java_thread(); + if (thread == NULL) { + return -1; + } + + // Transition thread state to VM. + ThreadInVMfromNative state_guard(thread); + ResetNoHandleMark rnhm; + HandleMark hm(thread); + + fieldDescriptor fd; + if (!get_field_descriptor(klass, field, sig, false, fd, thread)) { + return -1; + } + return fd.offset(); +} + +// Gets address of the given static field in a Java class, +// returns `NULL` on failure. +static void* get_static_field_addr(const char* klass, const char* field, + const char* sig) { + // Get the current Java thread. + JavaThread* thread = get_current_java_thread(); + if (thread == NULL) { + return NULL; + } + + // Transition thread state to VM. + ThreadInVMfromNative state_guard(thread); + ResetNoHandleMark rnhm; + HandleMark hm(thread); + + fieldDescriptor fd; + if (!get_field_descriptor(klass, field, sig, true, fd, thread)) { + return NULL; + } + return fd.field_holder()->java_mirror()->field_addr_raw(fd.offset()); +} + +extern const aiext_env_t GLOBAL_AIEXT_ENV = { + // Version. + get_jvm_version, + get_aiext_version, + + // JVM flag access. + get_jvm_flag_bool, + get_jvm_flag_int, + get_jvm_flag_uint, + get_jvm_flag_intx, + get_jvm_flag_uintx, + get_jvm_flag_uint64_t, + get_jvm_flag_size_t, + get_jvm_flag_double, + get_jvm_flag_ccstr, + set_jvm_flag_bool, + set_jvm_flag_int, + set_jvm_flag_uint, + set_jvm_flag_intx, + set_jvm_flag_uintx, + set_jvm_flag_uint64_t, + set_jvm_flag_size_t, + set_jvm_flag_double, + set_jvm_flag_ccstr, + + // Native acceleration. + register_naccel_provider, + + // Unit information. + get_unit_info, + + // JNI. + get_jni_env, + + // Object/pointer layout. + get_array_layout, + get_narrow_oop_layout, + get_field_offset, + get_static_field_addr, +}; diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 18763d8b4b0..1450f5d35f9 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -95,6 +95,9 @@ #include "jvmci/jvmciCompiler.hpp" #include "jvmci/jvmciRuntime.hpp" #endif +#if INCLUDE_AIEXT +#include "opto/aiExtension.hpp" +#endif static jint CurrentVersion = JNI_VERSION_10; @@ -4062,6 +4065,13 @@ static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) { // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving. ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native); MACOS_AARCH64_ONLY(thread->enable_wx(WXExec)); +#if INCLUDE_AIEXT + if (!AIExt::post_init()) { + // Failed to perform post initialization for AI-Extension units, + // just exit VM. + vm_exit(1); + } +#endif // INCLUDE_AIEXT } else { // If create_vm exits because of a pending exception, exit with that // exception. In the future when we figure out how to reclaim memory, diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 509e7268368..90646696ade 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2106,6 +2106,14 @@ WB_ENTRY(void, WB_CleanMetaspaces(JNIEnv* env, jobject target)) VMThread::execute(&op); WB_END +WB_ENTRY(jboolean, WB_IsAIExtSupported(JNIEnv* env)) +#if INCLUDE_AIEXT + return true; +#else + return false; +#endif // INCLUDE_AIEXT +WB_END + #define CC (char*) static JNINativeMethod methods[] = { @@ -2341,6 +2349,7 @@ static JNINativeMethod methods[] = { {CC"aotLibrariesCount", CC"()I", (void*)&WB_AotLibrariesCount }, {CC"getLibcName", CC"()Ljava/lang/String;", (void*)&WB_GetLibcName}, {CC"cleanMetaspaces", CC"()V", (void*)&WB_CleanMetaspaces}, + {CC"isAIExtSupported", CC"()Z", (void*)&WB_IsAIExtSupported}, }; diff --git a/src/hotspot/share/runtime/globals_ext.hpp b/src/hotspot/share/runtime/globals_ext.hpp index 9851208f1a6..8d8167b1736 100644 --- a/src/hotspot/share/runtime/globals_ext.hpp +++ b/src/hotspot/share/runtime/globals_ext.hpp @@ -118,6 +118,11 @@ product(uintx, MorphismLimit, 2, \ "Max call site's morphism we care about") \ \ + AIEXT_ONLY(experimental(bool, UseAIExtension, false, \ + "Enable Alibaba Dragonwell AI Extension")) \ + \ + AIEXT_ONLY(experimental(ccstrlist, AIExtensionUnit, "", \ + "Load external AI-Extension units")) \ //add new AJDK specific flags here diff --git a/src/hotspot/share/runtime/init.cpp b/src/hotspot/share/runtime/init.cpp index 90b203bf8b6..6a0aa9cc876 100644 --- a/src/hotspot/share/runtime/init.cpp +++ b/src/hotspot/share/runtime/init.cpp @@ -41,7 +41,9 @@ #include "runtime/sharedRuntime.hpp" #include "services/memTracker.hpp" #include "utilities/macros.hpp" - +#if INCLUDE_AIEXT +#include "opto/aiExtension.hpp" +#endif // Initialization done by VM thread in vm_init_globals() void check_ThreadShadow(); @@ -177,6 +179,9 @@ void exit_globals() { SymbolTable::dump(tty); StringTable::dump(tty); } +#if INCLUDE_AIEXT + AIExt::destroy(); +#endif // INCLUDE_AIEXT ostream_exit(); } } diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index 5561391641b..9de9404912b 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -142,6 +142,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_AIEXT +#include "opto/aiExtension.hpp" +#endif // Initialization after module runtime initialization void universe_post_module_init(); // must happen after call_initPhase2 @@ -3925,6 +3928,12 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { jint ergo_result = Arguments::apply_ergo(); if (ergo_result != JNI_OK) return ergo_result; +#if INCLUDE_AIEXT + if (!AIExt::init()) { + return JNI_EINVAL; + } +#endif // INCLUDE_AIEXT + // Final check of all ranges after ergonomics which may change values. if (!JVMFlagRangeList::check_ranges()) { return JNI_EINVAL; diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp index 7f1bcff6b32..dcca9c540c3 100644 --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.hpp @@ -287,6 +287,17 @@ #define NOT_JFR_RETURN_(code) { return code; } #endif +// Alibaba AI-Extension +#ifndef INCLUDE_AIEXT +#define INCLUDE_AIEXT 0 +#endif + +#if INCLUDE_AIEXT +#define AIEXT_ONLY(code) code +#else +#define AIEXT_ONLY(code) +#endif + #ifndef INCLUDE_JVMCI #define INCLUDE_JVMCI 1 #endif diff --git a/src/java.base/share/native/include/aiext.h b/src/java.base/share/native/include/aiext.h new file mode 100644 index 00000000000..ffa9148a0a0 --- /dev/null +++ b/src/java.base/share/native/include/aiext.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 _AIEXT_H_ +#define _AIEXT_H_ + +#include +#include + +#include "jni.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Versions of AI-Extension. +// The numerical value of the lower version is guaranteed to be less than that +// of the higher version. +#define AIEXT_VERSION_1 0xBABA0001U +#define AIEXT_VERSION_2 0xBABA0002U + +// The result of initializing AI-Extension unit. +typedef enum { + AIEXT_OK, + AIEXT_ERROR, +} aiext_result_t; + +// Type of a Java value. +typedef enum { + AIEXT_TYPE_BOOLEAN = 4, + AIEXT_TYPE_CHAR = 5, + AIEXT_TYPE_FLOAT = 6, + AIEXT_TYPE_DOUBLE = 7, + AIEXT_TYPE_BYTE = 8, + AIEXT_TYPE_SHORT = 9, + AIEXT_TYPE_INT = 10, + AIEXT_TYPE_LONG = 11, + AIEXT_TYPE_OBJECT = 12, + AIEXT_TYPE_ARRAY = 13, +} aiext_value_type_t; + +// AI-Extension unit handle, for identification of a unit. +typedef uint64_t aiext_handle_t; + +// APIs for AI-Extension units. +typedef struct aiext_env aiext_env_t; + +// Initializes AI-Extension unit. +typedef aiext_result_t (*aiext_init_t)(const aiext_env_t* env, + aiext_handle_t handle); + +// Initializes AI-Extension unit after JVM's initialization. +typedef aiext_result_t (*aiext_post_init_t)(const aiext_env_t* env, + aiext_handle_t handle); + +// Finalizes AI-Extension unit. +typedef void (*aiext_finalize_t)(const aiext_env_t* env, aiext_handle_t handle); + +// Native acceleration provider. +typedef void* (*aiext_naccel_provider_t)(const aiext_env_t* env, + const char* native_func_name, + void* data); + +// Definition of AI-Extension APIs. +struct aiext_env { + // Returns JVM version string. + aiext_result_t (*get_jvm_version)(char* buf, size_t buf_size); + + // Returns current AI-Extension version. + unsigned int (*get_aiext_version)(); + + // Gets JVM flag by name. +#define DECL_GET_JVM_FLAG(n, t) \ + aiext_result_t (*get_jvm_flag_##n)(const char* name, t* value) + DECL_GET_JVM_FLAG(bool, int); + DECL_GET_JVM_FLAG(int, int); + DECL_GET_JVM_FLAG(uint, unsigned int); + DECL_GET_JVM_FLAG(intx, intptr_t); + DECL_GET_JVM_FLAG(uintx, uintptr_t); + DECL_GET_JVM_FLAG(uint64_t, uint64_t); + DECL_GET_JVM_FLAG(size_t, size_t); + DECL_GET_JVM_FLAG(double, double); + aiext_result_t (*get_jvm_flag_ccstr)(const char* name, char* buf, + size_t buf_size); +#undef DECL_GET_JVM_FLAG + + // Sets JVM flag with new value. +#define DECL_SET_JVM_FLAG(n, t) \ + aiext_result_t (*set_jvm_flag_##n)(const char* name, t value) + DECL_SET_JVM_FLAG(bool, int); + DECL_SET_JVM_FLAG(int, int); + DECL_SET_JVM_FLAG(uint, unsigned int); + DECL_SET_JVM_FLAG(intx, intptr_t); + DECL_SET_JVM_FLAG(uintx, uintptr_t); + DECL_SET_JVM_FLAG(uint64_t, uint64_t); + DECL_SET_JVM_FLAG(size_t, size_t); + DECL_SET_JVM_FLAG(double, double); + DECL_SET_JVM_FLAG(ccstr, const char*); +#undef DECL_SET_JVM_FLAG + + // Registers native acceleration provider for specific Java method. + aiext_result_t (*register_naccel_provider)(const char* klass, + const char* method, + const char* sig, + const char* native_func_name, + void* func_or_data, + aiext_naccel_provider_t provider); + + // Gets unit info, including feature name, version and parameter list. + // `handle` is provided by the JVM in the `aiext_init` function. + aiext_result_t (*get_unit_info)(aiext_handle_t handle, char* feature_buf, + size_t feature_buf_size, char* version_buf, + size_t version_buf_size, char* param_list_buf, + size_t param_list_buf_size); + + // Gets JNI interface + JNIEnv* (*get_jni_env)(); + + // Gets Java array layout of the given element type, including element size in + // bytes, length offset in bytes and data offset in bytes. + // The offset of length are same for all array types, and the size of length + // should always be 4 bytes. + aiext_result_t (*get_array_layout)(aiext_value_type_t elem_type, + size_t* length_offset, size_t* data_offset, + size_t* elem_size); + + // Gets the layout of narrow oop. A non-null narrow oop can be decode to a raw + // pointer by `base + (oop << shift)`. + aiext_result_t (*get_narrow_oop_layout)(uint32_t* null, uintptr_t* base, + size_t* shift); + + // Gets field offset in bytes in a Java class, returns `-1` on failure. + int (*get_field_offset)(const char* klass, const char* field, + const char* sig); + + // Gets address of the given static field in a Java class, + // returns `nullptr` on failure. + // The address is only valid when calling this API. + // Program should not cache the address. + void* (*get_static_field_addr)(const char* klass, const char* field, + const char* sig); +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _AIEXT_H_ diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index ab78d050efc..d4e8c4fd051 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -73,6 +73,7 @@ requires.properties= \ vm.compiler2.enabled \ vm.musl \ vm.flagless \ + vm.aiext \ docker.support # Minimum jtreg version diff --git a/test/hotspot/jtreg/compiler/alibaba/TestAIExtension.java b/test/hotspot/jtreg/compiler/alibaba/TestAIExtension.java new file mode 100644 index 00000000000..8072f5b2c37 --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/TestAIExtension.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test TestAIExtension + * @summary Check that the AI-Extension feature works correctly + * @requires vm.aiext + * @library /test/lib / + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * @run main/native/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestAIExtension + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import sun.hotspot.WhiteBox; +import compiler.whitebox.CompilerWhiteBoxTest; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.List; + +public class TestAIExtension { + private static final String UNIT_NACCEL_1 = "libAIExtTestNaccel_1"; + private static final String UNIT_NACCEL_2 = "libAIExtTestNaccel_2"; + private static final String UNIT_NACCEL2_1 = "libAIExtTestNaccel2_1"; + private static final String UNIT_ENVCALL_1 = "libAIExtTestEnvCall_1"; + private static final String UNIT_JNICALL_1 = "libAIExtTestJNICall_1"; + private static final String UNIT_INITERR_1 = "libAIExtTestInitError_1"; + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + public static void main(String[] args) throws Exception { + // These should work. + testUnitLoadOk(); // Just `-version`. + testUnitLoadOk("-XX:AIExtensionUnit="); // No units. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_NACCEL_1); // A valid unit. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_NACCEL_2); // A valid unit but no finalizer. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_ENVCALL_1); // A valid unit including some env calls. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_JNICALL_1) // A valid unit including JNI calls. + .shouldContain("JNI call is success") + .shouldContain("Output from Java thread"); + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_INITERR_1); // Another valid units. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_INITERR_1 + // Valid unit with one parameter. + "?init_error=0"); + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_INITERR_1 + // Valid unit with multiple parameters. + "?init_error=0:post_init_error=0"); + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_INITERR_1 + // Valid unit with multiple parameters. + "?init_error=0:post_init_error=0:whatever=xxx"); + + // Duplicate units, but okay. + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_NACCEL_1, + "-XX:AIExtensionUnit=" + UNIT_NACCEL_1) + .shouldContain("Ignoring duplicate AI-Extension unit"); + testUnitLoadOk("-XX:AIExtensionUnit=" + UNIT_NACCEL_1, + "-XX:AIExtensionUnit=" + UNIT_NACCEL_2) + .shouldContain("Ignoring duplicate AI-Extension unit"); + + // Invalid acceleration unit name. + testUnitParseError("-XX:AIExtensionUnit=?"); + + // Duplicate entries in different units. + testUnitLoadError("-XX:AIExtensionUnit=" + UNIT_NACCEL_1, + "-XX:AIExtensionUnit=" + UNIT_NACCEL2_1) + .shouldContain("Duplicate native acceleration entry found"); + + // Some units are invalid. + testUnitParseError("-XX:AIExtensionUnit=" + UNIT_NACCEL_1, + "-XX:AIExtensionUnit=?"); + testUnitParseError("-XX:AIExtensionUnit=?", + "-XX:AIExtensionUnit=" + UNIT_NACCEL_1); + + // Error when performing initialization/post-initialization. + testUnitLoadError("-XX:AIExtensionUnit=" + UNIT_INITERR_1 + "?init_error=1") + .shouldContain("Returning error in `aiext_init`..."); + testUnitLoadError("-XX:AIExtensionUnit=" + UNIT_INITERR_1 + "?post_init_error=1") + .shouldContain("Returning error in `aiext_post_init`..."); + + // Test method compilation. + testMethodCompile("hello").shouldContain("Hello from"); + testMethodCompile("hello_int", "42").shouldContain("42 (int)"); + testMethodCompile("hello_long", "43").shouldContain("43 (long)"); + testMethodCompile("hello_float", "44").shouldContain("44.00 (float)"); + testMethodCompile("hello_double", "45").shouldContain("45.00 (double)"); + testMethodCompile("hello_bytes", "test method compile") + .shouldContain("test method compile (bytes)"); + testMethodCompile("hello_object", "hi") + .shouldContain("0x") + .shouldContain("(object)"); + testMethodCompile("hello_short_method", "46") + .shouldContain("0x") + .shouldContain("(this)") + .shouldContain("46 (short)"); + testMethodCompile("add_ints", "47", "48").shouldContain("95"); + testMethodCompile("add_doubles", "47", "48").shouldContain("95.00"); + testMethodCompile("add_arrays", "3", "1", "2", "2").shouldContain("3\n3\n1\n"); + testMethodCompile("add_to_int", "49").shouldContain("49"); + testMethodCompile("add_to_double", "50").shouldContain("50.00"); + testMethodCompile("should_skip").shouldContain("Skipped twice"); + testMethodCompileInDifferentOopLayouts((oa) -> oa.shouldContain("Hello\nworld\n!\n"), + "read_strs"); + testMethodCompile("add_to_static_int", "34").shouldContain("46"); + testMethodCompileInDifferentOopLayouts((oa) -> oa.shouldContain("A\nB\n"), + "check_static_enum"); + } + + private static OutputAnalyzer testUnitLoadOk(String... commands) throws Exception { + OutputAnalyzer output = getJavaVersionOutput(commands); + output.shouldHaveExitValue(0); + return output; + } + + private static void testUnitParseError(String... commands) throws Exception { + OutputAnalyzer output = getJavaVersionOutput(commands); + output.shouldNotHaveExitValue(0); + output.shouldContain("Invalid AI-Extension option:"); + output.shouldContain("Could not create the Java Virtual Machine"); + } + + private static OutputAnalyzer testUnitLoadError(String... commands) throws Exception { + OutputAnalyzer output = getJavaVersionOutput(commands); + output.shouldNotHaveExitValue(0); + return output; + } + + private static OutputAnalyzer getJavaVersionOutput(String... commands) throws Exception { + ArrayList args = new ArrayList<>(List.of( + "-Xlog:aiext=debug", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseAIExtension" + )); + args.addAll(List.of(commands)); + args.add("-version"); + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(args); + // Setup `DRAGONWELL_AIEXT_HOME` for testing. + pb.environment().put("DRAGONWELL_AIEXT_HOME", System.getProperty("test.nativepath")); + return ProcessTools.executeCommand(pb); + } + + private static OutputAnalyzer testMethodCompile(String... testArgs) throws Exception { + return testMethodCompile(null, testArgs); + } + + private static void testMethodCompileInDifferentOopLayouts( + Consumer checker, + String... testArgs + ) throws Exception { + String[][] jvmArgSets = new String[][]{ + new String[]{"-XX:-UseCompressedOops", "-XX:-UseCompressedClassPointers", "-XX:-UseCompactObjectHeaders"}, + new String[]{"-XX:-UseCompressedOops", "-XX:+UseCompressedClassPointers", "-XX:-UseCompactObjectHeaders"}, + new String[]{"-XX:-UseCompressedOops", "-XX:+UseCompressedClassPointers", "-XX:+UseCompactObjectHeaders"}, + new String[]{"-XX:+UseCompressedOops", "-XX:-UseCompressedClassPointers", "-XX:-UseCompactObjectHeaders"}, + new String[]{"-XX:+UseCompressedOops", "-XX:+UseCompressedClassPointers", "-XX:-UseCompactObjectHeaders"}, + new String[]{"-XX:+UseCompressedOops", "-XX:+UseCompressedClassPointers", "-XX:+UseCompactObjectHeaders"}, + }; + for (String[] jvmArgs : jvmArgSets) { + OutputAnalyzer output = testMethodCompile(jvmArgs, testArgs); + checker.accept(output); + } + } + + private static OutputAnalyzer testMethodCompile(String[] jvmArgs, String... testArgs) throws Exception { + ArrayList args = new ArrayList<>(); + if (jvmArgs != null) { + args.addAll(List.of(jvmArgs)); + } + args.addAll(List.of( + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-XX:-BackgroundCompilation", + // "-XX:CompileCommand=print,TestAIExtension$Launcher::dispatch", // For debugging. + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseAIExtension", + "-XX:AIExtensionUnit=" + UNIT_NACCEL_1, + Launcher.class.getName() + )); + args.addAll(List.of(testArgs)); + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(args); + // Setup `DRAGONWELL_AIEXT_HOME` for testing. + pb.environment().put("DRAGONWELL_AIEXT_HOME", System.getProperty("test.nativepath")); + + OutputAnalyzer output = ProcessTools.executeCommand(pb); + output.shouldHaveExitValue(0).shouldContain("aiext_finalize\n"); + return output; + } + + public static class Launcher { + public static void main(String[] args) throws Exception { + // Call the dispatch method to make classes to be loaded. + skip_counter = 0; + dispatch(args); + + // Compile it with C2. + int compLevel = CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION; + Method m = Launcher.class.getDeclaredMethod("dispatch", String[].class); + while (WHITE_BOX.getMethodCompilationLevel(m) != compLevel) { + WHITE_BOX.enqueueMethodForCompilation(m, compLevel); + } + + // Then call it again. + dispatch(args); + if (skip_counter == 2) { + System.out.println("Skipped twice"); + } + } + + private static void dispatch(String[] args) { + switch (args[0]) { + case "hello": { + hello(); + break; + } + case "hello_int": { + hello(Integer.parseInt(args[1])); + break; + } + case "hello_long": { + hello(Long.parseLong(args[1])); + break; + } + case "hello_float": { + hello(Float.parseFloat(args[1])); + break; + } + case "hello_double": { + hello(Double.parseDouble(args[1])); + break; + } + case "hello_bytes": { + hello(args[1].getBytes()); + break; + } + case "hello_object": { + hello(args[1]); + break; + } + case "hello_short_method": { + Launcher l = new Launcher(); + l.hello(Short.parseShort(args[1])); + break; + } + case "add_ints": { + int result = add(Integer.parseInt(args[1]), Integer.parseInt(args[2])); + System.out.println(result); + break; + } + case "add_doubles": { + double result = add(Double.parseDouble(args[1]), Double.parseDouble(args[2])); + System.out.printf("%.2f\n", result); + break; + } + case "add_arrays": { + int[] a = new int[Integer.parseInt(args[1])]; + Arrays.fill(a, Integer.parseInt(args[2])); + int[] b = new int[Integer.parseInt(args[3])]; + Arrays.fill(b, Integer.parseInt(args[4])); + + Launcher l = new Launcher(); + l.add(a, b); + + for (int i : a) { + System.out.println(i); + } + break; + } + case "add_to_int": { + int i = Integer.parseInt(args[1]); + + Launcher l = new Launcher(); + l.add_to_int(i); + + System.out.println(l.x_int); + break; + } + case "add_to_double": { + double d = Double.parseDouble(args[1]); + + Launcher l = new Launcher(); + l.add_to_double(d); + + System.out.printf("%.2f\n", l.x_double); + break; + } + case "should_skip": { + should_skip(); + break; + } + case "read_strs": { + Launcher l = new Launcher(); + l.read_strs(); + break; + } + case "add_to_static_int": { + int i = Integer.parseInt(args[1]); + add_to_static_int(i); + System.out.println(static_int); + break; + } + case "check_static_enum": { + check_static_enum(); + System.out.println(static_enum.name()); + break; + } + default: { + throw new RuntimeException("Unknown test: " + args[0]); + } + } + } + + private static void hello() {} + private static void hello(int i) {} + private static void hello(long l) {} + private static void hello(float f) {} + private static void hello(double d) {} + private static void hello(byte[] chars) {} + private static void hello(Object obj) {} + private void hello(short s) {} + + private static int add(int a, int b) { return 0; } + private static double add(double a, double b) { return 0; } + private void add(int[] a, int[] b) {} + + private int x_int = 0; + private double x_double = 0; + private void add_to_int(int i) {} + private void add_to_double(double d) {} + + private static int skip_counter = 0; + private static void should_skip() { skip_counter++; } + + private String[] strs = {"Hello", "world", "!"}; + private void read_strs() {} + + private static int static_int = 12; + private static void add_to_static_int(int i) {} + + enum TestEnum { + A, B, C + } + private static TestEnum static_enum = TestEnum.A; + private static void check_static_enum() {} + } + + // This will be invoked by JNI calls. + public static class JNITestClass { + public static void testMethod() { + new Thread(() -> { + try { + System.out.println("Output from Java thread"); + Thread.currentThread().sleep(1000); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestEnvCall_1.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestEnvCall_1.c new file mode 100644 index 00000000000..29797771098 --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestEnvCall_1.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + // Check feature name, version and parameter list. + char feature[32], version[32], param[32]; + aiext_result_t result = + env->get_unit_info(handle, feature, sizeof(feature), version, + sizeof(version), param, sizeof(param)); + if (result != AIEXT_OK) { + return result; + } + printf("aiext_init: feature=%s, version=%s, param=%s\n", feature, version, + param); + if (strcmp(feature, "libAIExtTestEnvCall") != 0 || + strcmp(version, "1") != 0 || param[0] != '\0') { + return AIEXT_ERROR; + } + + // Read flag `NonProfiledCodeHeapSize`. + uintptr_t size; + result = env->get_jvm_flag_uintx("NonProfiledCodeHeapSize", &size); + printf("Result %d, NonProfiledCodeHeapSize=%" PRIuPTR "\n", result, size); + if (result != AIEXT_OK) { + return result; + } + + // Shrink `NonProfiledCodeHeapSize`. + size -= 4096 * 20; + result = env->set_jvm_flag_uintx("NonProfiledCodeHeapSize", size); + if (result != AIEXT_OK) { + return result; + } + + // Read again. + uintptr_t new_size; + result = env->get_jvm_flag_uintx("NonProfiledCodeHeapSize", &new_size); + if (result != AIEXT_OK) { + return result; + } + printf("Result %d, NonProfiledCodeHeapSize=%" PRIuPTR "\n", result, new_size); + + // Check the new size. + return new_size == size ? AIEXT_OK : AIEXT_ERROR; +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestInitError_1.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestInitError_1.c new file mode 100644 index 00000000000..3207b48fd1c --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestInitError_1.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +#define INIT_ERROR "init_error=" +#define INIT_ERROR_LEN (sizeof(INIT_ERROR) - 1) +#define POST_INIT_ERROR "post_init_error=" +#define POST_INIT_ERROR_LEN (sizeof(POST_INIT_ERROR) - 1) + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +static bool init_error = false, post_init_error = false; + +// Parses command line arguments. +static void parse_param(const char* param, size_t len) { + if (strncmp(param, INIT_ERROR, MIN(INIT_ERROR_LEN, len)) == 0) { + // Parse `init_error`. + param += INIT_ERROR_LEN; + init_error = strncmp(param, "0", MIN(1, len)) != 0; + } else if (strncmp(param, POST_INIT_ERROR, MIN(POST_INIT_ERROR_LEN, len)) == + 0) { + // Parse `post_init_error`. + param += POST_INIT_ERROR_LEN; + post_init_error = strncmp(param, "0", MIN(1, len)) != 0; + } +} + +// Parses parameter list. +static void parse_params(const char* params) { + size_t len = strlen(params); + const char* param = params; + for (;;) { + char* colon = strchr(param, ':'); + if (colon == NULL) { + parse_param(param, len - (param - params)); + break; + } else { + parse_param(param, colon - param); + param = colon + 1; + } + } +} + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + // Parse parameter list. + char params[128]; + aiext_result_t result = + env->get_unit_info(handle, NULL, 0, NULL, 0, params, sizeof(params)); + if (result != AIEXT_OK) { + printf("Failed to get unit info\n"); + return AIEXT_ERROR; + } + parse_params(params); + + // Check if we should return an error. + if (init_error) { + printf("Returning error in `aiext_init`...\n"); + return AIEXT_ERROR; + } + return AIEXT_OK; +} + +JNIEXPORT aiext_result_t JNICALL aiext_post_init(const aiext_env_t* env, + aiext_handle_t handle) { + if (post_init_error) { + printf("Returning error in `aiext_post_init`...\n"); + return AIEXT_ERROR; + } + return AIEXT_OK; +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestJNICall_1.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestJNICall_1.c new file mode 100644 index 00000000000..13fcde66b19 --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestJNICall_1.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + return AIEXT_OK; +} + +JNIEXPORT aiext_result_t JNICALL aiext_post_init(const aiext_env_t* env, + aiext_handle_t handle) { + aiext_result_t result = AIEXT_ERROR; + JNIEnv* jni = env->get_jni_env(); + if (jni == NULL) { + return AIEXT_ERROR; + } + + // Test JNI invocation. + const char* test = "test_string"; + jstring str = (*jni)->NewStringUTF(jni, test); + jsize len = (*jni)->GetStringUTFLength(jni, str); + if (len != (jsize)strlen(test)) { + return AIEXT_ERROR; + } else { + fprintf(stdout, "JNI call is success\n"); + } + + // Test Java method invocation. + jclass testclass = (*jni)->FindClass(jni, "TestAIExtension$JNITestClass"); + if (testclass == NULL) { + fprintf(stderr, "Can not find test class\n"); + return AIEXT_ERROR; + } + + jmethodID m = (*jni)->GetStaticMethodID(jni, testclass, "testMethod", "()V"); + if (m == NULL) { + fprintf(stderr, "Can not find method\n"); + return AIEXT_ERROR; + } + + (*jni)->CallStaticVoidMethod(jni, testclass, m); + return AIEXT_OK; +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel2_1.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel2_1.c new file mode 100644 index 00000000000..ea0603da25f --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel2_1.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +// For ()V static method. +static void hello() { printf("Hello again from native library!\n"); } + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + return AIEXT_OK; +} + +JNIEXPORT aiext_result_t JNICALL aiext_post_init(const aiext_env_t* env, + aiext_handle_t handle) { + return env->register_naccel_provider("TestAIExtension$Launcher", "hello", + "()V", "hello", hello, NULL); +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_1.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_1.c new file mode 100644 index 00000000000..cac23685e97 --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_1.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +static size_t obj_array_len_offset, obj_array_data_offset, obj_array_elem_size; +static size_t byte_array_len_offset, byte_array_data_offset, + byte_array_elem_size; +static size_t int_array_len_offset, int_array_data_offset, int_array_elem_size; + +static uint32_t narrow_null; +static uintptr_t narrow_base; +static size_t narrow_shift; + +static int offset_x_int, offset_x_double, offset_strs, offset_string_value; + +static void *addr_static_int, *addr_static_enum, *addr_test_enum_a, + *addr_test_enum_b, *addr_test_enum_c; + +static void* native_provider(const aiext_env_t* env, + const char* native_func_name, void* data) { + offset_x_int = + env->get_field_offset("TestAIExtension$Launcher", "x_int", "I"); + offset_x_double = + env->get_field_offset("TestAIExtension$Launcher", "x_double", "D"); + offset_strs = env->get_field_offset("TestAIExtension$Launcher", "strs", + "[Ljava/lang/String;"); + offset_string_value = + env->get_field_offset("java/lang/String", "value", "[B"); + addr_static_int = + env->get_static_field_addr("TestAIExtension$Launcher", "static_int", "I"); + addr_static_enum = + env->get_static_field_addr("TestAIExtension$Launcher", "static_enum", + "LTestAIExtension$Launcher$TestEnum;"); + addr_test_enum_a = + env->get_static_field_addr("TestAIExtension$Launcher$TestEnum", "A", + "LTestAIExtension$Launcher$TestEnum;"); + addr_test_enum_b = + env->get_static_field_addr("TestAIExtension$Launcher$TestEnum", "B", + "LTestAIExtension$Launcher$TestEnum;"); + addr_test_enum_c = + env->get_static_field_addr("TestAIExtension$Launcher$TestEnum", "C", + "LTestAIExtension$Launcher$TestEnum;"); + printf( + "Compiling `%s`, offset_x_int=%d, offset_x_double=%d, " + "offset_strs=%d, offset_string_value=%d, addr_static_int=%p, " + "addr_static_enum=%p, addr_test_enum_a=%p, addr_test_enum_b=%p, " + "addr_test_enum_c=%p\n", + native_func_name, offset_x_int, offset_x_double, offset_strs, + offset_string_value, addr_static_int, addr_static_enum, addr_test_enum_a, + addr_test_enum_b, addr_test_enum_c); + if (offset_x_int < 0 || offset_x_double < 0 || offset_strs < 0 || + offset_string_value < 0 || addr_static_int == NULL || + addr_static_enum == NULL || addr_test_enum_a == NULL || + addr_test_enum_b == NULL || addr_test_enum_c == NULL) { + return NULL; + } + return data; +} + +static void* get_raw_pointer(void* oop) { + if (obj_array_elem_size < sizeof(void*)) { + // Narrow oop layout. + uint32_t narrow = *(uint32_t*)oop; + if (narrow == narrow_null) { + return NULL; + } + return (void*)(narrow_base + ((uintptr_t)narrow << narrow_shift)); + } else { + return *(void**)oop; + } +} + +static bool is_oop_eq(const void* lhs, const void* rhs) { + if (obj_array_elem_size < sizeof(void*)) { + // Narrow oop layout. + return *(const uint32_t*)lhs == *(const uint32_t*)rhs; + } else { + return *(void* const*)lhs == *(void* const*)rhs; + } +} + +static void set_oop(void* dst, const void* oop) { + if (obj_array_elem_size < sizeof(void*)) { + // Narrow oop layout. + *(uint32_t*)dst = *(const uint32_t*)oop; + } else { + *(void**)dst = *(void* const*)oop; + } +} + +// For ()V static method. +static void hello() { printf("Hello from native library!\n"); } + +// For (I)V static method. +static void hello_int(int32_t i) { + printf("Hello, I got %" PRId32 " (int)!\n", i); +} + +// For (J)V static method. +static void hello_long(int64_t l) { + printf("Hello, I got %" PRId64 " (long)!\n", l); +} + +// For (F)V static method. +static void hello_float(float f) { printf("Hello, I got %.2f (float)!\n", f); } + +// For (D)V static method. +static void hello_double(double d) { + printf("Hello, I got %.2f (double)!\n", d); +} + +// For ([B)V static method. +static void hello_bytes(const void* chars) { + int32_t len = *(const int32_t*)((const char*)chars + byte_array_len_offset); + char* cs = (char*)chars + byte_array_data_offset; + assert(byte_array_elem_size == sizeof(char) && "unexpected byte size"); + printf("Hello, I got %.*s (bytes)!\n", len, cs); +} + +// For (Ljava/lang/Object;)V static method. +static void hello_object(const void* obj) { + printf("Hello, I got %p (object)!\n", obj); +} + +// For (S)V method (with a `this` pointer). +static void hello_short_method(const void* this, int16_t i) { + printf("Hello, I got %p (this) and %" PRId16 " (short)!\n", this, i); +} + +// Adds two integers. +// For (II)I static method. +static int32_t add_ints(int32_t a, int32_t b) { return a + b; } + +// Adds two doubles. +// For (DD)D static method. +static double add_doubles(double a, double b) { return a + b; } + +// Adds two integer arrays, updates the first array in-place. +// For ([I[I)V method. +static void add_arrays(const void* this, void* a, const void* b) { + int32_t a_len = *(int32_t*)((char*)a + int_array_len_offset); + int32_t b_len = *(int32_t*)((char*)b + int_array_len_offset); + for (int i = 0; i < a_len && i < b_len; i++) { + int32_t* pa = + (int32_t*)((char*)a + int_array_data_offset + i * int_array_elem_size); + int32_t* pb = + (int32_t*)((char*)b + int_array_data_offset + i * int_array_elem_size); + *pa += *pb; + } +} + +// Adds the given integer to object's field. +// For (I)V method. +static void add_to_int(void* this, int32_t i) { + assert(offset_x_int > 0 && "Invalid field offset"); + int32_t* x_int = (int32_t*)((char*)this + offset_x_int); + *x_int += i; +} + +// Adds the given double to object's field. +// For (D)V method. +static void add_to_double(void* this, double d) { + assert(offset_x_double > 0 && "Invalid field offset"); + double* x_double = (double*)((char*)this + offset_x_double); + *x_double += d; +} + +// Reads the string array and prints it out. +// For ()V method. +static void read_strs(void* this) { + assert(offset_strs > 0 && offset_string_value > 0 && "Invalid field offset"); + + // Get pointer to the string array. + void* strs = get_raw_pointer((char*)this + offset_strs); + assert(strs != NULL && "Invalid string array"); + + // Traverse the string array. + int32_t strs_len = *(int32_t*)((char*)strs + obj_array_len_offset); + for (int32_t i = 0; i < strs_len; i++) { + void* str = get_raw_pointer((char*)strs + obj_array_data_offset + + i * obj_array_elem_size); + assert(str != NULL && "Invalid string"); + + // Get pointer to the string's byte array. + void* value = get_raw_pointer((char*)str + offset_string_value); + assert(value != NULL && "Invalid string's byte array"); + + // Print the string's byte array. + int32_t value_len = *(int32_t*)((char*)value + byte_array_len_offset); + for (int32_t j = 0; j < value_len; j++) { + char c = + *((char*)value + byte_array_data_offset + j * byte_array_elem_size); + putchar(c); + } + putchar('\n'); + } +} + +// Adds the given integer to static field. +// For (I)V method. +static void add_to_static_int(int32_t i) { + assert(addr_static_int != NULL && "Invalid static field address"); + *(int32_t*)addr_static_int += i; +} + +// Checks the static enum field and updates it. +// For ()V method. +static void check_static_enum() { + assert(addr_static_enum != NULL && "Invalid static field address"); + if (is_oop_eq(addr_static_enum, addr_test_enum_a)) { + printf("A\n"); + } else if (is_oop_eq(addr_static_enum, addr_test_enum_b)) { + printf("B\n"); + } else if (is_oop_eq(addr_static_enum, addr_test_enum_c)) { + printf("C\n"); + } else { + printf("Unknown\n"); + } + set_oop(addr_static_enum, addr_test_enum_b); +} + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + return AIEXT_OK; +} + +JNIEXPORT aiext_result_t JNICALL aiext_post_init(const aiext_env_t* env, + aiext_handle_t handle) { + // Get array layout. + aiext_result_t res; + res = env->get_array_layout(AIEXT_TYPE_OBJECT, &obj_array_len_offset, + &obj_array_data_offset, &obj_array_elem_size); + if (res != AIEXT_OK) { + return res; + } + res = env->get_array_layout(AIEXT_TYPE_BYTE, &byte_array_len_offset, + &byte_array_data_offset, &byte_array_elem_size); + if (res != AIEXT_OK) { + return res; + } + res = env->get_array_layout(AIEXT_TYPE_INT, &int_array_len_offset, + &int_array_data_offset, &int_array_elem_size); + if (res != AIEXT_OK) { + return res; + } + + // Get narrow oop layout. + res = env->get_narrow_oop_layout(&narrow_null, &narrow_base, &narrow_shift); + if (res != AIEXT_OK) { + return res; + } + +#define REPLACE_WITH_NATIVE(m, s, f) \ + do { \ + aiext_result_t res; \ + res = env->register_naccel_provider("TestAIExtension$Launcher", m, s, #f, \ + f, NULL); \ + if (res != AIEXT_OK) { \ + return res; \ + } \ + } while (0) +#define REPLACE_WITH_PROVIDER(m, s, f) \ + do { \ + aiext_result_t res; \ + res = env->register_naccel_provider("TestAIExtension$Launcher", m, s, #f, \ + f, native_provider); \ + if (res != AIEXT_OK) { \ + return res; \ + } \ + } while (0) + + REPLACE_WITH_NATIVE("hello", "()V", hello); + REPLACE_WITH_NATIVE("hello", "(I)V", hello_int); + REPLACE_WITH_NATIVE("hello", "(J)V", hello_long); + REPLACE_WITH_NATIVE("hello", "(F)V", hello_float); + REPLACE_WITH_NATIVE("hello", "(D)V", hello_double); + REPLACE_WITH_NATIVE("hello", "([B)V", hello_bytes); + REPLACE_WITH_NATIVE("hello", "(Ljava/lang/Object;)V", hello_object); + REPLACE_WITH_NATIVE("hello", "(S)V", hello_short_method); + + REPLACE_WITH_NATIVE("add", "(II)I", add_ints); + REPLACE_WITH_NATIVE("add", "(DD)D", add_doubles); + REPLACE_WITH_NATIVE("add", "([I[I)V", add_arrays); + + REPLACE_WITH_PROVIDER("add_to_int", "(I)V", add_to_int); + REPLACE_WITH_PROVIDER("add_to_double", "(D)V", add_to_double); + + REPLACE_WITH_PROVIDER("should_skip", "()V", NULL); + + REPLACE_WITH_PROVIDER("read_strs", "()V", read_strs); + + REPLACE_WITH_PROVIDER("add_to_static_int", "(I)V", add_to_static_int); + REPLACE_WITH_PROVIDER("check_static_enum", "()V", check_static_enum); + +#undef REPLACE_WITH_NATIVE +#undef REPLACE_WITH_PROVIDER + return AIEXT_OK; +} + +JNIEXPORT void JNICALL aiext_finalize(const aiext_env_t* env, + aiext_handle_t handle) { + printf("aiext_finalize\n"); +} diff --git a/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_2.c b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_2.c new file mode 100644 index 00000000000..927a72bc9c4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/alibaba/libAIExtTestNaccel_2.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. 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 "aiext.h" + +JNIEXPORT aiext_result_t JNICALL aiext_init(const aiext_env_t* env, + aiext_handle_t handle) { + // This just an empty unit. + return AIEXT_OK; +} diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 6dfbcfd337c..3f87afc4ca4 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -129,6 +129,7 @@ public Map call() { map.put("vm.musl", this::isMusl); map.put("release.implementor", this::implementor); map.put("vm.flagless", this::isFlagless); + map.put("vm.aiext", this::vmAIExt); map.putAll(xOptFlags()); // -Xmx4g -> @requires vm.opt.x.Xmx == "4g" ) vmGC(map); // vm.gc.X = true/false vmOptFinalFlags(map); @@ -636,6 +637,15 @@ private Stream allFlags() { return Stream.of((System.getProperty("test.vm.opts", "") + " " + System.getProperty("test.java.opts", "")).trim().split("\\s+")); } + /** + * Checks AI-Extension support. + * + * @return true if AI-Extension is supported. + */ + private String vmAIExt() { + return "" + WB.isAIExtSupported(); + } + /** * Parses extra options, options that start with -X excluding the * bare -X option (as it is not considered an extra option). diff --git a/test/lib/sun/hotspot/WhiteBox.java b/test/lib/sun/hotspot/WhiteBox.java index feae1ed8e2f..2726d9ae97f 100644 --- a/test/lib/sun/hotspot/WhiteBox.java +++ b/test/lib/sun/hotspot/WhiteBox.java @@ -555,4 +555,7 @@ public native int validateCgroup(String procCgroups, // libc name public native String getLibcName(); + + // AI-Extension support + public native boolean isAIExtSupported(); }