Small static method marked not entrant, inlining reversed?

Charles Oliver Nutter headius at
Wed Sep 8 00:03:10 PDT 2010

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'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

25 uncommon trap bimorphic maybe_recompile
  @1 org/jruby/javasupport/util/RuntimeHelpers isGenerationEqual

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
                                        ; -
ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
(line 4)
  0x028614dd: mov	ebx, [ebp+0x18]  ; implicit exception: dispatches to
  0x028614e0: cmp	ebx, 0x000001f3
  0x028614e6: jz	0x02861b11      ;*if_icmpne
                                        ; -
org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at 10 (line
                                        ; -
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
  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
                                        ; -
ruby.jit.fib_ruby_5EC86D8D24F89CAF26F0CBEA2FBDA4ED21326951::__file__ at 11
(line 4)
                                        ;   {virtual_call}
  0x0286e504: mov	ebx, [eax+0x18]  ; implicit exception: dispatches to
  0x0286e507: cmp	ebx, 0x000001f3
  0x0286e50d: jz	0x0286ea7f      ;*if_icmpne
                                        ; -
org.jruby.javasupport.util.RuntimeHelpers::isGenerationEqual at 10 (line
                                        ; -
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> 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> 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
>>> 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