Roland, thanks for looking into the fix!

You are right.
I moved VM_ENTRY_MARK to the beginning of the method [1].

Updated webrev in place.

Best regards,
Vladimir Ivanov

diff --git a/src/share/vm/ci/ciCallSite.cpp b/src/share/vm/ci/ciCallSite.cpp
--- a/src/share/vm/ci/ciCallSite.cpp
+++ b/src/share/vm/ci/ciCallSite.cpp
@@ -55,6 +55,8 @@
 // Return the target MethodHandle of this CallSite.
 ciKlass* ciCallSite::get_context() {
   assert(!is_constant_call_site(), "");
   oop call_site_oop = get_oop();
InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site_oop);
   if (ctxk == NULL) {
@@ -63,7 +65,6 @@
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();

On 4/15/15 1:16 PM, Roland Westrelin wrote:
Hi Vladimir,

In ciCallSite::get_context(), is it safe to manipulate a raw oop the way you do 
it (with 2 different oops). Can’t it be moved concurrently by the GC?


Best regards,
Vladimir Ivanov

On 4/1/15 11:56 PM, Vladimir Ivanov wrote:

HotSpot JITs inline very aggressively through CallSites. The
optimistically treat CallSite target as constant, but record a nmethod
dependency to invalidate the compiled code once CallSite target changes.

Right now, such dependencies have call site class as a context. This
context is too coarse and it leads to context pollution: if some
CallSite target changes, VM needs to enumerate all nmethods which
depends on call sites of such type.

As performance analysis in the bug report shows, it can sum to
significant amount of work.

While working on the fix, I investigated 3 approaches:
   (1) unique context per call site
   (2) use CallSite target class
   (3) use a class the CallSite instance is linked to

Considering call sites are ubiquitous (e.g. 10,000s on some octane
benchmarks), loading a dedicated class for every call site is an
overkill (even VM anonymous).

CallSite target class
(MethodHandle.form->LambdaForm.vmentry->MemberName.clazz->Class<?>) is
also not satisfactory, since it is a compiled LambdaForm VM anonymous
class, which is heavily shared. It gets context pollution down, but
still the overhead is quite high.

So, I decided to focus on (3) and ended up with a mixture of (2) & (3).

Comparing to other options, the complications of (3) are:
   - CallSite can stay unlinked (e.g. CallSite.dynamicInvoker()), so
there should be some default context VM can use

   - CallSite instances can be shared and it shouldn't keep the context
class from unloading;

It motivated a scheme where CallSite context is initialized lazily and
can change during lifetime. When CallSite is linked with an indy
instruction, it's context is initialized. Usually, JIT sees CallSite
instances with initialized context (since it reaches them through indy),
but if it's not the case and there's no context yet, JIT sets it to
"default context", which means "use target call site".

I introduced CallSite$DependencyContext, which represents a nmethod
dependency context and points (indirectly) to a Class<?> used as a context.

Context class is referenced through a phantom reference
(sun.misc.Cleaner to simplify cleanup). Though it's impossible to
extract referent using Reference.get(), VM can access it directly by
reading corresponding field. Unlike other types of references, phantom
references aren't cleared automatically. It allows VM to access context
class until cleanup is performed. And cleanup resets the context to
NULL, in addition to invalidating all relevant dependencies.

There are 3 context states a CallSite instance can be in:
   (1) NULL: no depedencies
   (2) DependencyContext.DEFAULT_CONTEXT: dependencies are stored in
call site target class
   (3) DependencyContext for some class: dependencies are stored on the
class DependencyContext instance points to

Every CallSite starts w/o a context (1) and then lazily gets one ((2) or
(3) depending on the situation).

State transitions:
   (1->3): When a CallSite w/o a context (1) is linked with some indy
call site, it's owner is recorded as a context (3).

   (1->2): When JIT needs to record a dependency on a target of a
CallSite w/o a context(1), it sets the context to DEFAULT_CONTEXT and
uses target class to store the dependency.

   (3->1): When context class becomes unreachable, a cleanup hook
invalidates all dependencies on that CallSite and resets the context to
NULL (1).

Only (3->1) requires dependency invalidation, because there are no
depedencies in (1) and (2->1) isn't performed.

(1->3) is done in Java code (CallSite.initContext) and (1->2) is
performed in VM (ciCallSite::get_context()). The updates are performed
by CAS, so there's no need in additional synchronization. Other
operations on VM side are volatile (to play well with Java code) and
performed with Compile_lock held (to avoid races between VM operations).

Some statistics:
   Box2D, latest jdk9-dev
     - CallSite instances: ~22000

     - invalidated nmethods due to CallSite target changes: ~60

     - checked call_site_target_value dependencies:
       - before the fix: ~1,600,000
       - after the fix:        ~600

   - dedicated test which excercises different state transitions
   - jdk/java/lang/invoke, hotspot/test/compiler/jsr292, nashorn


Best regards,
Vladimir Ivanov

mlvm-dev mailing list

Reply via email to