Today I played a bit more with expanding our use of invokedynamic in JRuby. The experiment today was to replace direct guarded calls with indy calls.
Specifically, when calling an operator (+, -, <, etc) with a literal integer, we use a different call site that checks instanceof RubyFixnum and calls the actual method (op_plus, op_minus, op_lt, etc). So there's a monomorphic path as long as the target is also a RubyFixnum. code => PlusCallSite (for example) => RubyFixnum.op_plus. Replacing this with indy uses a guardWithTest where the test is "self instanceof RubyFixnum", and the target path binds directly through to the RubyFixnum method with a bit of argument dropping. This is a good test for indy perf versus the same logic done with instance, casts, and invokevirtual. With the previous logic, fib(35) settles in around 0.74s. With the indy logic, performance decreases to 0.83s or so. This does beat logic that dispatched operators with literal values like any other, passing a RubyFixnum object and doing a full type identity and modification guard...that comes in closer to 0.95 or worse. Fixnum math operations really beg for as direct logic as possible, and it looks like indy adds a healthy amount of overhead over all virtual invocations. I have not done any investigation into the resulting assembly yet. Here's the original logic for +, in PlusCallSite (bytecode emitted calls virtual method CallSite.call): public IRubyObject call(ThreadContext context, IRubyObject caller, IRubyObject self, long fixnum) { if (self instanceof RubyFixnum) { return ((RubyFixnum) self).op_plus(context, fixnum); } else if (self instanceof RubyFloat) { return ((RubyFloat) self).op_plus(context, fixnum); } return super.call(context, caller, self, fixnum); } Here's how the indy logic does it: ASM emission: public void invokeBinaryFixnumRHS(String name, CompilerCallback receiverCallback, long fixnum) { if (receiverCallback != null) { receiverCallback.call(methodCompiler); } else { methodCompiler.loadSelf(); } methodCompiler.loadThreadContext(); method.ldc(name); method.ldc(fixnum); String signature = sig(IRubyObject.class, params(IRubyObject.class, ThreadContext.class, String.class, long.class)); method.invokedynamic("fixnumOperator", signature, InvokeDynamicSupport.bootstrapHandle("bootstrapOperatorFixnum")); } Bootstrap: public static CallSite bootstrapOperatorFixnum(MethodHandles.Lookup lookup, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { CallSite site = new MutableCallSite(type); MethodType fallbackType = type.insertParameterTypes(0, MutableCallSite.class); MethodHandle myFallback = MethodHandles.insertArguments( lookup.findStatic(InvokeDynamicSupport.class, "fixnumFallback", fallbackType), 0, site); site.setTarget(myFallback); return site; } Fallback, test, and slow-path (when a non-Fixnum is encountered) logic: public static IRubyObject fixnumFallback(MutableCallSite site, IRubyObject self, ThreadContext context, String name, long value) throws Throwable { RubyFixnum fixnum = context.runtime.newFixnum(value); if (self instanceof RubyFixnum) { String realMethod = MethodIndex.getFastOpsMethod(name); if (realMethod != null) { try { MethodHandle direct = MethodHandles.lookup().findVirtual(RubyFixnum.class, realMethod, MethodType.methodType(IRubyObject.class, ThreadContext.class, long.class)); MethodHandle target = MethodHandles.explicitCastArguments(direct, MethodType.methodType(IRubyObject.class, IRubyObject.class, ThreadContext.class, long.class)); target = MethodHandles.dropArguments(target, 2, String.class); MethodHandle test = MethodHandles.lookup().findStatic(InvokeDynamicSupport.class, "fixnumTest", site.type().changeReturnType(boolean.class)); MethodType fallbackType = site.type().insertParameterTypes(0, MutableCallSite.class); MethodHandle fallback = MethodHandles.insertArguments( MethodHandles.lookup().findStatic(InvokeDynamicSupport.class, "fixnumFallback", fallbackType), 0, site); site.setTarget(MethodHandles.guardWithTest(test, target, fallback)); return self.callMethod(context, name, fixnum); } catch (NoSuchMethodException nsme) { // just fall through } } } else { MethodType slowType = site.type().insertParameterTypes(0, MutableCallSite.class); MethodHandle target = MethodHandles.insertArguments( MethodHandles.lookup().findStatic(InvokeDynamicSupport.class, "fixnumSlow", slowType), 0, fixnum); site.setTarget(target); } return self.callMethod(context, name, fixnum); } public static boolean fixnumTest(IRubyObject self, ThreadContext context, String name, long value) { return self instanceof RubyFixnum; } public static IRubyObject fixnumSlow(IRubyObject fixValue, IRubyObject self, ThreadContext context, String name, long value) throws Throwable { return self.callMethod(context, name, fixValue); } - Charlie _______________________________________________ mlvm-dev mailing list mlvm-dev@openjdk.java.net http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev