Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Dirt simple fixed-arity support in new JIT.
In order to bring the new JIT in line with the old JIT in terms of
performance, this commit makes the JIT emit a second version of a
given method with exactly the number of required arguments rather
than just an array-boxed version. This second handle is used by
CompiledIRMethod and Bootstrap to wire matching-arity calls
straight through to the right target.

This brings the performance of several benchmarks within throwing
distance of the 1.7 JIT.

Caveats:

* No checking for maximum supported arity. We try to patch through
  any number of arguments. This is good, because it means we can
  escape the 0-3 arity splitting for Ruby code, but it also means
  we're missing some verification that there's not too many args
  to handle in the intermediate MethodHandles.
* CompiledIRMethod.call now has overloads for other arities, but
  this is the only benefit to JVM6 support right now. JVM6 will
  need to emit DynamicMethod.call invocations that match 0-3 args
  rather than boxing all the time.
* This is not as generic as I'd like it to be to support multiple
  method entry points. The approach may extend well to methods
  with N..M arguments.

I have only done a bit of testing with this, but many different
fixed-arity and variable-arity methods appear to bind correctly.
  • Loading branch information
headius committed Oct 23, 2014
1 parent fa44bc1 commit 31f7926
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 72 deletions.
Expand Up @@ -4,16 +4,13 @@
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRMetaClassBody;
import org.jruby.ir.IRScope;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.List;

public class CompiledIRMetaClassBody extends CompiledIRMethod {
private final IRMetaClassBody irMetaClassBody;
Expand Down Expand Up @@ -58,7 +55,7 @@ protected void pre(ThreadContext context, IRubyObject self, String name, Block b

@Override
public DynamicMethod dup() {
CompiledIRMetaClassBody x = new CompiledIRMetaClassBody(handle, method, implementationClass);
CompiledIRMetaClassBody x = new CompiledIRMetaClassBody(variable, method, implementationClass);

return x;
}
Expand Down
Expand Up @@ -3,7 +3,6 @@
import org.jruby.RubyModule;
import org.jruby.ir.IRMethod;
import org.jruby.ir.IRScope;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
Expand All @@ -15,28 +14,35 @@
import org.jruby.util.log.LoggerFactory;

import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.List;

import org.jruby.runtime.Helpers;

public class CompiledIRMethod extends JavaMethod implements MethodArgs2, PositionAware {
private static final Logger LOG = LoggerFactory.getLogger("CompiledIRMethod");

protected final MethodHandle handle;
protected final MethodHandle variable;

protected final MethodHandle specific;
protected final int specificArity;

protected final IRScope method;
private final Arity arity;
private String[] parameterList;

public CompiledIRMethod(MethodHandle handle, IRScope method, Visibility visibility, RubyModule implementationClass) {
public CompiledIRMethod(MethodHandle variable, IRScope method, Visibility visibility, RubyModule implementationClass) {
this(variable, null, -1, method, visibility, implementationClass);
}

public CompiledIRMethod(MethodHandle variable, MethodHandle specific, int specificArity, IRScope method, Visibility visibility, RubyModule implementationClass) {
super(implementationClass, visibility, CallConfiguration.FrameNoneScopeNone, method.getName());
this.handle = handle;
this.variable = variable;
this.specific = specific;
this.specificArity = specificArity;
this.method = method;
this.method.getStaticScope().determineModule();
this.arity = calculateArity();

setHandle(handle);
setHandle(variable);
}

public IRScope getIRMethod() {
Expand All @@ -47,6 +53,14 @@ public StaticScope getStaticScope() {
return method.getStaticScope();
}

public MethodHandle getHandleFor(int arity) {
if (specificArity != -1 && arity == specificArity) {
return specific;
}

return null;
}

public String[] getParameterList() {
if (parameterList != null) return parameterList;

Expand Down Expand Up @@ -89,7 +103,71 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
pre(context, self, name, block);

try {
return (IRubyObject)this.handle.invokeExact(context, method.getStaticScope(), self, args, block, implementationClass);
return (IRubyObject)this.variable.invokeExact(context, method.getStaticScope(), self, args, block, implementationClass);
} catch (Throwable t) {
Helpers.throwException(t);
// not reached
return null;
} finally {
post(context);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
if (specificArity != 0) return call(context, self, clazz, name, IRubyObject.NULL_ARRAY, block);
pre(context, self, name, block);

try {
return (IRubyObject)this.specific.invokeExact(context, method.getStaticScope(), self, block, implementationClass);
} catch (Throwable t) {
Helpers.throwException(t);
// not reached
return null;
} finally {
post(context);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
if (specificArity != 1) return call(context, self, clazz, name, Helpers.arrayOf(arg0), block);
pre(context, self, name, block);

try {
return (IRubyObject)this.specific.invokeExact(context, method.getStaticScope(), self, arg0, block, implementationClass);
} catch (Throwable t) {
Helpers.throwException(t);
// not reached
return null;
} finally {
post(context);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
if (specificArity != 2) return call(context, self, clazz, name, Helpers.arrayOf(arg0, arg1), block);
pre(context, self, name, block);

try {
return (IRubyObject)this.specific.invokeExact(context, method.getStaticScope(), self, arg0, arg1, block, implementationClass);
} catch (Throwable t) {
Helpers.throwException(t);
// not reached
return null;
} finally {
post(context);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
if (specificArity != 3) return call(context, self, clazz, name, Helpers.arrayOf(arg0, arg1, arg2), block);
pre(context, self, name, block);

try {
return (IRubyObject)this.specific.invokeExact(context, method.getStaticScope(), self, arg0, arg1, arg2, block, implementationClass);
} catch (Throwable t) {
Helpers.throwException(t);
// not reached
Expand All @@ -105,7 +183,7 @@ public boolean hasExplicitCallProtocol() {

@Override
public DynamicMethod dup() {
return new CompiledIRMethod(handle, method, visibility, implementationClass);
return new CompiledIRMethod(variable, specific, specificArity, method, visibility, implementationClass);
}

public String getFile() {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/IRManager.java
Expand Up @@ -19,7 +19,7 @@
public class IRManager {
public static String SAFE_COMPILER_PASSES = "";
public static String DEFAULT_COMPILER_PASSES = "OptimizeTempVarsPass,LocalOptimizationPass";
public static String DEFAULT_JIT_PASSES = "AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static String DEFAULT_JIT_PASSES = "DeadCodeElimination,AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static String DEFAULT_INLINING_COMPILER_PASSES = "LocalOptimizationPass";

private int dummyMetaClassCount = 0;
Expand Down
21 changes: 21 additions & 0 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Expand Up @@ -1104,6 +1104,14 @@ public static void defCompiledClassMethod(ThreadContext context, MethodHandle ha
obj.callMethod(context, "singleton_method_added", context.runtime.fastNewSymbol(method.getName()));
}

@JIT
public static void defCompiledClassMethod(ThreadContext context, MethodHandle variable, MethodHandle specific, int specificArity, IRScope method, IRubyObject obj) {
RubyClass rubyClass = checkClassForDef(context, method, obj);

rubyClass.addMethod(method.getName(), new CompiledIRMethod(variable, specific, specificArity, method, Visibility.PUBLIC, rubyClass));
obj.callMethod(context, "singleton_method_added", context.runtime.fastNewSymbol(method.getName()));
}

private static RubyClass checkClassForDef(ThreadContext context, IRScope method, IRubyObject obj) {
if (obj instanceof RubyFixnum || obj instanceof RubySymbol) {
throw context.runtime.newTypeError("can't define singleton method \"" + method.getName() + "\" for " + obj.getMetaClass().getBaseName());
Expand Down Expand Up @@ -1140,6 +1148,19 @@ public static void defCompiledInstanceMethod(ThreadContext context, MethodHandle
Helpers.addInstanceMethod(clazz, method.getName(), newMethod, currVisibility, context, runtime);
}

@JIT
public static void defCompiledInstanceMethod(ThreadContext context, MethodHandle variable, MethodHandle specific, int specificArity, IRScope method, DynamicScope currDynScope, IRubyObject self) {
Ruby runtime = context.runtime;
RubyModule clazz = findInstanceMethodContainer(context, currDynScope, self);

Visibility currVisibility = context.getCurrentVisibility();
Visibility newVisibility = Helpers.performNormalMethodChecksAndDetermineVisibility(runtime, clazz, method.getName(), currVisibility);

DynamicMethod newMethod = new CompiledIRMethod(variable, specific, specificArity, method, newVisibility, clazz);

Helpers.addInstanceMethod(clazz, method.getName(), newMethod, currVisibility, context, runtime);
}

@JIT
public static IRubyObject invokeModuleBody(ThreadContext context, DynamicMethod method) {
RubyModule implClass = method.getImplementationClass();
Expand Down
20 changes: 15 additions & 5 deletions core/src/main/java/org/jruby/ir/targets/Bootstrap.java
Expand Up @@ -824,18 +824,28 @@ private static MethodHandle getHandle(RubyClass selfClass, String fallbackName,
if (compiledIRMethod != null) {
assert compiledIRMethod.hasExplicitCallProtocol() : "all jitted methods must have call protocol";

mh = (MethodHandle)compiledIRMethod.getHandle();

binder = SmartBinder.from(site.signature)
.drop("caller");

// IR compiled methods only support varargs right now
if (site.arity == -1) {
// already [], nothing to do
mh = (MethodHandle)compiledIRMethod.getHandle();
} else if (site.arity == 0) {
binder = binder.insert(2, "args", IRubyObject.NULL_ARRAY);
MethodHandle specific;
if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) {
mh = specific;
} else {
mh = (MethodHandle)compiledIRMethod.getHandle();
binder = binder.insert(2, "args", IRubyObject.NULL_ARRAY);
}
} else {
binder = binder.collect("args", "arg.*");
MethodHandle specific;
if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) {
mh = specific;
} else {
mh = (MethodHandle) compiledIRMethod.getHandle();
binder = binder.collect("args", "arg.*");
}
}

if (!block) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/targets/ClassData.java
Expand Up @@ -72,7 +72,7 @@ private static final Type[] typesFromSignature(Signature signature) {
return types;
}

public abstract void pushmethod(String name, IRScope scope, Signature signature);
public abstract void pushmethod(String name, IRScope scope, Signature signature, boolean specificArity);

public void popmethod() {
method().endMethod();
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/ClassData6.java
Expand Up @@ -27,14 +27,15 @@ public ClassData6(String clsName, ClassVisitor cls) {
super(clsName, cls);
}

public void pushmethod(String name, IRScope scope, Signature signature) {
public void pushmethod(String name, IRScope scope, Signature signature, boolean specificArity) {
Method m = new Method(name, Type.getType(signature.type().returnType()), IRRuntimeHelpers.typesFromSignature(signature));
SkinnyMethodAdapter adapter = new SkinnyMethodAdapter(cls, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, m.getName(), m.getDescriptor(), null, null);
methodStack.push(
new MethodData(
new IRBytecodeAdapter6(adapter, signature, this),
scope,
signature)
signature,
specificArity ? scope.getStaticScope().getRequiredArgs() : -1)
);
}
}
5 changes: 3 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/ClassData7.java
Expand Up @@ -23,14 +23,15 @@ public ClassData7(String clsName, ClassVisitor cls) {
super(clsName, cls);
}

public void pushmethod(String name, IRScope scope, Signature signature) {
public void pushmethod(String name, IRScope scope, Signature signature, boolean specificArity) {
Method m = new Method(name, Type.getType(signature.type().returnType()), IRRuntimeHelpers.typesFromSignature(signature));
SkinnyMethodAdapter adapter = new SkinnyMethodAdapter(cls, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, m.getName(), m.getDescriptor(), null, null);
methodStack.push(
new MethodData(
new IRBytecodeAdapter7(adapter, signature, this),
scope,
signature)
signature,
specificArity ? scope.getStaticScope().getRequiredArgs() : -1)
);
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/JVM.java
Expand Up @@ -61,8 +61,8 @@ public IRBytecodeAdapter method() {
return clsData().method();
}

public void pushmethod(String name, IRScope scope, Signature signature) {
clsData().pushmethod(name, scope, signature);
public void pushmethod(String name, IRScope scope, Signature signature, boolean specificArity) {
clsData().pushmethod(name, scope, signature, specificArity);
method().startMethod();
}

Expand Down

0 comments on commit 31f7926

Please sign in to comment.