Small static method marked not entrant, inlining reversed?

Charles Oliver Nutter headius at headius.com
Wed Sep 8 00:08:50 PDT 2010


Well, this is a frustrating discovery. Hotspot seems to have a lot of
trouble with invokeinterface versus casting to a concrete type and
using invokevirtual.

Modifying isGenerationEqual to do this instead seems to avoid the
deoptimization:

    public static boolean isGenerationEqual(IRubyObject object, int
generation) {
        return ((RubyBasicObject)object).getMetaClass().getCacheToken()
== generation;
    }

There may be a future where an IRubyObject enters JRuby and does not
extend RubyBasicObject, but for now this cast should succeed every
time. But the same goes for the invokeinterface path, where both
RubyObject and RubyFixnum inherit the same getMetaClass implementation
from RubyBasicObject. Why is Hotspot able to cope with the
cast+invokevirtual when it can't cope with invokeinterface always
resolving to the same method?

- Charlie

On Wed, Sep 8, 2010 at 7:03 AM, Charles Oliver Nutter
<headius at headius.com> wrote:
> Ok, I have some additional evidence.
>
> The __file__ method that contains the jitted code body is jitted by
> hotspot initially and things I expected to inline inline correctly.
>
> 31    ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__
> (442 bytes)
>
> Later another recompilation forces __file__ to be recompiled...it's
> around the 50th iteration of the benchmark, at which point all the
> benchmark library's code is jitted by JRuby.
>
> 31 make_not_entrant
>
> Immediately after this, it appears that calls to isGenerationEqual are
> detected to be bimorphic, and so hotspot considers recompiling them as
> well.
>
> 25 uncommon trap bimorphic maybe_recompile
>  @1 org/jruby/javasupport/util/RuntimeHelpers isGenerationEqual
> (Lorg/jruby/runtime/builtin/IRubyObject;I)Z
>
> A type profile inside the isGenerationEqual call site looks like this
> in both the before and after case
>
>    @ 62 org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual (19 bytes)
>      @ 1 org.jruby.runtime.builtin.IRubyObject::getMetaClass (0 bytes)
>       type profile org/jruby/runtime/builtin/IRubyObject ->
> org/jruby/RubyFixnum (71%)
>
> ...but it does appear to inline according to this output :( All
> occurrences of isGenerationEqual in the PrintInlining output are
> inlined. So why do I see CALLs in the assembly?
>
> More information: The recursive calls to the method appear to never
> inline, probably because the method body is too large based on this
> logging output. It's not ideal, since in Ruby terms this is a pretty
> small method...but I guess I'm stuck here:
>
>    @ 115 ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__
> hot method too big
>
> I remember tweaking various flags and getting "fib" to inline multiple
> levels deep, but I generally had to bump up several flags
> (MaxInlineSize, InlineSmallCode, MaxInlineLevel, and even a "max node
> count" flag I dug out of the Hotspot sources).
>
> Here's part of the PrintAssembly showing the calls to isGenerationEqual:
>
> (type checks to see which of the two bimorphic targets it's seeing)
>  0x028614bd: cmp       ecx, 'org/jruby/RubyFixnum'
>                                        ;   {oop('org/jruby/RubyFixnum')}
>  0x028614c3: jz        0x028614da
>  0x028614c5: cmp       ecx, 'org/jruby/RubyObject'
>                                        ;   {oop('org/jruby/RubyObject')}
>  0x028614cb: jnz       0x02861b55
>
> (the RubyFixnum branch with all of isGenerationEqual and its component
> calls inlined)
>  0x028614da: mov       ebp, [ebx+0xC]  ;*synchronization entry
>                                        ; -
> org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at -1 (line
> 1863)
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
> (line 4)
>  0x028614dd: mov       ebx, [ebp+0x18]  ; implicit exception: dispatches to
> 0x028620ed
>  0x028614e0: cmp       ebx, 0x000001f3
>  0x028614e6: jz        0x02861b11      ;*if_icmpne
>                                        ; -
> org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at 10 (line
> 1863)
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
> (line 4)
>
> (subsequent casting to RubyFixnum and direct invocation of op_lt, the
> Java implementation of Fixnum#<)
>  0x028614ec: cmp       ecx, 'org/jruby/RubyFixnum'
>                                        ;   {oop('org/jruby/RubyFixnum')}
>  0x028614f2: jnz       0x02862081      ;*checkcast
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 19
> (line 4)
>  0x028614f8: mov       ebx, [edx+0x28]  ;*getfield runtime
>                                        ; -
> org.jruby.runtime.ThreadContext::getRuntime at 1 (line 147)
>                                        ; -
> org.jruby.RubyFixnum::op_lt at 1 (line 888)
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 26
> (line 4)
>                                        ; implicit exception:
> dispatches to 0x028620fd
>
> And the same sequence in the second compilation of __file__:
>
> (Perhaps the problem here is actually getMetaClass()? It shows as
> inlining based on the type profile information in LogCompilation, so
> why wouldn't it be inlined here? There's a single, final
> implementation on RubyBasicObject that both RubyObject and RubyFixnum
> share.)
>  0x0286e4f6: mov       ecx, [esp+0x44]
>  0x0286e4fa: mov       eax, -1         ;   {oop(NULL)}
>  0x0286e4ff: call      0x0282d2a0      ; OopMap{[64]=Oop [68]=Oop [16]=Oop
> [24]=Oop off=36}
>                                        ;*invokeinterface getMetaClass
>                                        ; -
> org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at 1 (line
> 1863)
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
> (line 4)
>                                        ;   {virtual_call}
>  0x0286e504: mov       ebx, [eax+0x18]  ; implicit exception: dispatches to
> 0x0286efc9
>  0x0286e507: cmp       ebx, 0x000001f3
>  0x0286e50d: jz        0x0286ea7f      ;*if_icmpne
>                                        ; -
> org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at 10 (line
> 1863)
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
> (line 4)
>  0x0286e513: mov       eax, [esp+0x44]
>  0x0286e517: mov       ebx, [eax+0x4]  ; implicit exception: dispatches to 0x0286efd9
>  0x0286e51a: mov       [esp+0x44], ebx
>  0x0286e51e: cmp       ebx, 'org/jruby/RubyFixnum'
>                                        ;   {oop('org/jruby/RubyFixnum')}
>  0x0286e524: jnz       0x0286ef5d      ;*checkcast
>                                        ; -
> ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 19
> (line 4)
>
> So is it defeating the inlining of getMetaClass() that the IRubyObject
> passed in is of two different types, even though getMetaClass comes
> from their common superclass? In other words, if Hotspot encounters an
> invokeinterface with two different types with a shared hierarchy and a
> single implementation of that interface method...why does it fail to
> inline that method? It seems like a case against using invokeinterface
> if at all possible, even against a shared class hierarchy.
>
> - Charlie
>
> On Wed, Sep 8, 2010 at 6:34 AM, Charles Oliver Nutter
> <headius at headius.com> wrote:
>> I'll give that a shot, Tom, thanks. Should have thought of it myself.
>>
>> On Wed, Sep 8, 2010 at 12:04 AM, Tom Rodriguez <tom.rodriguez at oracle.com> wrote:
>>> Did you run with -XX:+PrintInlining?  That will report why we didn't inline.
>>>
>>> tom
>>>
>>> On Sep 7, 2010, at 2:44 PM, Charles Oliver Nutter wrote:
>>>
>>>> I've been working on JRuby performance lately and ran into a peculiar situation.
>>>>
>>>> I have a static utility method in JRuby that checks whether a given
>>>> object's class is the same as the when the compiler optimized it. So
>>>> for a snippit of code like this:
>>>>
>>>> def foo
>>>>  bar
>>>> end
>>>>
>>>> def bar
>>>>  # whatever
>>>> end
>>>>
>>>> After running for some time, the "foo" call will be compiled, the
>>>> compiler will see that the "bar" call has a cached method handle, and
>>>> it will emit both a dynamic call and a static-typed call plus guard.
>>>> The static-typed call looks like this:
>>>>
>>>>    ALOAD 8
>>>>    LDC 446
>>>>    INVOKESTATIC
>>>> org/jruby/javasupport/util/RuntimeHelpers.isGenerationEqual
>>>> (Lorg/jruby/runtime/builtin/IRubyObject;I)Z
>>>>    IFNE L2
>>>>    ALOAD 8
>>>>    CHECKCAST org/jruby/RubyFixnum
>>>>    ALOAD 1
>>>>    LDC 1
>>>>    INVOKEVIRTUAL org/jruby/RubyFixnum.op_plus
>>>> (Lorg/jruby/runtime/ThreadContext;J)Lorg/jruby/runtime/builtin/IRubyObject;
>>>>
>>>> And the isGenerationEqual method looks like this:
>>>>
>>>>    public static boolean isGenerationEqual(IRubyObject object, int
>>>> generation) {
>>>>        return object.getMetaClass().getCacheToken() == generation;
>>>>    }
>>>>
>>>> While running benchmarks, I noticed a peculiar thing happening. For
>>>> "fib", the method JITs in JRuby very quickly and is soon after JITed
>>>> by Hotspot. But later compiles cause "fib" to get deoptimized and
>>>> marked not-entrant. Around the same time, isGenerationEqual gets
>>>> marked not entrant. Unfortunately, when fib re-optimizes, it does so
>>>> without inlining the isGenerationEqual call, and I can see that where
>>>> it was inlined before, it now actually does a CALL in assembly.
>>>>
>>>> Manually inlining the same bytecode everywhere isGenerationEqual would
>>>> be called does not seem to be subject to the same effect.
>>>>
>>>> Any thoughts? The only theory I have is that early in optimization
>>>> Hotspot sees that the target object type (IRubyObject object in the
>>>> method def) is the same, and so it optimizes based on that. Later, as
>>>> other compiled methods start to hit this code, the tyoe of "object"
>>>> changes. But the logic behind the scenes should be identical in every
>>>> case... IRubyObject.getMetaClass() only has one final implementation
>>>> on org.jruby.RubyBasicObject, and getCacheToken() has only one final
>>>> implementation on org.jruby.RubyModule, which simply returns an int
>>>> field.
>>>>
>>>> So I'm stumped why at least isGenerationEqual would not inline in all cases.
>>>>
>>>> - Charlie
>>>
>>>
>>
>


More information about the hotspot-compiler-dev mailing list