Slide 1

Slide 1 text

InvokeDynamic in Practice How we use InvokeDynamic in JRuby

Slide 2

Slide 2 text

Hello FOSDEM! • Charles Oliver Nutter • @headius(@mastodon.social) • [email protected] • JRuby developer for 20 years • Java developer for nearly 30 years • Funding JRuby and other OSS with commercial support contracts • Stickers and business cards

Slide 3

Slide 3 text

My Role Today • JRuby lead • Keep project moving and improving with latest JDK features • Ensure community is healthy and growing • Headius Enterprises founder • Provide commercial support to users of JRuby + other OSS • Exploring other ways to fund JRuby + other OSS

Slide 4

Slide 4 text

JRuby • Ruby on the JVM • Primary focus on Ruby compatibility and experience • Standard Ruby libs, frameworks, work fl ow all are the same • JVM for Ruby • Bring the best of the JVM to the Ruby world • Literally every single OpenJDK project is useful to us

Slide 5

Slide 5 text

"What About Truf fl eRuby?" • JRuby is intended to run on any JVM, anywhere • Only JVM features, modulo some native call-outs (POSIX etc) • Deployable on existing servers, desktops, Android • JRuby supports Windows (and a dozen other esoteric platforms) • Truf fl eRuby is limited to GraalVM on Mac, Linux • Depends on native extensions, heavy down/upcalls hurt perf • Truf fl e startup/warmup are even worse than JRuby

Slide 6

Slide 6 text

MethodHandles • FOSDEM 2018: MethodHandles Everywhere • Introduction to MethodHandles • Introduction to InvokeBinder ( fl uent API for MethodHandles) • Implementing a simple language with MethodHandles • https://archive.fosdem.org/2018/schedule/event/method_handles/

Slide 7

Slide 7 text

JRuby and InvokeDynamic • InvokeDynamic makes JRuby possible • Ruby is heavily dynamic, in sometimes surprising ways • JRuby and Indy have evolved together • Still need to support non-Indy mode • Startup, warmup, memory impact can be large • Odd platforms have issues e.g. Android, OpenJ9

Slide 8

Slide 8 text

Method Calls • Obvious case and earliest use in JRuby • Not as straightforward as it seems • Different targets: Ruby, Java, native, ... • Validation and binding: Mutable types in Ruby, overloads in Java • Adapting argument layouts (optionals, varargs, keywords) • Indy allows all of these adaptations to inline!

Slide 9

Slide 9 text

Calls From Ruby Receiver Invalidation Notes Ruby single arity Type identity (passive) Type/method table modi fi cation (active) Direct binding, no boxing, inlines well Ruby variable arity Ruby with keyword args Type identity (passive) Type/method table modi fi cation (active) Direct binding, fully boxed, usually does not EA. May split entry point in future. Core JRuby method (Java) Type identity (passive) Type/method table modi fi cation (active) Matched arity binds directly. Mismatched arity through varargs box. Normal Java/JVM method Same as above, but also mismatched argument types and Java overloads Single-arity matched args is mostly direct. Overloads use varargs. Native downcall Like Ruby to Ruby; overloads are separate methods. JNR: indy all the way to JNI call. Panama: indy all the way. Ruby special calls (super, re fi ned) Partial hierarchy check (super). Local scope method table check. Fully unoptimized currently. Calls with blocks (lambdas) Same as above; block can replace trailing interface arg like lambda. Indirect block dispatch, poor inlining as with lambda.

Slide 10

Slide 10 text

def foo bar end def bar java.lang.Thread.dumpStack end 2.times { foo }

Slide 11

Slide 11 text

at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeDirectWithExceptionHandling(JavaMethod.java:290) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeStaticDirect(JavaMethod.java:221) at org.jruby.dist/org.jruby.java.invokers.StaticMethodInvoker.call(StaticMethodInvoker.java:23) at org.jruby.dist/org.jruby.java.invokers.StaticMethodInvoker.call(StaticMethodInvoker.java:85) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:90) at org.jruby.dist/org.jruby.ir.instructions.CallBase.interpret(CallBase.java:556) at org.jruby.dist/org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:372) at org.jruby.dist/org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:66) at org.jruby.dist/org.jruby.ir.interpreter.Interpreter.interpretFrameScope(Interpreter.java:174) at org.jruby.dist/org.jruby.ir.interpreter.Interpreter.INTERPRET_METHOD(Interpreter.java:145) at org.jruby.dist/org.jruby.internal.runtime.methods.InterpretedIRMethod.call(InterpretedIRMethod.java:130) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:193) at org.jruby.dist/org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:352) at org.jruby.dist/org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:66) at org.jruby.dist/org.jruby.ir.interpreter.Interpreter.interpretFrameScope(Interpreter.java:174) at org.jruby.dist/org.jruby.ir.interpreter.Interpreter.INTERPRET_METHOD(Interpreter.java:145) at org.jruby.dist/org.jruby.internal.runtime.methods.InterpretedIRMethod.call(InterpretedIRMethod.java:130) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:193) at org.jruby.dist/org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:352) at org.jruby.dist/org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:66) at org.jruby.dist/org.jruby.ir.interpreter.Interpreter.INTERPRET_BLOCK(Interpreter.java:120)

Slide 12

Slide 12 text

at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeDirectWithExceptionHandling(JavaMethod.java:290) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeStaticDirect(JavaMethod.java:221) at org.jruby.dist/org.jruby.java.invokers.StaticMethodInvoker.call(StaticMethodInvoker.java:23) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:75) at blah.invokeOther5:dumpStack(blah.rb:8) at blah .️ ❤ def bar #2(blah.rb:8) at org.jruby.dist/org.jruby.internal.runtime.methods.CompiledIRMethod.call(CompiledIRMethod.java:139) at org.jruby.dist/org.jruby.internal.runtime.methods.CompiledIRMethod.call(CompiledIRMethod.java:212) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:193) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.fcall(CachingCallSite.java:199) at blah.invokeOther1:bar(blah.rb:4) at blah .️ ❤ def foo #1(blah.rb:4) at org.jruby.dist/org.jruby.internal.runtime.methods.CompiledIRMethod.call(CompiledIRMethod.java:215) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:193) at org.jruby.dist/org.jruby.runtime.callsite.CachingCallSite.fcall(CachingCallSite.java:199) at blah.invokeOther0:foo(blah.rb:11) at blah .️ ❤ {} \=\^main\_ #0(blah.rb:11)

Slide 13

Slide 13 text

def foo bar end def bar java.lang.Thread.dumpStack end 2.times { foo } at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at blah .️ ❤ def bar #2(blah.rb:6) at blah .️ ❤ def foo #1(blah.rb:2)

Slide 14

Slide 14 text

at blah .️ ❤ def bar #2(blah.rb:6) at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(java.base@21/DirectMethodHandle$Holder) at java.lang.invoke.LambdaForm$MH/0x00000070015ab000.invoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015abc00.reinvoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015ac000.guard(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015abc00.reinvoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015ac000.guard(java.base@21/LambdaForm$MH) at java.lang.invoke.Invokers$Holder.linkToCallSite(java.base@21/Invokers$Holder) at blah .️ ❤ def foo #1(blah.rb:2) at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(java.base@21/DirectMethodHandle$Holder) at java.lang.invoke.LambdaForm$MH/0x00000070015ab000.invoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015abc00.reinvoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015ac000.guard(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015abc00.reinvoke(java.base@21/LambdaForm$MH) at java.lang.invoke.LambdaForm$MH/0x00000070015ac000.guard(java.base@21/LambdaForm$MH) at java.lang.invoke.Invokers$Holder.linkToCallSite(java.base@21/Invokers$Holder) at blah .️ ❤ {} \=\^main\_ #0(blah.rb:9)

Slide 15

Slide 15 text

def foo bar(1) end def bar(*args) java.lang.Thread.dumpStack end 2.times { foo } at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at blah .️ ❤ def bar #2(blah.rb:6) at blah .️ ❤ def foo #1(blah.rb:2)

Slide 16

Slide 16 text

Block Call from Java at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at blah .️ ❤ def bar #3(blah.rb:6) at blah .️ ❤ def foo #2(blah.rb:2) at blah .️ ❤ {} \=\^main\_ #1(blah.rb:9) at org.jruby.dist/org.jruby.runtime.CompiledIRBlockBody.yieldDirect(CompiledIRBlockBody.java:151) at org.jruby.dist/org.jruby.runtime.IRBlockBody.yieldSpecific(IRBlockBody.java:74) at org.jruby.dist/org.jruby.runtime.Block.yieldSpecific(Block.java:160) at org.jruby.dist/org.jruby.RubyFixnum.times(RubyFixnum.java:333) at blah .️ ❤ script(blah.rb:9)

Slide 17

Slide 17 text

Varargs Ruby to Java def foo bar end def bar(*args) java.lang.Thread.dumpStack(*args) end at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeDirectWithExceptionHandling(JavaMethod.java:290) at org.jruby.dist/org.jruby.javasupport.JavaMethod.invokeStaticDirect(JavaMethod.java:221) at org.jruby.dist/org.jruby.java.invokers.StaticMethodInvoker.call(StaticMethodInvoker.java:23) at blah .️ ❤ def bar #2(blah.rb:10) at blah .️ ❤ def foo #1(blah.rb:6)

Slide 18

Slide 18 text

Unexplored Call Forms • Dynamic calls from Java? • Need a good pattern for rewriting code to indy • Splitting and specialization for blocks (lambdas) • Same problem as in Java, but pervasive • Splitting heuristics are an open research problem • Numeric unboxing • Dif fi cult to do without being too aggressive, generating too much code

Slide 19

Slide 19 text

Instance Variables class Person def initialize(name, number) @name, @number = name, number end attr_accessor :name, :number def add_tag(tag, value) instance_variable_set(:"@#{tag}", value) end end

Slide 20

Slide 20 text

Instance Variables • Dynamically-allocated object fi elds • Scan method table for accesses on fi rst instantiation • Load or generate class "shape" to map ivars to fi elds • Object array for spilled or dynamic variables • InvokeDynamic wires access directly to fi eld or array

Slide 21

Slide 21 text

Constants and Globals DEBUG = true $debug = true module Foo module Bar class Baz end end end Foo::Bar::Baz.new

Slide 22

Slide 22 text

Constants and Globals • Constants are scoped, assigned lazily, rarely overwritten • Globals are global, either constant or frequently modi fi ed • Indy call sites can constantize them effectively • Mutable call site folds value just fi ne • Invalidate globally based on constant or global name • Complex lookup forms like Foo::Bar::Baz could be improved

Slide 23

Slide 23 text

Ruby Literals • Numeric literal objects constructed lazily • Numeric Fixnum (long), Bignum (BigInteger), Float (double) • Ruby Strings/Regexp (byte[] + Encoding) assembled, interned, cached • All-literal Arrays ([1, 2, 3]) and Range (1..10) • To-do: All-literal Hash, Range, Complex, Rational

Slide 24

Slide 24 text

INVOKEDYNAMIC fixnum(Lorg/jruby/runtime/ThreadContext;)... org/jruby/ir/targets/indy/FixnumObjectSite.bootstrap(... // arguments: 1L INVOKEDYNAMIC frozen(Lorg/jruby/runtime/ThreadContext;)... org/jruby/ir/targets/indy/StringBootstrap.fstring(... // arguments: "hello", "UTF-8", 16, "blah.rb", 3 INVOKEDYNAMIC regexp(Lorg/jruby/runtime/ThreadContext;) org/jruby/ir/targets/indy/RegexpObjectSite.bootstrap(... // arguments: "foo", "UTF-8", 512

Slide 25

Slide 25 text

INVOKEDYNAMIC fixnumArray(Lorg/jruby/runtime/ThreadContext;)... org/jruby/ir/targets/indy/ArrayBootstrap.literalArray(... // arguments: "\u0001\u0000\u0000\u0000\u0002\u0000\u0000\u0000" INVOKEDYNAMIC bignum(Lorg/jruby/runtime/ThreadContext;)... org/jruby/ir/targets/indy/BignumObjectSite.bootstrap(... // arguments: "111111111111111111111" INVOKEDYNAMIC range(Lorg/jruby/runtime/ThreadContext;)... org/jruby/ir/targets/indy/RangeObjectSite.bootstrapFixnums(... // arguments: 1L, 10L, 0

Slide 26

Slide 26 text

String Interpolation • Embed constant bits + speci fi cation into indy site • Indy chooses from several forms to interpret speci fi cation • N overloads that stitch it together (up to 4 args) • Static bits from site, dynamic from args[] • Simple loop over static + dynamic args[] • Bail out for extremely large forms (>50 elements)

Slide 27

Slide 27 text

INVOKEDYNAMIC buildDynamicString(..., IRubyObject)Lorg/jruby/RubyString; org/jruby/ir/targets/indy/BuildDynamicStringSite.buildDString(... // arguments: "the value ", // string "UTF-8", // encoding 16, // code range " was passed", // string "UTF-8", // encoding 16, // code range 55, // size estimate "UTF-8", // final encoding 0, // frozen? 0, // chilled? 5L, // specification (bit indicates a static piece) 3 // how many bits are relevant ] def foo(a) puts "the value #{a} was passed" end

Slide 28

Slide 28 text

Runtime Plumbing • Initial lambda construction, captured state construction • Heap-based local variables • Direct walk to appropriate scope depth, retrieve/set var • Thread interrupt with safepoints, but invalidates on interrupt 🤔 • Other "constant dynamic" runtime utils used within JRuby

Slide 29

Slide 29 text

MethodHandles as IR • Object construction: Class.allocate + obj.initialize • Negated comparison • != is not(==), !~ is not(=~) • Common forms: Kernel#loop, Integer#times • Anything could be compiled to IR (see my FOSDEM 2018 talk) • But impossible to reconstruct/simulate call stack

Slide 30

Slide 30 text

class Class def self.new(*args) obj = allocate obj.initialize(*args) obj end end

Slide 31

Slide 31 text

class Dumper def initialize java.lang.Thread.dumpStack end end def foo Dumper.new end at java.base/java.lang.Thread.dumpStack(Thread.java:2210) at blah .️ ❤ def initialize #2(blah.rb:5) at blah .️ ❤ def foo #3(blah.rb:10)

Slide 32

Slide 32 text

Challenges

Slide 33

Slide 33 text

Stacktrace with LambdaForm • LambdaForm messes up thread dumps, pro fi ling • LF frames included in pro fi les • LF adaptations look like separate call paths for same call • The more we use Indy, the harder it is to get accurate pro fi les

Slide 34

Slide 34 text

java.lang.invoke Objects • Many MethodHandles, LambdaForms in memory • MH chains take longer to warm up than trivial inline cache • Dif fi cult to cache anything between runs

Slide 35

Slide 35 text

red/black no indy bytes objs name 296792 5291 java.lang.invoke.MemberName 85320 2650 java.lang.invoke.BoundMethodHandle$Species_L 59016 1828 java.lang.invoke.LambdaForm$Name 46472 1436 java.lang.invoke.DirectMethodHandle 649192 16217 java.lang.invoke.MethodType 519464 16217 java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry 41952 1036 java.lang.invoke.BoundMethodHandle$Species_LL 41800 862 java.lang.invoke.LambdaForm$Name[] 21720 442 java.lang.invoke.LambdaForm 14312 345 java.lang.invoke.DirectMethodHandle$Accessor 14384 248 java.lang.invoke.MethodTypeForm 11904 474 java.lang.invoke.LambdaForm$NamedFunction 11880 237 java.lang.invoke.MethodHandleImpl$CountingWrapper Total: 1.814MB

Slide 36

Slide 36 text

red/black with indy bytes objs name 92040 1907 java.lang.invoke.MethodHandleImpl$CountingWrapper 158712 3955 java.lang.invoke.BoundMethodHandle$Species_LL 88008 2734 java.lang.invoke.DirectMethodHandle 14344 3557 java.lang.invoke.LambdaForm$Name 81768 1693 java.lang.invoke.BoundMethodHandle$Species_L4 181160 5645 java.lang.invoke.BoundMethodHandle$Species_L 1333512 33325 java.lang.invoke.MethodType 1066920 33325 java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry 62608 1350 java.lang.invoke.LambdaForm$Name[] 31320 642 java.lang.invoke.LambdaForm 614312 10961 java.lang.invoke.LambdaFormEditor$Transform 15840 638 java.lang.invoke.LambdaForm$NamedFunction 15232 368 java.lang.invoke.DirectMethodHandle$Accessor 15616 270 java.lang.invoke.MethodTypeForm 48912 1210 java.lang.invoke.BoundMethodHandle$Species_L3 6976 140 java.lang.invoke.MethodHandle[] 9048 178 java.lang.invoke.BoundMethodHandle$Species_L5 4512 100 java.lang.invoke.DirectMethodHandle$Constructor 4248 155 java.lang.invoke.SwitchPoint Total: 4.278MB

Slide 37

Slide 37 text

No Indy from Java • Really need a way to inject invokedynamic in Java code • Static CallSite.invoke gets us close, but dat Throwable 😵 • Build-time rewriting as a fi rst step? • Give up on Java and move all to Ruby?

Slide 38

Slide 38 text

JRuby + Indy Future • JRuby 10 will fi ll in many gaps • Better adaptations for argument forms, overloads • Finally wire up Panama for native • Explore more intrinsic forms and specialization • Looking forward to working with OpenJDK projects more

Slide 39

Slide 39 text

Thank You! • Charles Oliver Nutter • [email protected] • @headius(@mastodon.social) • blog.headius.com • github.com/sponsors/headius • headius.com/support • github.com/jruby/jruby • github.com/jruby/jcodings • github.com/jruby/joni • github.com/jnr