From cd6c3313e4edaf2b99046926fa0e4eb9b3df934a Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 27 Oct 2014 16:55:17 -0500 Subject: [PATCH] Indy binding for class super plus opto for scopes around super. * Class super now does a direct indy binding * Restore super utility methods JVM6 still needed * Binding deopt is no longer triggered by closure arg to supers * Unresolved super sets frame and binding deopt (TODO: it should not need binding to get block in simple method bodies) Performance of resolved super calls appears to be about 2x faster than 1.7. ```ruby class A def foo self end end class B < A def foo super() end end 10.times do puts Benchmark.measure { b = B.new 10_000_000.times { b.foo; b.foo; b.foo; b.foo; b.foo } } end ``` Results: ``` JRuby 9000 with invokedynamic 3.040000 0.030000 3.070000 ( 1.095000) 0.610000 0.000000 0.610000 ( 0.599000) 0.640000 0.000000 0.640000 ( 0.606000) 0.600000 0.000000 0.600000 ( 0.590000) 0.600000 0.000000 0.600000 ( 0.602000) 0.590000 0.000000 0.590000 ( 0.590000) 0.600000 0.010000 0.610000 ( 0.592000) 0.600000 0.000000 0.600000 ( 0.597000) 0.600000 0.000000 0.600000 ( 0.602000) 0.600000 0.000000 0.600000 ( 0.602000) JRuby 1.7 with invokedynamic 2.660000 0.030000 2.690000 ( 1.384000) 1.070000 0.000000 1.070000 ( 1.064000) 1.180000 0.000000 1.180000 ( 1.119000) 1.110000 0.010000 1.120000 ( 1.109000) 1.100000 0.000000 1.100000 ( 1.101000) 1.120000 0.000000 1.120000 ( 1.119000) 1.120000 0.000000 1.120000 ( 1.116000) 1.120000 0.000000 1.120000 ( 1.114000) 1.100000 0.000000 1.100000 ( 1.101000) 1.100000 0.000000 1.100000 ( 1.100000) MRI 2.1.2 5.240000 0.000000 5.240000 ( 5.241291) 5.250000 0.010000 5.260000 ( 5.249326) 5.220000 0.000000 5.220000 ( 5.228288) 5.270000 0.000000 5.270000 ( 5.265240) 5.270000 0.000000 5.270000 ( 5.278682) 5.280000 0.010000 5.290000 ( 5.276591) 5.240000 0.000000 5.240000 ( 5.247407) 5.240000 0.000000 5.240000 ( 5.236135) 5.220000 0.000000 5.220000 ( 5.221761) 5.260000 0.000000 5.260000 ( 5.264340) Rubinius 2.2.10 2.773542 0.007273 2.780815 ( 2.750797) 2.170249 0.001253 2.171502 ( 2.171655) 2.172850 0.003060 2.175910 ( 2.176082) 2.188119 0.002913 2.191032 ( 2.191586) 2.145197 0.000986 2.146183 ( 2.146217) 2.127599 0.000201 2.127800 ( 2.127831) 2.163769 0.001115 2.164884 ( 2.164966) 2.154462 0.002307 2.156769 ( 2.156858) 2.146972 0.000304 2.147276 ( 2.147446) 2.153281 0.000254 2.153535 ( 2.153569) ``` --- .../org/jruby/ir/instructions/CallBase.java | 14 ++--- .../ir/instructions/UnresolvedSuperInstr.java | 9 ++++ .../jruby/ir/runtime/IRRuntimeHelpers.java | 10 ++++ .../ir/targets/ClassSuperInvokeSite.java | 31 ++--------- .../ir/targets/InstanceSuperInvokeSite.java | 44 +--------------- .../org/jruby/ir/targets/SuperInvokeSite.java | 52 ++++++++++++++++++- 6 files changed, 83 insertions(+), 77 deletions(-) diff --git a/core/src/main/java/org/jruby/ir/instructions/CallBase.java b/core/src/main/java/org/jruby/ir/instructions/CallBase.java index 3db6bc1b71b..be1fc1e68be 100644 --- a/core/src/main/java/org/jruby/ir/instructions/CallBase.java +++ b/core/src/main/java/org/jruby/ir/instructions/CallBase.java @@ -131,6 +131,10 @@ public boolean hasClosure() { return closure != null; } + public boolean hasLiteralClosure() { + return closure instanceof WrappedIRClosure; + } + public boolean isAllConstants() { for (Operand argument : arguments) { if (!(argument instanceof ImmutableLiteral)) return false; @@ -287,9 +291,8 @@ private boolean computeEvalFlag() { private boolean computeRequiresCallersBindingFlag() { if (canBeEval()) return true; - // Conservative -- assuming that the callee will save the closure - // and use it at a later point. - if (closure != null) return true; + // literal closures can be used to capture surrounding binding + if (hasLiteralClosure()) return true; String mname = getMethodAddr().getName(); if (MethodIndex.SCOPE_AWARE_METHODS.contains(mname)) { @@ -345,9 +348,8 @@ private boolean computeRequiresCallersBindingFlag() { private boolean computeRequiresCallersFrameFlag() { if (canBeEval()) return true; - // Conservative -- assuming that the callee will save the closure - // and use it at a later point. - if (closure != null) return true; + // literal closures can be used to capture surrounding binding + if (hasLiteralClosure()) return true; if (procNew) return true; diff --git a/core/src/main/java/org/jruby/ir/instructions/UnresolvedSuperInstr.java b/core/src/main/java/org/jruby/ir/instructions/UnresolvedSuperInstr.java index d91e69101b8..23eefa32b64 100644 --- a/core/src/main/java/org/jruby/ir/instructions/UnresolvedSuperInstr.java +++ b/core/src/main/java/org/jruby/ir/instructions/UnresolvedSuperInstr.java @@ -1,5 +1,7 @@ package org.jruby.ir.instructions; +import org.jruby.ir.IRFlags; +import org.jruby.ir.IRScope; import org.jruby.ir.IRVisitor; import org.jruby.ir.Operation; import org.jruby.ir.operands.MethAddr; @@ -24,6 +26,13 @@ public UnresolvedSuperInstr(Variable result, Operand receiver, Operand[] args, O this(Operation.UNRESOLVED_SUPER, result, receiver, args, closure); } + @Override + public boolean computeScopeFlags(IRScope scope) { + scope.getFlags().add(IRFlags.REQUIRES_FRAME); // for current class and method name + scope.getFlags().add(IRFlags.REQUIRES_DYNSCOPE); // for current class and method name + return true; + } + @Override public Instr clone(CloneInfo ii) { return new UnresolvedSuperInstr(ii.getRenamedVariable(getResult()), getReceiver().cloneForInlining(ii), cloneCallArgs(ii), closure == null ? null : closure.cloneForInlining(ii)); diff --git a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java index 758352633ee..9be0a92d906 100644 --- a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java +++ b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java @@ -783,6 +783,11 @@ public static IRubyObject setCapturedVar(ThreadContext context, IRubyObject matc return val; } + @JIT // for JVM6 + public static IRubyObject instanceSuperSplatArgs(ThreadContext context, IRubyObject self, String methodName, RubyModule definingModule, IRubyObject[] args, Block block, boolean[] splatMap) { + return instanceSuper(context, self, methodName, definingModule, splatArguments(args, splatMap), block); + } + @Interp public static IRubyObject instanceSuper(ThreadContext context, IRubyObject self, String methodName, RubyModule definingModule, IRubyObject[] args, Block block) { RubyClass superClass = definingModule.getSuperClass(); @@ -792,6 +797,11 @@ public static IRubyObject instanceSuper(ThreadContext context, IRubyObject self, return rVal; } + @JIT // for JVM6 + public static IRubyObject classSuperSplatArgs(ThreadContext context, IRubyObject self, String methodName, RubyModule definingModule, IRubyObject[] args, Block block, boolean[] splatMap) { + return classSuper(context, self, methodName, definingModule, splatArguments(args, splatMap), block); + } + @Interp public static IRubyObject classSuper(ThreadContext context, IRubyObject self, String methodName, RubyModule definingModule, IRubyObject[] args, Block block) { RubyClass superClass = definingModule.getMetaClass().getSuperClass(); diff --git a/core/src/main/java/org/jruby/ir/targets/ClassSuperInvokeSite.java b/core/src/main/java/org/jruby/ir/targets/ClassSuperInvokeSite.java index dcbd521480c..66bdf4d8e3e 100644 --- a/core/src/main/java/org/jruby/ir/targets/ClassSuperInvokeSite.java +++ b/core/src/main/java/org/jruby/ir/targets/ClassSuperInvokeSite.java @@ -11,7 +11,9 @@ import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.callsite.CacheEntry; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.lang.invoke.SwitchPoint; import static org.jruby.ir.runtime.IRRuntimeHelpers.splatArguments; @@ -23,32 +25,7 @@ public ClassSuperInvokeSite(MethodType type, String name, String splatmapString) super(type, name, splatmapString); } - public IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { - return fail(context, caller, self, definingModule, args, block); - } - - public IRubyObject fail(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { - // TODO: get rid of caller - // TODO: mostly copy of org.jruby.ir.targets.InstanceSuperInvokeSite - - RubyClass superClass = definingModule.getMetaClass().getMetaClass().getSuperClass(); - String name = methodName; - CacheEntry entry = cache; - - if (entry.typeOk(superClass)) { - return entry.method.call(context, self, superClass, name, splatArguments(args, splatMap), block); - } - - entry = superClass != null ? superClass.searchWithCache(name) : CacheEntry.NULL_CACHE; - - DynamicMethod method = entry.method; - - if (method.isUndefined()) { - return Helpers.callMethodMissing(context, self, method.getVisibility(), name, callType, splatArguments(args, splatMap), block); - } - - cache = entry; - - return method.call(context, self, superClass, name, splatArguments(args, splatMap), block); + protected RubyClass getSuperClass(RubyClass definingModule) { + return definingModule.getMetaClass().getMetaClass().getSuperClass(); } } diff --git a/core/src/main/java/org/jruby/ir/targets/InstanceSuperInvokeSite.java b/core/src/main/java/org/jruby/ir/targets/InstanceSuperInvokeSite.java index 4879079f2b9..e2621205df2 100644 --- a/core/src/main/java/org/jruby/ir/targets/InstanceSuperInvokeSite.java +++ b/core/src/main/java/org/jruby/ir/targets/InstanceSuperInvokeSite.java @@ -26,47 +26,7 @@ public InstanceSuperInvokeSite(MethodType type, String name, String splatmapStri super(type, name, splatmapString); } - public IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { - // TODO: mostly copy of org.jruby.ir.targets.InvokeSite because of different target class logic - - RubyClass selfClass = pollAndGetClass(context, self); - RubyClass superClass = definingModule.getSuperClass(); - SwitchPoint switchPoint = (SwitchPoint) superClass.getInvalidator().getData(); - CacheEntry entry = superClass.searchWithCache(methodName); - DynamicMethod method = entry.method; - - if (methodMissing(entry, caller)) { - return callMethodMissing(entry, callType, context, self, methodName, args, block); - } - - MethodHandle mh = getHandle(superClass, this, method); - - updateInvocationTarget(mh, self, selfClass, entry, switchPoint); - - return method.call(context, self, superClass, methodName, args, block); - } - - public IRubyObject fail(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { - // TODO: get rid of caller - - RubyClass superClass = definingModule.getSuperClass(); - String name = methodName; - CacheEntry entry = cache; - - if (entry.typeOk(superClass)) { - return entry.method.call(context, self, superClass, name, splatArguments(args, splatMap), block); - } - - entry = superClass != null ? superClass.searchWithCache(name) : CacheEntry.NULL_CACHE; - - DynamicMethod method = entry.method; - - if (method.isUndefined()) { - return Helpers.callMethodMissing(context, self, method.getVisibility(), methodName, callType, splatArguments(args, splatMap), block); - } - - cache = entry; - - return method.call(context, self, superClass, methodName, splatArguments(args, splatMap), block); + protected RubyClass getSuperClass(RubyClass definingModule) { + return definingModule.getSuperClass(); } } diff --git a/core/src/main/java/org/jruby/ir/targets/SuperInvokeSite.java b/core/src/main/java/org/jruby/ir/targets/SuperInvokeSite.java index 11db8dba5b1..17ebd3dd079 100644 --- a/core/src/main/java/org/jruby/ir/targets/SuperInvokeSite.java +++ b/core/src/main/java/org/jruby/ir/targets/SuperInvokeSite.java @@ -3,9 +3,11 @@ import com.headius.invokebinder.Binder; import com.headius.invokebinder.SmartBinder; import org.jruby.RubyClass; +import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.ir.runtime.IRRuntimeHelpers; import org.jruby.runtime.Block; import org.jruby.runtime.CallType; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.callsite.CacheEntry; @@ -17,7 +19,9 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.SwitchPoint; +import static org.jruby.ir.runtime.IRRuntimeHelpers.splatArguments; import static org.jruby.runtime.Helpers.arrayOf; import static org.jruby.util.CodegenUtils.p; import static org.jruby.util.CodegenUtils.sig; @@ -64,7 +68,51 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, Metho return InvokeSite.bootstrap(site, lookup); } - public abstract IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable; + public IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { + // TODO: mostly copy of org.jruby.ir.targets.InvokeSite because of different target class logic - public abstract IRubyObject fail(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable; + RubyClass selfClass = pollAndGetClass(context, self); + RubyClass superClass = getSuperClass(definingModule); + SwitchPoint switchPoint = (SwitchPoint) superClass.getInvalidator().getData(); + CacheEntry entry = superClass.searchWithCache(methodName); + DynamicMethod method = entry.method; + + if (methodMissing(entry, caller)) { + return callMethodMissing(entry, callType, context, self, methodName, args, block); + } + + MethodHandle mh = getHandle(superClass, this, method); + + updateInvocationTarget(mh, self, selfClass, entry, switchPoint); + + return method.call(context, self, superClass, methodName, args, block); + } + + public IRubyObject fail(ThreadContext context, IRubyObject caller, IRubyObject self, RubyClass definingModule, IRubyObject[] args, Block block) throws Throwable { + // TODO: get rid of caller + + context.callThreadPoll(); + + RubyClass superClass = getSuperClass(definingModule); + String name = methodName; + CacheEntry entry = cache; + + if (entry.typeOk(superClass)) { + return entry.method.call(context, self, superClass, name, splatArguments(args, splatMap), block); + } + + entry = superClass != null ? superClass.searchWithCache(name) : CacheEntry.NULL_CACHE; + + DynamicMethod method = entry.method; + + if (method.isUndefined()) { + return Helpers.callMethodMissing(context, self, method.getVisibility(), name, callType, splatArguments(args, splatMap), block); + } + + cache = entry; + + return method.call(context, self, superClass, name, splatArguments(args, splatMap), block); + } + + protected abstract RubyClass getSuperClass(RubyClass definingModule); } \ No newline at end of file