Projects
openEuler:24.03:SP1:Everything
openjdk-1.8.0
_service:tar_scm:8057967-CallSite-dependency-tr...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:8057967-CallSite-dependency-tracking-scales-devastat.patch of Package openjdk-1.8.0
From 1059c5d5f9d1e50607c726b619f39ac68954bda7 Mon Sep 17 00:00:00 2001 From: zhangyipeng <zhangyipeng7@huawei.com> Date: Mon, 15 Jan 2024 11:40:07 +0800 Subject: [PATCH] [Backport]8057967: CallSite dependency tracking scales devastatingly poorly --- hotspot/src/share/vm/ci/ciCallSite.cpp | 19 +++ hotspot/src/share/vm/ci/ciCallSite.hpp | 1 + hotspot/src/share/vm/classfile/javaClasses.cpp | 48 +++++- hotspot/src/share/vm/classfile/javaClasses.hpp | 9 +- hotspot/src/share/vm/classfile/vmSymbols.hpp | 4 +- hotspot/src/share/vm/code/dependencies.cpp | 27 ++-- hotspot/src/share/vm/code/dependencies.hpp | 7 +- hotspot/src/share/vm/memory/universe.cpp | 8 +- hotspot/src/share/vm/prims/methodHandles.cpp | 50 +++++- hotspot/src/share/vm/prims/methodHandles.hpp | 3 + .../compiler/jsr292/CallSiteDepContextTest.java | 179 +++++++++++++++++++++ .../share/classes/java/lang/invoke/CallSite.java | 55 ++++++- .../java/lang/invoke/MethodHandleNatives.java | 4 + 13 files changed, 385 insertions(+), 29 deletions(-) create mode 100644 hotspot/test/compiler/jsr292/CallSiteDepContextTest.java diff --git a/hotspot/src/share/vm/ci/ciCallSite.cpp b/hotspot/src/share/vm/ci/ciCallSite.cpp index 794042a79..f58346aea 100644 --- a/hotspot/src/share/vm/ci/ciCallSite.cpp +++ b/hotspot/src/share/vm/ci/ciCallSite.cpp @@ -49,6 +49,25 @@ ciMethodHandle* ciCallSite::get_target() const { } // ------------------------------------------------------------------ +// ciCallSite::get_context +// +// Return the target MethodHandle of this CallSite. +ciKlass* ciCallSite::get_context() { + assert(!is_constant_call_site(), ""); + + VM_ENTRY_MARK; + oop call_site_oop = get_oop(); + InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site_oop); + if (ctxk == NULL) { + // The call site doesn't have a context associated. Set it to the default context. + oop def_context_oop = java_lang_invoke_CallSite::default_context(); + java_lang_invoke_CallSite::set_context_cas(call_site_oop, def_context_oop, /*expected=*/NULL); + ctxk = MethodHandles::get_call_site_context(call_site_oop); + } + return (CURRENT_ENV->get_metadata(ctxk))->as_klass(); +} + +// ------------------------------------------------------------------ // ciCallSite::print // // Print debugging information about the CallSite. diff --git a/hotspot/src/share/vm/ci/ciCallSite.hpp b/hotspot/src/share/vm/ci/ciCallSite.hpp index 063f1e3a5..040e894d0 100644 --- a/hotspot/src/share/vm/ci/ciCallSite.hpp +++ b/hotspot/src/share/vm/ci/ciCallSite.hpp @@ -43,6 +43,7 @@ public: // Return the target MethodHandle of this CallSite. ciMethodHandle* get_target() const; + ciKlass* get_context(); void print(); }; diff --git a/hotspot/src/share/vm/classfile/javaClasses.cpp b/hotspot/src/share/vm/classfile/javaClasses.cpp index 9db88611b..fc4165b04 100644 --- a/hotspot/src/share/vm/classfile/javaClasses.cpp +++ b/hotspot/src/share/vm/classfile/javaClasses.cpp @@ -100,21 +100,22 @@ InjectedField* JavaClasses::get_injected(Symbol* class_name, int* field_count) { static bool find_field(InstanceKlass* ik, Symbol* name_symbol, Symbol* signature_symbol, fieldDescriptor* fd, - bool allow_super = false) { - if (allow_super) - return ik->find_field(name_symbol, signature_symbol, fd) != NULL; - else + bool is_static = false, bool allow_super = false) { + if (allow_super || is_static) { + return ik->find_field(name_symbol, signature_symbol, is_static, fd) != NULL; + } else { return ik->find_local_field(name_symbol, signature_symbol, fd); + } } // Helpful routine for computing field offsets at run time rather than hardcoding them static void compute_offset(int &dest_offset, Klass* klass_oop, Symbol* name_symbol, Symbol* signature_symbol, - bool allow_super = false) { + bool is_static = false, bool allow_super = false) { fieldDescriptor fd; InstanceKlass* ik = InstanceKlass::cast(klass_oop); - if (!find_field(ik, name_symbol, signature_symbol, &fd, allow_super)) { + if (!find_field(ik, name_symbol, signature_symbol, &fd, is_static, allow_super)) { ResourceMark rm; tty->print_cr("Invalid layout of %s at %s", ik->external_name(), name_symbol->as_C_string()); #ifndef PRODUCT @@ -3002,15 +3003,50 @@ int java_lang_invoke_MethodType::rtype_slot_count(oop mt) { // Support for java_lang_invoke_CallSite int java_lang_invoke_CallSite::_target_offset; +int java_lang_invoke_CallSite::_context_offset; +int java_lang_invoke_CallSite::_default_context_offset; void java_lang_invoke_CallSite::compute_offsets() { if (!EnableInvokeDynamic) return; Klass* k = SystemDictionary::CallSite_klass(); if (k != NULL) { compute_offset(_target_offset, k, vmSymbols::target_name(), vmSymbols::java_lang_invoke_MethodHandle_signature()); + compute_offset(_context_offset, k, vmSymbols::context_name(), vmSymbols::sun_misc_Cleaner_signature()); + compute_offset(_default_context_offset, k, + vmSymbols::DEFAULT_CONTEXT_name(), vmSymbols::sun_misc_Cleaner_signature(), + /*is_static=*/true, /*allow_super=*/false); } } +oop java_lang_invoke_CallSite::context_volatile(oop call_site) { + assert(java_lang_invoke_CallSite::is_instance(call_site), ""); + + oop dep_oop = call_site->obj_field_volatile(_context_offset); + return dep_oop; +} + +void java_lang_invoke_CallSite::set_context_volatile(oop call_site, oop context) { + assert(java_lang_invoke_CallSite::is_instance(call_site), ""); + call_site->obj_field_put_volatile(_context_offset, context); +} + +bool java_lang_invoke_CallSite::set_context_cas(oop call_site, oop context, oop expected) { + assert(java_lang_invoke_CallSite::is_instance(call_site), ""); + HeapWord* context_addr = call_site->obj_field_addr<HeapWord>(_context_offset); + oop res = oopDesc::atomic_compare_exchange_oop(context, context_addr, expected, true); + bool success = (res == expected); + if (success) { + update_barrier_set((void*)context_addr, context); + } + return success; +} + +oop java_lang_invoke_CallSite::default_context() { + InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::CallSite_klass()); + oop def_context_oop = ik->java_mirror()->obj_field(_default_context_offset); + assert(!oopDesc::is_null(def_context_oop), ""); + return def_context_oop; +} // Support for java_security_AccessControlContext diff --git a/hotspot/src/share/vm/classfile/javaClasses.hpp b/hotspot/src/share/vm/classfile/javaClasses.hpp index d6e288fb8..35934319d 100644 --- a/hotspot/src/share/vm/classfile/javaClasses.hpp +++ b/hotspot/src/share/vm/classfile/javaClasses.hpp @@ -1212,6 +1212,9 @@ class java_lang_invoke_CallSite: AllStatic { private: static int _target_offset; + static int _context_offset; + static int _default_context_offset; + static void compute_offsets(); @@ -1222,6 +1225,11 @@ public: static volatile oop target_volatile(oop site) { return oop((oopDesc *)(site->obj_field_volatile(_target_offset))); } static void set_target_volatile(oop site, oop target) { site->obj_field_put_volatile(_target_offset, target); } + static oop context_volatile(oop site); + static void set_context_volatile(oop site, oop context); + static bool set_context_cas (oop site, oop context, oop expected); + + static oop default_context(); // Testers static bool is_subclass(Klass* klass) { @@ -1235,7 +1243,6 @@ public: static int target_offset_in_bytes() { return _target_offset; } }; - // Interface to java.security.AccessControlContext objects class java_security_AccessControlContext: AllStatic { diff --git a/hotspot/src/share/vm/classfile/vmSymbols.hpp b/hotspot/src/share/vm/classfile/vmSymbols.hpp index 494fd9bdf..f92b709ed 100644 --- a/hotspot/src/share/vm/classfile/vmSymbols.hpp +++ b/hotspot/src/share/vm/classfile/vmSymbols.hpp @@ -301,6 +301,7 @@ template(setTargetNormal_name, "setTargetNormal") \ template(setTargetVolatile_name, "setTargetVolatile") \ template(setTarget_signature, "(Ljava/lang/invoke/MethodHandle;)V") \ + template(DEFAULT_CONTEXT_name, "DEFAULT_CONTEXT") \ NOT_LP64( do_alias(intptr_signature, int_signature) ) \ LP64_ONLY( do_alias(intptr_signature, long_signature) ) \ \ @@ -517,6 +518,7 @@ template(string_signature, "Ljava/lang/String;") \ template(reference_signature, "Ljava/lang/ref/Reference;") \ template(referencequeue_signature, "Ljava/lang/ref/ReferenceQueue;") \ + template(sun_misc_Cleaner_signature, "Lsun/misc/Cleaner;") \ template(executable_signature, "Ljava/lang/reflect/Executable;") \ template(concurrenthashmap_signature, "Ljava/util/concurrent/ConcurrentHashMap;") \ template(String_StringBuilder_signature, "(Ljava/lang/String;)Ljava/lang/StringBuilder;") \ @@ -570,7 +572,7 @@ template(createGarbageCollectorMBean_signature, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/management/GarbageCollectorMBean;") \ template(trigger_name, "trigger") \ template(clear_name, "clear") \ - template(trigger_method_signature, "(ILjava/lang/management/MemoryUsage;)V") \ + template(trigger_method_signature, "(ILjava/lang/management/MemoryUsage;)V") \ template(startAgent_name, "startAgent") \ template(startRemoteAgent_name, "startRemoteManagementAgent") \ template(startLocalAgent_name, "startLocalManagementAgent") \ diff --git a/hotspot/src/share/vm/code/dependencies.cpp b/hotspot/src/share/vm/code/dependencies.cpp index d1fe08b54..decbce8be 100644 --- a/hotspot/src/share/vm/code/dependencies.cpp +++ b/hotspot/src/share/vm/code/dependencies.cpp @@ -123,8 +123,9 @@ void Dependencies::assert_has_no_finalizable_subclasses(ciKlass* ctxk) { } void Dependencies::assert_call_site_target_value(ciCallSite* call_site, ciMethodHandle* method_handle) { - check_ctxk(call_site->klass()); - assert_common_2(call_site_target_value, call_site, method_handle); + ciKlass* ctxk = call_site->get_context(); + check_ctxk(ctxk); + assert_common_3(call_site_target_value, ctxk, call_site, method_handle); } // Helper function. If we are adding a new dep. under ctxk2, @@ -396,7 +397,7 @@ int Dependencies::_dep_args[TYPE_LIMIT] = { 3, // unique_concrete_methods_2 ctxk, m1, m2 2, // unique_implementor ctxk, implementor 1, // no_finalizable_subclasses ctxk - 2 // call_site_target_value call_site, method_handle + 3 // call_site_target_value ctxk, call_site, method_handle }; const char* Dependencies::dep_name(Dependencies::DepType dept) { @@ -598,7 +599,7 @@ void Dependencies::DepStream::log_dependency(Klass* witness) { const int nargs = argument_count(); GrowableArray<DepArgument>* args = new GrowableArray<DepArgument>(nargs); for (int j = 0; j < nargs; j++) { - if (type() == call_site_target_value) { + if (is_oop_argument(j)) { args->push(argument_oop(j)); } else { args->push(argument(j)); @@ -726,7 +727,7 @@ Klass* Dependencies::DepStream::context_type() { } // Some dependencies are using the klass of the first object - // argument as implicit context type (e.g. call_site_target_value). + // argument as implicit context type. { int ctxkj = dep_implicit_context_arg(type()); if (ctxkj >= 0) { @@ -1647,9 +1648,16 @@ Klass* Dependencies::check_has_no_finalizable_subclasses(Klass* ctxk, KlassDepCh return find_finalizable_subclass(search_at); } -Klass* Dependencies::check_call_site_target_value(oop call_site, oop method_handle, CallSiteDepChange* changes) { - assert(call_site ->is_a(SystemDictionary::CallSite_klass()), "sanity"); - assert(method_handle->is_a(SystemDictionary::MethodHandle_klass()), "sanity"); +Klass* Dependencies::check_call_site_target_value(Klass* recorded_ctxk, oop call_site, oop method_handle, CallSiteDepChange* changes) { + assert(call_site->is_a(SystemDictionary::CallSite_klass()), "sanity"); + assert(!oopDesc::is_null(method_handle), "sanity"); + + Klass* call_site_ctxk = MethodHandles::get_call_site_context(call_site); + assert(!Klass::is_null(call_site_ctxk), "call site context should be initialized already"); + if (recorded_ctxk != call_site_ctxk) { + // Stale context + return recorded_ctxk; + } if (changes == NULL) { // Validate all CallSites if (java_lang_invoke_CallSite::target(call_site) != method_handle) @@ -1664,7 +1672,6 @@ Klass* Dependencies::check_call_site_target_value(oop call_site, oop method_hand return NULL; // assertion still valid } - void Dependencies::DepStream::trace_and_log_witness(Klass* witness) { if (witness != NULL) { if (TraceDependencies) { @@ -1728,7 +1735,7 @@ Klass* Dependencies::DepStream::check_call_site_dependency(CallSiteDepChange* ch Klass* witness = NULL; switch (type()) { case call_site_target_value: - witness = check_call_site_target_value(argument_oop(0), argument_oop(1), changes); + witness = check_call_site_target_value(context_type(), argument_oop(1), argument_oop(2), changes); break; default: witness = NULL; diff --git a/hotspot/src/share/vm/code/dependencies.hpp b/hotspot/src/share/vm/code/dependencies.hpp index 0392d4e3d..da2201c3f 100644 --- a/hotspot/src/share/vm/code/dependencies.hpp +++ b/hotspot/src/share/vm/code/dependencies.hpp @@ -176,7 +176,7 @@ class Dependencies: public ResourceObj { klass_types = all_types & ~non_klass_types, non_ctxk_types = (1 << evol_method), - implicit_ctxk_types = (1 << call_site_target_value), + implicit_ctxk_types = 0, explicit_ctxk_types = all_types & ~(non_ctxk_types | implicit_ctxk_types), max_arg_count = 3, // current maximum number of arguments (incl. ctxk) @@ -340,7 +340,7 @@ class Dependencies: public ResourceObj { static Klass* check_exclusive_concrete_methods(Klass* ctxk, Method* m1, Method* m2, KlassDepChange* changes = NULL); static Klass* check_has_no_finalizable_subclasses(Klass* ctxk, KlassDepChange* changes = NULL); - static Klass* check_call_site_target_value(oop call_site, oop method_handle, CallSiteDepChange* changes = NULL); + static Klass* check_call_site_target_value(Klass* recorded_ctxk, oop call_site, oop method_handle, CallSiteDepChange* changes = NULL); // A returned Klass* is NULL if the dependency assertion is still // valid. A non-NULL Klass* is a 'witness' to the assertion // failure, a point in the class hierarchy where the assertion has @@ -506,6 +506,7 @@ class Dependencies: public ResourceObj { bool next(); DepType type() { return _type; } + bool is_oop_argument(int i) { return type() == call_site_target_value && i > 0; } int argument_count() { return dep_args(type()); } int argument_index(int i) { assert(0 <= i && i < argument_count(), "oob"); return _xi[i]; } @@ -664,7 +665,7 @@ class CallSiteDepChange : public DepChange { _method_handle(method_handle) { assert(_call_site() ->is_a(SystemDictionary::CallSite_klass()), "must be"); - assert(_method_handle()->is_a(SystemDictionary::MethodHandle_klass()), "must be"); + assert(_method_handle.is_null() || _method_handle()->is_a(SystemDictionary::MethodHandle_klass()), "must be"); } // What kind of DepChange is this? diff --git a/hotspot/src/share/vm/memory/universe.cpp b/hotspot/src/share/vm/memory/universe.cpp index 23433d187..7028d378e 100644 --- a/hotspot/src/share/vm/memory/universe.cpp +++ b/hotspot/src/share/vm/memory/universe.cpp @@ -56,6 +56,7 @@ #include "oops/oop.inline.hpp" #include "oops/typeArrayKlass.hpp" #include "prims/jvmtiRedefineClassesTrace.hpp" +#include "prims/methodHandles.hpp" #include "runtime/arguments.hpp" #include "runtime/deoptimization.hpp" #include "runtime/fprofiler.hpp" @@ -1233,8 +1234,11 @@ void Universe::flush_dependents_on(Handle call_site, Handle method_handle) { int marked = 0; { MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); - InstanceKlass* call_site_klass = InstanceKlass::cast(call_site->klass()); - marked = call_site_klass->mark_dependent_nmethods(changes); + InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site()); + if (ctxk == NULL) { + return; // No dependencies to invalidate yet. + } + marked = ctxk->mark_dependent_nmethods(changes); } if (marked > 0) { // At least one nmethod has been marked for deoptimization diff --git a/hotspot/src/share/vm/prims/methodHandles.cpp b/hotspot/src/share/vm/prims/methodHandles.cpp index 29598d500..c1cbabec2 100644 --- a/hotspot/src/share/vm/prims/methodHandles.cpp +++ b/hotspot/src/share/vm/prims/methodHandles.cpp @@ -946,6 +946,24 @@ int MethodHandles::find_MemberNames(KlassHandle k, return rfill + overflow; } +// Get context class for a CallSite instance: either extract existing context or use default one. +InstanceKlass* MethodHandles::get_call_site_context(oop call_site) { + // In order to extract a context the following traversal is performed: + // CallSite.context => Cleaner.referent => Class._klass => Klass + assert(java_lang_invoke_CallSite::is_instance(call_site), ""); + oop context_oop = java_lang_invoke_CallSite::context_volatile(call_site); + if (oopDesc::is_null(context_oop)) { + return NULL; // The context hasn't been initialized yet. + } + oop context_class_oop = java_lang_ref_Reference::referent(context_oop); + if (oopDesc::is_null(context_class_oop)) { + // The context reference was cleared by GC, so current dependency context + // isn't usable anymore. Context should be fetched from CallSite again. + return NULL; + } + return InstanceKlass::cast(java_lang_Class::as_Klass(context_class_oop)); +} + //------------------------------------------------------------------------------ // MemberNameTable // @@ -1305,7 +1323,7 @@ JVM_END JVM_ENTRY(void, MHN_setCallSiteTargetNormal(JNIEnv* env, jobject igcls, jobject call_site_jh, jobject target_jh)) { Handle call_site(THREAD, JNIHandles::resolve_non_null(call_site_jh)); - Handle target (THREAD, JNIHandles::resolve(target_jh)); + Handle target (THREAD, JNIHandles::resolve_non_null(target_jh)); { // Walk all nmethods depending on this call site. MutexLocker mu(Compile_lock, thread); @@ -1317,7 +1335,7 @@ JVM_END JVM_ENTRY(void, MHN_setCallSiteTargetVolatile(JNIEnv* env, jobject igcls, jobject call_site_jh, jobject target_jh)) { Handle call_site(THREAD, JNIHandles::resolve_non_null(call_site_jh)); - Handle target (THREAD, JNIHandles::resolve(target_jh)); + Handle target (THREAD, JNIHandles::resolve_non_null(target_jh)); { // Walk all nmethods depending on this call site. MutexLocker mu(Compile_lock, thread); @@ -1327,6 +1345,33 @@ JVM_ENTRY(void, MHN_setCallSiteTargetVolatile(JNIEnv* env, jobject igcls, jobjec } JVM_END +JVM_ENTRY(void, MHN_invalidateDependentNMethods(JNIEnv* env, jobject igcls, jobject call_site_jh)) { + Handle call_site(THREAD, JNIHandles::resolve_non_null(call_site_jh)); + { + // Walk all nmethods depending on this call site. + MutexLocker mu1(Compile_lock, thread); + + CallSiteDepChange changes(call_site(), Handle()); + + InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site()); + if (ctxk == NULL) { + return; // No dependencies to invalidate yet. + } + int marked = 0; + { + MutexLockerEx mu2(CodeCache_lock, Mutex::_no_safepoint_check_flag); + marked = ctxk->mark_dependent_nmethods(changes); + } + java_lang_invoke_CallSite::set_context_volatile(call_site(), NULL); // Reset call site to initial state + if (marked > 0) { + // At least one nmethod has been marked for deoptimization + VM_Deoptimize op; + VMThread::execute(&op); + } + } +} +JVM_END + /** * Throws a java/lang/UnsupportedOperationException unconditionally. * This is required by the specification of MethodHandle.invoke if @@ -1381,6 +1426,7 @@ static JNINativeMethod MHN_methods[] = { {CC "objectFieldOffset", CC "(" MEM ")J", FN_PTR(MHN_objectFieldOffset)}, {CC "setCallSiteTargetNormal", CC "(" CS "" MH ")V", FN_PTR(MHN_setCallSiteTargetNormal)}, {CC "setCallSiteTargetVolatile", CC "(" CS "" MH ")V", FN_PTR(MHN_setCallSiteTargetVolatile)}, + {CC"invalidateDependentNMethods", CC"("CS")V", FN_PTR(MHN_invalidateDependentNMethods)}, {CC "staticFieldOffset", CC "(" MEM ")J", FN_PTR(MHN_staticFieldOffset)}, {CC "staticFieldBase", CC "(" MEM ")" OBJ, FN_PTR(MHN_staticFieldBase)}, {CC "getMemberVMInfo", CC "(" MEM ")" OBJ, FN_PTR(MHN_getMemberVMInfo)} diff --git a/hotspot/src/share/vm/prims/methodHandles.hpp b/hotspot/src/share/vm/prims/methodHandles.hpp index db6e06180..4b6af60df 100644 --- a/hotspot/src/share/vm/prims/methodHandles.hpp +++ b/hotspot/src/share/vm/prims/methodHandles.hpp @@ -68,6 +68,9 @@ class MethodHandles: AllStatic { // bit values for suppress argument to expand_MemberName: enum { _suppress_defc = 1, _suppress_name = 2, _suppress_type = 4 }; + // CallSite support + static InstanceKlass* get_call_site_context(oop call_site); + // Generate MethodHandles adapters. static void generate_adapters(); diff --git a/hotspot/test/compiler/jsr292/CallSiteDepContextTest.java b/hotspot/test/compiler/jsr292/CallSiteDepContextTest.java new file mode 100644 index 000000000..11e46ed03 --- /dev/null +++ b/hotspot/test/compiler/jsr292/CallSiteDepContextTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 + * @bug 8057967 + * @run main/bootclasspath -Xbatch java.lang.invoke.CallSiteDepContextTest + */ +package java.lang.invoke; + +import java.lang.ref.*; +import jdk.internal.org.objectweb.asm.*; +import sun.misc.Unsafe; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +public class CallSiteDepContextTest { + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + static final MethodHandles.Lookup LOOKUP = MethodHandles.Lookup.IMPL_LOOKUP; + static final String CLASS_NAME = "java/lang/invoke/Test"; + static final String METHOD_NAME = "m"; + static final MethodType TYPE = MethodType.methodType(int.class); + + static MutableCallSite mcs; + static MethodHandle bsmMH; + + static { + try { + bsmMH = LOOKUP.findStatic( + CallSiteDepContextTest.class, "bootstrap", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class)); + } catch(Throwable e) { + throw new InternalError(e); + } + } + + public static CallSite bootstrap(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType) { + return mcs; + } + + static class T { + static int f1() { return 1; } + static int f2() { return 2; } + } + + static byte[] getClassFile(String suffix) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + MethodVisitor mv; + cw.visit(52, ACC_PUBLIC | ACC_SUPER, CLASS_NAME + suffix, null, "java/lang/Object", null); + { + mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, METHOD_NAME, TYPE.toMethodDescriptorString(), null, null); + mv.visitCode(); + Handle bsm = new Handle(H_INVOKESTATIC, + "java/lang/invoke/CallSiteDepContextTest", "bootstrap", + bsmMH.type().toMethodDescriptorString()); + mv.visitInvokeDynamicInsn("methodName", TYPE.toMethodDescriptorString(), bsm); + mv.visitInsn(IRETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + cw.visitEnd(); + return cw.toByteArray(); + } + + private static void execute(int expected, MethodHandle... mhs) throws Throwable { + for (int i = 0; i < 20_000; i++) { + for (MethodHandle mh : mhs) { + int r = (int) mh.invokeExact(); + if (r != expected) { + throw new Error(r + " != " + expected); + } + } + } + } + + public static void testSharedCallSite() throws Throwable { + Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_1"), null); + Class<?> cls2 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_2"), null); + + MethodHandle[] mhs = new MethodHandle[] { + LOOKUP.findStatic(cls1, METHOD_NAME, TYPE), + LOOKUP.findStatic(cls2, METHOD_NAME, TYPE) + }; + + mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE)); + execute(1, mhs); + mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE)); + execute(2, mhs); + } + + public static void testNonBoundCallSite() throws Throwable { + mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE)); + + // mcs.context == null + MethodHandle mh = mcs.dynamicInvoker(); + execute(1, mh); + + // mcs.context == cls1 + Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("NonBound_1"), null); + MethodHandle mh1 = LOOKUP.findStatic(cls1, METHOD_NAME, TYPE); + + execute(1, mh1); + + mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE)); + + execute(2, mh, mh1); + } + + static ReferenceQueue rq = new ReferenceQueue(); + static PhantomReference ref; + + public static void testGC() throws Throwable { + mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE)); + + Class<?>[] cls = new Class[] { + UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_1"), null), + UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_2"), null), + }; + + MethodHandle[] mhs = new MethodHandle[] { + LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE), + LOOKUP.findStatic(cls[1], METHOD_NAME, TYPE), + }; + + // mcs.context == cls[0] + int r = (int) mhs[0].invokeExact(); + + execute(1, mhs); + + ref = new PhantomReference<>(cls[0], rq); + cls[0] = UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_3"), null); + mhs[0] = LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE); + + do { + System.gc(); + try { + Reference ref1 = rq.remove(1000); + if (ref1 == ref) { + ref1.clear(); + System.gc(); // Ensure that the stale context is cleared + break; + } + } catch(InterruptedException e) { /* ignore */ } + } while (true); + + execute(1, mhs); + mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE)); + execute(2, mhs); + } + + public static void main(String[] args) throws Throwable { + testSharedCallSite(); + testNonBoundCallSite(); + testGC(); + System.out.println("TEST PASSED"); + } +} diff --git a/jdk/src/share/classes/java/lang/invoke/CallSite.java b/jdk/src/share/classes/java/lang/invoke/CallSite.java index 10ac1c071..11e452b96 100644 --- a/jdk/src/share/classes/java/lang/invoke/CallSite.java +++ b/jdk/src/share/classes/java/lang/invoke/CallSite.java @@ -25,9 +25,10 @@ package java.lang.invoke; -import sun.invoke.empty.Empty; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; +import java.lang.reflect.Field; +import sun.misc.Cleaner; /** * A {@code CallSite} is a holder for a variable {@link MethodHandle}, @@ -136,6 +137,50 @@ public class CallSite { } /** + * {@code CallSite} dependency context. + * VM uses context class to store nmethod dependencies on the call site target. + * Can be in 2 states: (a) null; or (b) {@code Cleaner} instance pointing to some Class instance. + * Lazily initialized when CallSite instance is linked to some indy call site or VM needs + * it to store dependencies. As a corollary, "null" context means there are no dependencies + * registered yet. {@code Cleaner} is used in 2 roles: + * (a) context class access for VM; + * (b) stale context class cleanup. + * {@code Cleaner} holds the context class until cleanup action is finished (see {@code PhantomReference}). + * Though it's impossible to get the context class using {@code Reference.get()}, VM extracts it directly + * from {@code Reference.referent} field. + */ + private volatile Cleaner context = null; + + /** + * Default context. + * VM uses it to initialize non-linked CallSite context. + */ + private static class DefaultContext {} + private static final Cleaner DEFAULT_CONTEXT = makeContext(DefaultContext.class, null); + + private static Cleaner makeContext(Class<?> referent, final CallSite holder) { + return Cleaner.create(referent, + new Runnable() { + @Override public void run() { + MethodHandleNatives.invalidateDependentNMethods(holder); + } + }); + } + + /** Initialize context class used for nmethod dependency tracking */ + /*package-private*/ + void initContext(Class<?> newContext) { + // If there are concurrent actions, exactly one succeeds. + if (context == null) { + UNSAFE.compareAndSwapObject(this, CONTEXT_OFFSET, /*expected=*/null, makeContext(newContext, this)); + // No need to care about failed CAS attempt. + // Since initContext is called from indy call site linkage in newContext class, there's no risk + // that the context class becomes dead while corresponding context cleaner is alive (causing cleanup + // action in the wrong context). + } + } + + /** * Returns the type of this call site's target. * Although targets may change, any call site's type is permanent, and can never change to an unequal type. * The {@code setTarget} method enforces this invariant by refusing any new target that does @@ -246,11 +291,13 @@ public class CallSite { } // unsafe stuff: - private static final long TARGET_OFFSET; + private static final long TARGET_OFFSET; + private static final long CONTEXT_OFFSET; static { try { - TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target")); - } catch (Exception ex) { throw new Error(ex); } + TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target")); + CONTEXT_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("context")); + } catch (Exception ex) { throw newInternalError(ex); } } /*package-private*/ diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java index ecc146078..9a1343c50 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -71,6 +71,9 @@ class MethodHandleNatives { static native void setCallSiteTargetNormal(CallSite site, MethodHandle target); static native void setCallSiteTargetVolatile(CallSite site, MethodHandle target); + /** Invalidate CallSite context: clean up dependent nmethods and reset call site context to initial state (null). */ + static native void invalidateDependentNMethods(CallSite site); + private static native void registerNatives(); static { registerNatives(); @@ -314,6 +317,7 @@ class MethodHandleNatives { return Invokers.linkToTargetMethod(type); } else { appendixResult[0] = callSite; + callSite.initContext(caller); return Invokers.linkToCallSiteMethod(type); } } -- 2.12.3
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2