Skip to content

Commit

Permalink
Indy binding for class super plus opto for scopes around super.
Browse files Browse the repository at this point in the history
* 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)
```
  • Loading branch information
headius committed Oct 27, 2014
1 parent 5e7fd54 commit cd6c331
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 77 deletions.
14 changes: 8 additions & 6 deletions core/src/main/java/org/jruby/ir/instructions/CallBase.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;

Expand Down
@@ -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;
Expand All @@ -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));
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Expand Up @@ -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();
Expand All @@ -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();
Expand Down
31 changes: 4 additions & 27 deletions core/src/main/java/org/jruby/ir/targets/ClassSuperInvokeSite.java
Expand Up @@ -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;

Expand All @@ -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();
}
}
Expand Up @@ -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();
}
}
52 changes: 50 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/SuperInvokeSite.java
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

0 comments on commit cd6c331

Please sign in to comment.