8305252: make_method_handle_intrinsic may call java code under a lock

Reviewed-by: dholmes, matsaave, iklam
This commit is contained in:
Coleen Phillimore 2023-04-24 12:55:15 +00:00
parent f239695b56
commit 7400aff3b8
3 changed files with 57 additions and 30 deletions

View File

@ -1583,11 +1583,13 @@ void SystemDictionary::methods_do(void f(Method*)) {
}
auto doit = [&] (InvokeMethodKey key, Method* method) {
f(method);
if (method != nullptr) {
f(method);
}
};
{
MutexLocker ml(InvokeMethodTable_lock);
MutexLocker ml(InvokeMethodIntrinsicTable_lock);
_invoke_method_intrinsic_table.iterate_all(doit);
}
@ -1939,40 +1941,62 @@ Method* SystemDictionary::find_method_handle_intrinsic(vmIntrinsicID iid,
iid != vmIntrinsics::_invokeGeneric,
"must be a known MH intrinsic iid=%d: %s", iid_as_int, vmIntrinsics::name_at(iid));
InvokeMethodKey key(signature, iid_as_int);
Method** met = nullptr;
// We only want one entry in the table for this (signature/id, method) pair but the code
// to create the intrinsic method needs to be outside the lock.
// The first thread claims the entry by adding the key and the other threads wait, until the
// Method has been added as the value.
{
MutexLocker ml(THREAD, InvokeMethodTable_lock);
InvokeMethodKey key(signature, iid_as_int);
Method** met = _invoke_method_intrinsic_table.get(key);
if (met != nullptr) {
return *met;
MonitorLocker ml(THREAD, InvokeMethodIntrinsicTable_lock);
while (met == nullptr) {
bool created;
met = _invoke_method_intrinsic_table.put_if_absent(key, &created);
if (met != nullptr && *met != nullptr) {
return *met;
} else if (!created) {
// Second thread waits for first to actually create the entry and returns
// it after notify. Loop until method return is non-null.
ml.wait();
}
}
}
bool throw_error = false;
// This function could get an OOM but it is safe to call inside of a lock because
// throwing OutOfMemoryError doesn't call Java code.
methodHandle m = Method::make_method_handle_intrinsic(iid, signature, CHECK_NULL);
if (!Arguments::is_interpreter_only() || iid == vmIntrinsics::_linkToNative) {
// Generate a compiled form of the MH intrinsic
// linkToNative doesn't have interpreter-specific implementation, so always has to go through compiled version.
AdapterHandlerLibrary::create_native_wrapper(m);
// Check if have the compiled code.
throw_error = (!m->has_compiled_code());
}
methodHandle m = Method::make_method_handle_intrinsic(iid, signature, THREAD);
bool throw_error = HAS_PENDING_EXCEPTION;
if (!throw_error && (!Arguments::is_interpreter_only() || iid == vmIntrinsics::_linkToNative)) {
// Generate a compiled form of the MH intrinsic
// linkToNative doesn't have interpreter-specific implementation, so always has to go through compiled version.
AdapterHandlerLibrary::create_native_wrapper(m);
// Check if have the compiled code.
throw_error = (!m->has_compiled_code());
}
if (!throw_error) {
{
MonitorLocker ml(THREAD, InvokeMethodIntrinsicTable_lock);
if (throw_error) {
// Remove the entry and let another thread try, or get the same exception.
bool removed = _invoke_method_intrinsic_table.remove(key);
assert(removed, "must be the owner");
ml.notify_all();
} else {
signature->make_permanent(); // The signature is never unloaded.
bool created = _invoke_method_intrinsic_table.put(key, m());
assert(created, "must be since we still hold the lock");
assert(Arguments::is_interpreter_only() || (m->has_compiled_code() &&
m->code()->entry_point() == m->from_compiled_entry()),
"MH intrinsic invariant");
*met = m(); // insert the element
ml.notify_all();
return m();
}
}
// Throw error outside of the lock.
THROW_MSG_NULL(vmSymbols::java_lang_VirtualMachineError(),
"Out of space in CodeCache for method handle intrinsic");
// Throw VirtualMachineError or the pending exception in the JavaThread
if (throw_error && !HAS_PENDING_EXCEPTION) {
THROW_MSG_NULL(vmSymbols::java_lang_VirtualMachineError(),
"Out of space in CodeCache for method handle intrinsic");
}
return nullptr;
}
// Helper for unpacking the return value from linkMethod and linkCallSite.
@ -2115,7 +2139,7 @@ Handle SystemDictionary::find_method_handle_type(Symbol* signature,
Handle empty;
OopHandle* o;
{
MutexLocker ml(THREAD, InvokeMethodTable_lock);
MutexLocker ml(THREAD, InvokeMethodTypeTable_lock);
o = _invoke_method_type_table.get(signature);
}
@ -2184,7 +2208,7 @@ Handle SystemDictionary::find_method_handle_type(Symbol* signature,
if (can_be_cached) {
// We can cache this MethodType inside the JVM.
MutexLocker ml(THREAD, InvokeMethodTable_lock);
MutexLocker ml(THREAD, InvokeMethodTypeTable_lock);
bool created = false;
assert(method_type != nullptr, "unexpected null");
OopHandle* h = _invoke_method_type_table.get(signature);

View File

@ -39,7 +39,8 @@
Mutex* Patching_lock = nullptr;
Mutex* CompiledMethod_lock = nullptr;
Monitor* SystemDictionary_lock = nullptr;
Mutex* InvokeMethodTable_lock = nullptr;
Mutex* InvokeMethodTypeTable_lock = nullptr;
Monitor* InvokeMethodIntrinsicTable_lock = nullptr;
Mutex* SharedDictionary_lock = nullptr;
Monitor* ClassInitError_lock = nullptr;
Mutex* Module_lock = nullptr;
@ -254,7 +255,9 @@ void mutex_init() {
}
MUTEX_DEFN(JmethodIdCreation_lock , PaddedMutex , nosafepoint-2); // used for creating jmethodIDs.
MUTEX_DEFN(InvokeMethodTable_lock , PaddedMutex , safepoint);
MUTEX_DEFN(InvokeMethodTypeTable_lock , PaddedMutex , safepoint);
MUTEX_DEFN(InvokeMethodIntrinsicTable_lock , PaddedMonitor, safepoint);
MUTEX_DEFN(AdapterHandlerLibrary_lock , PaddedMutex , safepoint);
MUTEX_DEFN(SharedDictionary_lock , PaddedMutex , safepoint);
MUTEX_DEFN(VMStatistic_lock , PaddedMutex , safepoint);
MUTEX_DEFN(SignatureHandlerLibrary_lock , PaddedMutex , safepoint);
@ -344,7 +347,6 @@ void mutex_init() {
MUTEX_DEFL(Threads_lock , PaddedMonitor, CompileThread_lock, true);
MUTEX_DEFL(Compile_lock , PaddedMutex , MethodCompileQueue_lock);
MUTEX_DEFL(AdapterHandlerLibrary_lock , PaddedMutex , InvokeMethodTable_lock);
MUTEX_DEFL(Heap_lock , PaddedMonitor, AdapterHandlerLibrary_lock);
MUTEX_DEFL(PerfDataMemAlloc_lock , PaddedMutex , Heap_lock);

View File

@ -34,7 +34,8 @@
extern Mutex* Patching_lock; // a lock used to guard code patching of compiled code
extern Mutex* CompiledMethod_lock; // a lock used to guard a compiled method and OSR queues
extern Monitor* SystemDictionary_lock; // a lock on the system dictionary
extern Mutex* InvokeMethodTable_lock;
extern Mutex* InvokeMethodTypeTable_lock;
extern Monitor* InvokeMethodIntrinsicTable_lock;
extern Mutex* SharedDictionary_lock; // a lock on the CDS shared dictionary
extern Monitor* ClassInitError_lock; // a lock on the class initialization error table
extern Mutex* Module_lock; // a lock on module and package related data structures