Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trivial call site not inlining with invokedynamic #6280

Closed
chrisseaton opened this issue Jun 14, 2020 · 14 comments
Closed

Trivial call site not inlining with invokedynamic #6280

chrisseaton opened this issue Jun 14, 2020 · 14 comments

Comments

@chrisseaton
Copy link
Contributor

chrisseaton commented Jun 14, 2020

Environment Information

Provide at least:

  • jruby 9.3.0.0-SNAPSHOT (2.6.5) 2020-06-12 ccecbe8014 OpenJDK 64-Bit Server VM 14.0.1+7 on 14.0.1+7 +jit [darwin-x86_64] (master)
  • Darwin Chris-Seatons-MacBook-Pro.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64

Expected Behavior

main.rb

require_relative 'inline'

inline.rb

class Bar
  def bar
    14
  end
end

def foo(bar)
  bar.bar
end

bar = Bar.new

while true
  foo bar
end
% bin/ruby -Xcompile.invokedynamic "-J-XX:CompileCommand=print,*::*foo*" main.rb

I'd expect the invokedynamic call from foo to bar to be fully inlined in C2.

Actual Behavior

Not inlined.

  0x000000011ca18e40:   mov    %eax,-0x14000(%rsp)
  0x000000011ca18e47:   push   %rbp
  0x000000011ca18e48:   sub    $0x20,%rsp                   ;*synchronization entry
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@-1
  0x000000011ca18e4c:   mov    %rsi,%r11
  0x000000011ca18e4f:   mov    %rcx,%r10
  0x000000011ca18e52:   mov    0x8(%r8),%ecx                ; implicit exception: dispatches to 0x000000011ca18f11
  0x000000011ca18e56:   cmp    $0x201b37,%ecx               ;   {metadata('org/jruby/gen/RubyObject1')}
  0x000000011ca18e5c:   jne    0x000000011ca18ed8
  0x000000011ca18e5e:   mov    %r8,%rcx                     ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.RubyBasicObject::getMetaClass@1 (line 525)
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063840::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000011ca18e61:   mov    0x14(%rcx),%ebp              ;*getfield metaClass {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.RubyBasicObject::getMetaClass@4 (line 525)
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063840::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000011ca18e64:   cmp    $0xc38372e8,%ebp             ;   {oop(a 'org/jruby/RubyClass'{0x000000061c1b9740})}
  0x000000011ca18e6a:   jne    0x000000011ca18eac           ;*if_acmpne {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@5 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063840::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000011ca18e6c:   movabs $0x61c1b76d0,%rdx            ;   {oop(a 'org/jruby/parser/StaticScope'{0x000000061c1b76d0})}
  0x000000011ca18e76:   movabs $0x6022465a8,%rdi            ;   {oop("bar"{0x00000006022465a8})}
  0x000000011ca18e80:   movabs $0x61c1b9740,%r9             ;   {oop(a 'org/jruby/RubyClass'{0x000000061c1b9740})}
  0x000000011ca18e8a:   movabs $0x600015118,%r8             ;   {oop(a 'org/jruby/runtime/Block'{0x0000000600015118})}
  0x000000011ca18e94:   data32 xchg %ax,%ax
  0x000000011ca18e97:   callq  0x0000000114dbc400           ; ImmutableOopMap {}
                                                            ;*invokestatic linkToStatic {reexecute=0 rethrow=0 return_oop=1}
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@20
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801068440::invoke@50
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@78
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064c40::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
                                                            ;   {static_call Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$bar$0}
  0x000000011ca18e9c:   add    $0x20,%rsp
  0x000000011ca18ea0:   pop    %rbp
  0x000000011ca18ea1:   mov    0x110(%r15),%r10
  0x000000011ca18ea8:   test   %eax,(%r10)                  ;   {poll_return}
  0x000000011ca18eab:   retq   

The inlining log here is confusing - sometimes says it's inlined, sometimes says not due to The inlining log says 'unloaded signature classes'. Possibly worth looking for a deoptimisation and recompilation? Could also be related to tiering.

But whatever the logs say, bottom line seems to be there is a callq where that does not seem reasonable. (But I'm not an expert on invokedynamic.)

As discussed on Matrix.

@chrisseaton
Copy link
Contributor Author

Note that I need to run via main.rb to see this. Seems to go away if I run via inline.rb directly. Although in that case it's RUBY$method$foo$2 that inlines successfully, when when I run via main.rb it's RUBY$method$foo$0 where it isn't working. I'm not sure what this name mangling means.

@headius
Copy link
Member

headius commented Jun 14, 2020

Thanks for filing this!

As we discussed on Matrix, I did play with your other example a bit and was able to reproduce it. When the target script is immediately compiled, everything inlines well. But when not immediately compiling that script and instead allowing normal JIT thresholds to kick in, we get this peculiar "unloaded signature classes" message from e.g. PrintInlining output.

I only looked into it briefly, but turning off tiered compilation seems to help; the message and deopt still appears, but it's followed shortly after by a new compilation that appears to inline and emit the expected native code. So tiering could indeed be to blame.

The other theory I have is that our one-method-one-classloader mechanism (done so that abandoned JIT classes can get thrown out by the JVM) may be confounding something in the method handle inlining logic.

FWIW I do not believe this has always been here, but I couldn't point at any particular change that might have regressed it. There's also the possibility that this has been intrinsic to the always-on tiered compilation (starting some time in Java 8) and we just never saw it because we usually test against a fully-compiled target script and/or a JVM in which tiering has been turned off (to avoid the extra noise caused by early tiers.

In any case, this is a great find, and could mean that a whole lot of invokedynamic optimizations are simply not being realized in JRuby today.

@chrisseaton
Copy link
Contributor Author

Some things we found out:

  • Disabling tiering -J-XX:-TieredCompilation works around
  • The bug isn't present on GraalVM OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02)
  • The bug is present on Java 8 OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.252-b09, mixed mode)
  • The bug is present on Java 14 OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

@headius
Copy link
Member

headius commented Jun 14, 2020

So this does appear to be a bug in tiered compilation going back at least as far as OpenJDK 8 and still present in OpenJDK 14.

WIth tiered compilation disabled (-XX:-TieredCompilation) the "bar" method appears to inline properly, and the resulting native code is what we would expect to see. This means that using C2 alone works fine. Given the complexities of tiered compilation in OpenJDK, I'm not sure it's a good use of my time to try to dig deeper, so I will raise a red flag on the Hotspot compiler dev list.

@chrisseaton
Copy link
Contributor Author

What it looks like when it's working

  0x000000010e3cd540:   mov    %eax,-0x14000(%rsp)
  0x000000010e3cd547:   push   %rbp
  0x000000010e3cd548:   sub    $0x20,%rsp                   ;*synchronization entry
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@-1
  0x000000010e3cd54c:   mov    %rsi,%r10
  0x000000010e3cd54f:   mov    0x8(%r8),%r9d                ; implicit exception: dispatches to 0x000000010e3cd5e0
  0x000000010e3cd553:   cmp    $0x201b37,%r9d               ;   {metadata('org/jruby/gen/RubyObject1')}
  0x000000010e3cd55a:   jne    0x000000010e3cd5b4
  0x000000010e3cd55c:   mov    %r8,%r11                     ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.RubyBasicObject::getMetaClass@1 (line 525)
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063440::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000010e3cd55f:   mov    0x14(%r11),%ebp              ;*getfield metaClass {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.RubyBasicObject::getMetaClass@4 (line 525)
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063440::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000010e3cd563:   cmp    $0xc030001c,%ebp             ;   {oop(a 'org/jruby/RubyClass'{0x00000006018000e0})}
  0x000000010e3cd569:   jne    0x000000010e3cd585           ;*if_acmpne {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - org.jruby.ir.targets.indy.Bootstrap::testType@5 (line 1001)
                                                            ; - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801063440::invoke@25
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@37
                                                            ; - java.lang.invoke.DelegatingMethodHandle$Holder::delegate@17
                                                            ; - java.lang.invoke.LambdaForm$MH/0x0000000801064040::guard@78
                                                            ; - java.lang.invoke.Invokers$Holder::linkToCallSite@17
                                                            ; - Users.chrisseaton.src.github_dot_com.jruby.jruby.inline::RUBY$method$foo$0@9 (line 8)
  0x000000010e3cd56b:   movabs $0x6004309d8,%rax            ;   {oop(a 'org/jruby/RubyFixnum'{0x00000006004309d8})}
  0x000000010e3cd575:   add    $0x20,%rsp
  0x000000010e3cd579:   pop    %rbp
  0x000000010e3cd57a:   mov    0x110(%r15),%r10
  0x000000010e3cd581:   test   %eax,(%r10)                  ;   {poll_return}
  0x000000010e3cd584:   retq   

@headius
Copy link
Member

headius commented Jun 14, 2020

And here's the PrintInlining output for the broken setup:

   7778 1535    b  4       Users.headius.projects.jruby.foo_bar_inline::RUBY$method$foo$0 (20 bytes)
                              @ 9   java.lang.invoke.LambdaForm$MH/1627960023::linkToCallSite (18 bytes)   force inline by annotation
                                @ 1   java.lang.invoke.Invokers::getCallSiteTarget (8 bytes)   force inline by annotation
                                  @ 4   java.lang.invoke.MutableCallSite::getTarget (5 bytes)   accessor
                                @ 14   java.lang.invoke.LambdaForm$MH/99347477::guard (103 bytes)   force inline by annotation
                                  @ 37   java.lang.invoke.LambdaForm$MH/1096283470::exactInvoker (51 bytes)   force inline by annotation
                                    @ 16   java.lang.invoke.LambdaForm$BMH/1828972342::reinvoke (24 bytes)   force inline by annotation
                                      @ 20   java.lang.invoke.LambdaForm$DMH/746292446::invokeVirtual_L_L (14 bytes)   force inline by annotation
                                        @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                        @ 10   java.lang.invoke.MutableCallSite::getTarget (5 bytes)   accessor
                                    @ 31   java.lang.invoke.Invokers::checkExactType (30 bytes)   force inline by annotation
                                      @ 11   java.lang.invoke.MethodHandle::type (5 bytes)   accessor
                                    @ 36   java.lang.invoke.Invokers::checkCustomized (20 bytes)   force inline by annotation
                                    @ 47   java.lang.invoke.LambdaForm$MH/75457651::identity_I (14 bytes)   force inline by annotation
               !                  @ 51   java.lang.invoke.MethodHandleImpl::profileBoolean (34 bytes)   (intrinsic)
                                  @ 78   java.lang.invoke.LambdaForm$MH/523429237::delegate (18 bytes)   force inline by annotation
                                    @ 4   java.lang.invoke.MethodHandleImpl$CountingWrapper::getTarget (5 bytes)   accessor
                                    @ 14   java.lang.invoke.LambdaForm$MH/99347477::guard (103 bytes)   force inline by annotation
                                      @ 37   java.lang.invoke.LambdaForm$BMH/3447021::reinvoke (29 bytes)   force inline by annotation
                                        @ 25   java.lang.invoke.LambdaForm$DMH/380894366::invokeStatic_LL_I (15 bytes)   force inline by annotation
                                          @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                          @ 11   org.jruby.ir.targets.Bootstrap::testType (14 bytes)   inline (hot)
                                            @ 2   org.jruby.RubyBasicObject::getMetaClass (8 bytes)   inline (hot)
               !                      @ 51   java.lang.invoke.MethodHandleImpl::profileBoolean (34 bytes)   (intrinsic)
                                      @ 78   java.lang.invoke.LambdaForm$MH/523429237::delegate (18 bytes)   force inline by annotation
                                        @ 4   java.lang.invoke.MethodHandleImpl$CountingWrapper::getTarget (5 bytes)   accessor
                                        @ 14   java.lang.invoke.LambdaForm$BMH/846492085::reinvoke (54 bytes)   force inline by annotation
                                          @ 50   java.lang.invoke.LambdaForm$DMH/2016447921::invokeStatic_L6_L (24 bytes)   force inline by annotation
                                            @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                            @ 20   Users.headius.projects.jruby.foo_bar_inline::RUBY$method$bar$0 (10 bytes)   unloaded signature classes
                                            @ 20   Users.headius.projects.jruby.foo_bar_inline::RUBY$method$bar$0 (10 bytes)   unloaded signature classes

With tiered compilation disabled, the ...bar... method inlines correctly.

@headius
Copy link
Member

headius commented Jun 14, 2020

I have confirmed this fails on the most recent JRuby release, 9.2.11.1, and works properly there with tiered compilation disabled.

Here's the PrintInlining output for the working setup with tiering off:

   1398  229    b        java.lang.invoke.LambdaForm$DMH/362239120::invokeStatic_L7_L (32 bytes)
                            @ 7   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                            @ 28   Users.headius.projects.jruby.foo_bar_inline::RUBY$method$foo$0 (20 bytes)   inline (hot)
                              @ 9   java.lang.invoke.LambdaForm$MH/1811044090::linkToCallSite (18 bytes)   force inline by annotation
                                @ 1   java.lang.invoke.Invokers::getCallSiteTarget (8 bytes)   force inline by annotation
                                  @ 4   java.lang.invoke.MutableCallSite::getTarget (5 bytes)   accessor
                                @ 14   java.lang.invoke.LambdaForm$MH/940553268::guard (103 bytes)   force inline by annotation
                                  @ 37   java.lang.invoke.LambdaForm$MH/75457651::exactInvoker (51 bytes)   force inline by annotation
                                    @ 16   java.lang.invoke.LambdaForm$BMH/1828972342::reinvoke (24 bytes)   force inline by annotation
                                      @ 20   java.lang.invoke.LambdaForm$DMH/746292446::invokeVirtual_L_L (14 bytes)   force inline by annotation
                                        @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                        @ 10   java.lang.invoke.MutableCallSite::getTarget (5 bytes)   accessor
                                    @ 31   java.lang.invoke.Invokers::checkExactType (30 bytes)   force inline by annotation
                                      @ 11   java.lang.invoke.MethodHandle::type (5 bytes)   accessor
                                    @ 36   java.lang.invoke.Invokers::checkCustomized (20 bytes)   force inline by annotation
                                    @ 47   java.lang.invoke.LambdaForm$MH/1796488937::identity_I (14 bytes)   force inline by annotation
               !                  @ 51   java.lang.invoke.MethodHandleImpl::profileBoolean (34 bytes)   (intrinsic)
                                  @ 78   java.lang.invoke.LambdaForm$MH/523429237::delegate (18 bytes)   force inline by annotation
                                    @ 4   java.lang.invoke.MethodHandleImpl$CountingWrapper::getTarget (5 bytes)   accessor
                                    @ 14   java.lang.invoke.LambdaForm$MH/940553268::guard (103 bytes)   force inline by annotation
                                      @ 37   java.lang.invoke.LambdaForm$BMH/1032616650::reinvoke (29 bytes)   force inline by annotation
                                        @ 25   java.lang.invoke.LambdaForm$DMH/380894366::invokeStatic_LL_I (15 bytes)   force inline by annotation
                                          @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                          @ 11   org.jruby.ir.targets.Bootstrap::testType (14 bytes)   inline (hot)
                                            @ 2   org.jruby.RubyBasicObject::getMetaClass (8 bytes)   inline (hot)
               !                      @ 51   java.lang.invoke.MethodHandleImpl::profileBoolean (34 bytes)   (intrinsic)
                                      @ 78   java.lang.invoke.LambdaForm$MH/523429237::delegate (18 bytes)   force inline by annotation
                                        @ 4   java.lang.invoke.MethodHandleImpl$CountingWrapper::getTarget (5 bytes)   accessor
                                        @ 14   java.lang.invoke.LambdaForm$BMH/152005629::reinvoke (54 bytes)   force inline by annotation
                                          @ 50   java.lang.invoke.LambdaForm$DMH/2016447921::invokeStatic_L6_L (24 bytes)   force inline by annotation
                                            @ 1   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                            @ 20   Users.headius.projects.jruby.foo_bar_inline::RUBY$method$bar$0 (10 bytes)   inline (hot)
                                              @ 3   java.lang.invoke.LambdaForm$MH/1753447031::linkToCallSite (14 bytes)   force inline by annotation
                                                @ 1   java.lang.invoke.Invokers::getCallSiteTarget (8 bytes)   force inline by annotation
                                                  @ 4   java.lang.invoke.MutableCallSite::getTarget (5 bytes)   accessor
                                                @ 10   java.lang.invoke.LambdaForm$MH/1595428806::identity_L (8 bytes)   force inline by annotation

@charliegracie
Copy link

charliegracie commented Jun 15, 2020

Hey guys! I had some time this morning so I decided to dig into this a bit while writing some doc :)

I was able to reproduce the issue on my Mac but it failed maybe 1 out of 10 times for me so I started playing with command line options to make it more reproducible.

When I run with the following options I see the failure to inline 100% of the time:

./jruby -Xcompile.invokedynamic "-J-XX:CompileCommand=option *::*foo*,PrintInlining" "-J-XX:CompileCommand=compileonly,*::*foo*" "-J-XX:-TieredCompilation" main.rb

The last line in the PrintInlining is always:

@ 20   Users.chgracie.dev.jruby.jruby_minus_head.bin.inline::RUBY$method$bar$0 (10 bytes)   unloaded signature classes

Starting at the error message unloaded signature classes I worked backwards to find the class(es) which were causing the error. The first class in the signature that caused issues was org/jruby/RubyModule. This class was found on the current class loader but it is rejected due to a protection domain check. There was a 2nd failure related to java/lang/String which is just not found on the particular class loader.

Out of time for today most likely but I might find some more time later this evening to dig further into this.

@chrisseaton
Copy link
Contributor Author

https://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2020-June/038604.html

headius added a commit that referenced this issue Sep 3, 2020
In #6280 we found that JIT-generated code for Ruby methods was not
inlining, due to a reported error of "unloaded signature classes".
This error means that not all classes in the target method's
signature were fully loaded and resolved at the point of inlining,
and since the Hotspot JIT cannot directly load classes it would
reject these methods. Specifically for this case, at least the
String argument (called method name) and possibly the RubyModule
argument (called-as class) were flagged as "unloaded", likely
because the simple code body in #6280 never actually accessed
those arguments.

This patch forces the generated code to forcibly load and resolve
all classes used in the jitted method signatures, which should
guarantee they do not appear "unloaded" at native JIT inlining
time. Ideally this will be a fairly cheap operation, performed
only once at classloading time, since the classes in question are
clearly already loaded in the parent classloaders.
@headius
Copy link
Member

headius commented Sep 3, 2020

Updating from the hotspot-compiler-dev list discussion...

Vladimir Ivanov was able to reproduce the "unloaded signature class" problem and let us know that the issue was those classes not being fully loaded and resolved in the JIT method's classloader. As @charliegracie reported above, the example seems to fail with one of two "unloaded" classes, either RubyModule or String. These two classes are provided to method bodies so they can "super" with a proper method name and lookup class, but this trivial example does not use either of those arguments. As a result, not all classes in the method signature are fully loaded and resolved and the JIT rejects it for inlining.

I have committed a workaround that forcibly loads all classes used by our JIT method signatures, and this appears to fix the issue.

It is not clear at this time whether we're doing anything "wrong". The method is called (possibly hundreds of times) before becoming eligible for inlining, but without accessing those arguments nothing forces those classes to resolve. I do not like the workaround, and it seems like this problem could be affecting lots of other generated code, but at least we have a path forward.

The output below shows the inlined assembly for "foo" plus "bar" and it looks about like we expect it to look.

  # {method} {0x0000000123c628d0} 'RUBY$method$foo$0' '(Lorg/jruby/runtime/ThreadContext;Lorg/jruby/parser/StaticScope;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/Block;Lorg/jruby/Rub
yModule;Ljava/lang/String;)Lorg/jruby/runtime/builtin/IRubyObject;' in 'Users/headius/projects/jruby/inline'
  # parm0:    rsi:rsi   = 'org/jruby/runtime/ThreadContext'
  # parm1:    rdx:rdx   = 'org/jruby/parser/StaticScope'
  # parm2:    rcx:rcx   = 'org/jruby/runtime/builtin/IRubyObject'
  # parm3:    r8:r8     = 'org/jruby/runtime/builtin/IRubyObject'
  # parm4:    r9:r9     = 'org/jruby/runtime/Block'
  # parm5:    rdi:rdi   = 'org/jruby/RubyModule'
  # parm6:    [sp+0x30]   = 'java/lang/String'  (sp of caller)
  0x0000000109aaea60: mov    %eax,-0x14000(%rsp)
  0x0000000109aaea67: push   %rbp
  0x0000000109aaea68: sub    $0x20,%rsp         ;*synchronization entry
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$foo$0@-1

  0x0000000109aaea6c: mov    %rsi,%r10
  0x0000000109aaea6f: mov    0x8(%r8),%r9d      ; implicit exception: dispatches to 0x0000000109aaeadd
  0x0000000109aaea73: cmp    $0xf809db98,%r9d   ;   {metadata('org/jruby/gen/RubyObject1')}
  0x0000000109aaea7a: jne    0x0000000109aaeac1
  0x0000000109aaea7c: mov    %r8,%r11           ;*checkcast
                                                ; - org.jruby.RubyBasicObject::getMetaClass@1 (line 525)
                                                ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1002)
                                                ; - java.lang.invoke.LambdaForm$DMH/1221555852::invokeStatic_LL_I@11
                                                ; - java.lang.invoke.LambdaForm$BMH/1312884893::reinvoke@25
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@37
                                                ; - java.lang.invoke.LambdaForm$MH/523429237::delegate@14
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@78
                                                ; - java.lang.invoke.LambdaForm$MH/302155142::linkToCallSite@14
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$foo$0@9 (line 8)

  0x0000000109aaea7f: mov    0x14(%r11),%ebp    ;*getfield metaClass
                                                ; - org.jruby.RubyBasicObject::getMetaClass@4 (line 525)
                                                ; - org.jruby.ir.targets.indy.Bootstrap::testType@2 (line 1002)
                                                ; - java.lang.invoke.LambdaForm$DMH/1221555852::invokeStatic_LL_I@11
                                                ; - java.lang.invoke.LambdaForm$BMH/1312884893::reinvoke@25
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@37
                                                ; - java.lang.invoke.LambdaForm$MH/523429237::delegate@14
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@78
                                                ; - java.lang.invoke.LambdaForm$MH/302155142::linkToCallSite@14
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$foo$0@9 (line 8)

  0x0000000109aaea83: cmp    $0xed974343,%ebp   ;   {oop(a 'org/jruby/RubyClass')}
  0x0000000109aaea89: jne    0x0000000109aaeaa1  ;*if_acmpne
                                                ; - org.jruby.ir.targets.indy.Bootstrap::testType@5 (line 1002)
                                                ; - java.lang.invoke.LambdaForm$DMH/1221555852::invokeStatic_LL_I@11
                                                ; - java.lang.invoke.LambdaForm$BMH/1312884893::reinvoke@25
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@37
                                                ; - java.lang.invoke.LambdaForm$MH/523429237::delegate@14
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@78
                                                ; - java.lang.invoke.LambdaForm$MH/302155142::linkToCallSite@14
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$foo$0@9 (line 8)

  0x0000000109aaea8b: movabs $0x6c0223188,%rax  ;*synchronization entry
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$bar$0@-1
                                                ; - java.lang.invoke.LambdaForm$DMH/2016447921::invokeStatic_L6_L@20
                                                ; - java.lang.invoke.LambdaForm$BMH/1891546521::reinvoke@50
                                                ; - java.lang.invoke.LambdaForm$MH/523429237::delegate@14
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@78
                                                ; - java.lang.invoke.LambdaForm$MH/523429237::delegate@14
                                                ; - java.lang.invoke.LambdaForm$MH/912011468::guard@78
                                                ; - java.lang.invoke.LambdaForm$MH/302155142::linkToCallSite@14
                                                ; - Users.headius.projects.jruby.inline::RUBY$method$foo$0@9 (line 8)
                                                ;   {oop(a 'org/jruby/RubyFixnum')}
  0x0000000109aaea95: add    $0x20,%rsp
  0x0000000109aaea99: pop    %rbp
  0x0000000109aaea9a: test   %eax,-0x34c9aa0(%rip)        # 0x00000001065e5000
                                                ;   {poll_return}
  0x0000000109aaeaa0: retq   

headius added a commit that referenced this issue Sep 3, 2020
In #6280 we found that JIT-generated code for Ruby methods was not
inlining, due to a reported error of "unloaded signature classes".
This error means that not all classes in the target method's
signature were fully loaded and resolved at the point of inlining,
and since the Hotspot JIT cannot directly load classes it would
reject these methods. Specifically for this case, at least the
String argument (called method name) and possibly the RubyModule
argument (called-as class) were flagged as "unloaded", likely
because the simple code body in #6280 never actually accessed
those arguments.

This patch forces the generated code to forcibly load and resolve
all classes used in the jitted method signatures, which should
guarantee they do not appear "unloaded" at native JIT inlining
time. Ideally this will be a fairly cheap operation, performed
only once at classloading time, since the classes in question are
clearly already loaded in the parent classloaders.

For JRuby 9.2, this is only done for the "JVM7" logic, since the
"JVM6" logic typically does not make methods inlinable anyway.
@headius
Copy link
Member

headius commented Sep 3, 2020

Better fix will be to make sure any classes used in generated signatures are added to the static initializer, rather than having a hardcoded list.

@headius
Copy link
Member

headius commented Oct 21, 2020

I'm going to mark this fixed for 9.2.14 and 9.3 and open a separate issue for improving this logic.

@retronym
Copy link

Noting for the record that we ran into this JIT corner case in a JMH benchmark for measuring the performance of Scala's generated equals/hashCode methods for case classes (our version of records).

lampepfl/dotty-feature-requests#196

In that case, the nested classloader was just part of the usual the JDK 9+ classloader layers (not a custom one like in this ticket). The funny part was that although the benchmark instantiated the test data with lots of String-s, java/lang/String remained uninitialized because all the instances were constants. As such, JIT refused to inline a getter method for a String field into the equals implementation, which hurt performance.

@headius
Copy link
Member

headius commented Apr 15, 2021

Thank you for posting @retronym! Good to hear that you determined the cause and an appropriate solution. I did ask the JIT folks if there's any way to force signature classes to initialize when they become eligible for JIT or inlining but the answer gets complicated due to the asynchronous nature of the JIT (it can't just call back into Java to boot those classes). Perhaps a classloader-level solution might be workable, like an eager-loading classloader that scans all signatures and makes sure the classes get properly initialized. I still have that open issue above to find a more general fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants