Skip to content

Commit

Permalink
First pass at making dyncalls from Java use call site caching.
Browse files Browse the repository at this point in the history
This introduces a new class, JavaCallSites, which holds per-
runtime call sites and function objects that cache better than our
per-class caches. This improves the performance of core methods
implemented with Java that need to make dynamic calls. This is a
first step toward eventually getting those calls to use
invokedynamic, which will require bytecode manpulation.

This first commit also reworks Ruby.safeRecurse to accept a richer
function object that also receives context and a generic state
object. This will allow most safe-recursion guards to work without
any allocation.
headius committed Jul 19, 2016
1 parent 22ea1be commit 0a4aa21
Showing 13 changed files with 239 additions and 54 deletions.
40 changes: 29 additions & 11 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@
import org.jruby.javasupport.JavaSupportImpl;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.JavaCallSites;
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
import org.jruby.util.ClassDefiningClassLoader;
import org.objectweb.asm.util.TraceClassVisitor;
@@ -159,7 +160,6 @@
import org.objectweb.asm.ClassReader;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -4310,7 +4310,11 @@ public void unregisterInspecting(Object obj) {
if (val != null ) val.remove(obj);
}

public static interface RecursiveFunction {
public interface RecursiveFunctionEx<T> {
IRubyObject call(ThreadContext context, T state, IRubyObject obj, boolean recur);
}

public interface RecursiveFunction {
IRubyObject call(IRubyObject obj, boolean recur);
}

@@ -4467,7 +4471,7 @@ private IRubyObject execRecursiveInternal(RecursiveFunction func, IRubyObject ob

ThreadLocal<Map<String, Map<IRubyObject, IRubyObject>>> symMap = new ThreadLocal<>();

public IRubyObject safeRecurse(RecursiveFunction func, IRubyObject obj, String name, boolean outer) {
public <T> IRubyObject safeRecurse(RecursiveFunctionEx<T> func, ThreadContext context, T state, IRubyObject obj, String name, boolean outer) {
Map<IRubyObject, IRubyObject> guards = safeRecurseGetGuards(name);

boolean outermost = outer && !guards.containsKey(recursiveKey);
@@ -4477,29 +4481,29 @@ public IRubyObject safeRecurse(RecursiveFunction func, IRubyObject obj, String n
if (outer && !outermost) {
throw new RecursiveError(guards);
}
return func.call(obj, true);
return func.call(context, state, obj, true);
} else {
if (outermost) {
return safeRecurseOutermost(func, obj, guards);
return safeRecurseOutermost(func, context, state, obj, guards);
} else {
return safeRecurseInner(func, obj, guards);
return safeRecurseInner(func, context, state, obj, guards);
}
}
}

private IRubyObject safeRecurseOutermost(RecursiveFunction func, IRubyObject obj, Map<IRubyObject, IRubyObject> guards) {
private <T> IRubyObject safeRecurseOutermost(RecursiveFunctionEx<T> func, ThreadContext context, T state, IRubyObject obj, Map<IRubyObject, IRubyObject> guards) {
boolean recursed = false;
guards.put(recursiveKey, recursiveKey);

try {
return safeRecurseInner(func, obj, guards);
return safeRecurseInner(func, context, state, obj, guards);
} catch (RecursiveError re) {
if (re.tag != guards) {
throw re;
}
recursed = true;
guards.remove(recursiveKey);
return func.call(obj, true);
return func.call(context, state, obj, true);
} finally {
if (!recursed) guards.remove(recursiveKey);
}
@@ -4519,10 +4523,10 @@ private Map<IRubyObject, IRubyObject> safeRecurseGetGuards(String name) {
} return guards;
}

private IRubyObject safeRecurseInner(RecursiveFunction func, IRubyObject obj, Map<IRubyObject, IRubyObject> guards) {
private <T> IRubyObject safeRecurseInner(RecursiveFunctionEx<T> func, ThreadContext context, T state, IRubyObject obj, Map<IRubyObject, IRubyObject> guards) {
try {
guards.put(obj, obj);
return func.call(obj, false);
return func.call(context, state, obj, false);
} finally {
guards.remove(obj);
}
@@ -5087,6 +5091,18 @@ public FilenoUtil getFilenoUtil() {
return filenoUtil;
}

@Deprecated
public IRubyObject safeRecurse(RecursiveFunction func, IRubyObject obj, String name, boolean outer) {
return safeRecurse(LEGACY_RECURSE, getCurrentContext(), func, obj, name, outer);
}

private static final RecursiveFunctionEx<RecursiveFunction> LEGACY_RECURSE = new RecursiveFunctionEx<RecursiveFunction>() {
@Override
public IRubyObject call(ThreadContext context, RecursiveFunction func, IRubyObject obj, boolean recur) {
return func.call(obj, recur);
}
};

private final ConcurrentHashMap<String, Invalidator> constantNameInvalidators =
new ConcurrentHashMap<String, Invalidator>(
16 /* default initial capacity */,
@@ -5417,4 +5433,6 @@ protected TypePopulator computeValue(Class<?> type) {
return RubyModule.loadPopulatorFor(type);
}
};

public final JavaCallSites sites = new JavaCallSites();
}
9 changes: 5 additions & 4 deletions core/src/main/java/org/jruby/RubyArray.java
Original file line number Diff line number Diff line change
@@ -1885,19 +1885,20 @@ private RubyString joinAny(ThreadContext context, IRubyObject obj, RubyString se

private void recursiveJoin(final ThreadContext context, final IRubyObject outValue,
final RubyString sep, final RubyString result, final IRubyObject ary) {
final Ruby runtime = context.runtime;

Ruby runtime = context.runtime;

if (ary == this) throw runtime.newArgumentError("recursive array join");

runtime.safeRecurse(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
runtime.safeRecurse(new Ruby.RecursiveFunctionEx<Ruby>() {
public IRubyObject call(ThreadContext context, Ruby runtime, IRubyObject obj, boolean recur) {
if (recur) throw runtime.newArgumentError("recursive array join");

RubyArray recAry = ((RubyArray) ary);
recAry.joinAny(context, outValue, sep, 0, result);

return runtime.getNil();
}}, outValue, "join", true);
}}, context, runtime, outValue, "join", true);
}

/** rb_ary_join
18 changes: 12 additions & 6 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@
import org.jcodings.Encoding;
import org.jruby.ir.interpreter.Interpreter;
import org.jruby.runtime.Constants;
import org.jruby.runtime.JavaCallSites;
import org.jruby.runtime.ivars.VariableAccessor;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -579,7 +580,7 @@ public final boolean respondsTo(String name) {

// respond_to? or respond_to_missing? is not defined, so we must dispatch to trigger method_missing
if ( respondToUndefined ) {
return callMethod(context, "respond_to?", mname).isTrue();
return runtime.sites.BO_respond_to.call(context, this, this, mname).isTrue();
}

// respond_to? is defined, invoke already-retrieved method object
@@ -728,7 +729,10 @@ public RubyInteger convertToInteger(String convertMethod) {
*/
@Override
public RubyString convertToString() {
return (RubyString) TypeConverter.convertToType(this, getRuntime().getString(), "to_str");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaCallSites sites = context.sites;
return (RubyString) TypeConverter.convertToType(context, this, getRuntime().getString(), sites.STR_respond_to_to_str, sites.STR_to_str);
}

/**
@@ -828,7 +832,7 @@ public IRubyObject dup() {
IRubyObject dup = getMetaClass().getRealClass().allocate();
if (isTaint()) dup.setTaint(true);

initCopy(dup, this, "initialize_dup");
initCopy(runtime.getCurrentContext(), dup, this, false);

return dup;
}
@@ -838,7 +842,7 @@ public IRubyObject dup() {
* Initializes a copy with variable and special instance variable
* information, and then call the initialize_copy Ruby method.
*/
private static void initCopy(IRubyObject clone, IRubyObject original, String method) {
private static IRubyObject initCopy(ThreadContext context, IRubyObject clone, IRubyObject original, boolean doClone) {
assert !clone.isFrozen() : "frozen object (" + clone.getMetaClass().getName() + ") allocated";

original.copySpecialInstanceVariables(clone);
@@ -851,7 +855,9 @@ private static void initCopy(IRubyObject clone, IRubyObject original, String met
}

/* FIXME: finalizer should be dupped here */
clone.callMethod(clone.getRuntime().getCurrentContext(), method, original);
return doClone ?
context.sites.BO_initialize_dup.call(context, clone, clone, original) :
context.sites.BO_initialize_clone.call(context, clone, clone, original);
}

protected static boolean OBJ_INIT_COPY(IRubyObject obj, IRubyObject orig) {
@@ -902,7 +908,7 @@ public IRubyObject rbClone() {
clone.setMetaClass(getSingletonClassClone());
if (isTaint()) clone.setTaint(true);

initCopy(clone, this, "initialize_clone");
initCopy(runtime.getCurrentContext(), clone, this, true);

if (isFrozen()) clone.setFrozen(true);
return clone;
33 changes: 26 additions & 7 deletions core/src/main/java/org/jruby/RubyComparable.java
Original file line number Diff line number Diff line change
@@ -35,8 +35,10 @@

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.RespondToCallSite;

import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_CMP;
@@ -106,14 +108,23 @@ public static IRubyObject cmperr(IRubyObject recv, IRubyObject other) {
*
*/
public static IRubyObject invcmp(final ThreadContext context, final IRubyObject recv, final IRubyObject other) {
return invcmp(context, DEFAULT_INVCMP, recv, other);
}

private static final Ruby.RecursiveFunctionEx DEFAULT_INVCMP = new Ruby.RecursiveFunctionEx<IRubyObject>() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject recv, IRubyObject other, boolean recur) {
if (recur || !other.respondsTo("<=>")) return context.runtime.getNil();
return invokedynamic(context, other, OP_CMP, recv);
}
};

/** rb_invcmp
*
*/
public static IRubyObject invcmp(final ThreadContext context, Ruby.RecursiveFunctionEx func, IRubyObject recv, IRubyObject other) {
final Ruby runtime = context.runtime;
IRubyObject result = runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
@Override
public IRubyObject call(IRubyObject obj, boolean recur) {
if (recur || !other.respondsTo("<=>")) return context.runtime.getNil();
return invokedynamic(context, other, OP_CMP, recv);
}
}, recv);
IRubyObject result = runtime.safeRecurse(func, context, recv, other, "<=>", true);

if (result.isNil()) return result;
return RubyFixnum.newFixnum(runtime, -cmpint(context, result, recv, other));
@@ -194,6 +205,14 @@ public static RubyBoolean op_lt(ThreadContext context, IRubyObject recv, IRubyOb
return RubyBoolean.newBoolean(context.runtime, cmpint(context, result, recv, other) < 0);
}

public static RubyBoolean op_lt(ThreadContext context, CallSite cmp, IRubyObject recv, IRubyObject other) {
IRubyObject result = cmp.call(context, recv, recv, other);

if (result.isNil()) cmperr(recv, other);

return RubyBoolean.newBoolean(context.runtime, cmpint(context, result, recv, other) < 0);
}

/** cmp_le
*
*/
16 changes: 8 additions & 8 deletions core/src/main/java/org/jruby/RubyString.java
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@
import org.jruby.platform.Platform;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.JavaCallSites;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
@@ -67,7 +68,6 @@
import org.jruby.runtime.encoding.EncodingCapable;
import org.jruby.runtime.encoding.MarshalEncoding;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.specialized.RubyArrayTwoObject;
import org.jruby.util.*;
import org.jruby.util.io.EncodingUtils;

@@ -81,7 +81,6 @@
import static org.jruby.anno.FrameField.BACKREF;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_EQUAL;
import static org.jruby.util.StringSupport.CR_7BIT;
import static org.jruby.util.StringSupport.CR_BROKEN;
import static org.jruby.util.StringSupport.CR_MASK;
@@ -1094,12 +1093,13 @@ public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
if (other instanceof RubyString) {
return runtime.newFixnum(op_cmp((RubyString)other));
}
if (other.respondsTo("to_str")) {
IRubyObject tmp = other.callMethod(context, "to_str");
JavaCallSites sites = runtime.sites;
if (sites.STR_respond_to_to_str.respondsTo(context, this, other)) {
IRubyObject tmp = TypeConverter.checkStringType(context, sites.STR_respond_to_to_str, sites.STR_to_str, other);
if (tmp instanceof RubyString)
return runtime.newFixnum(op_cmp((RubyString)tmp));
} else {
return invcmp(context, this, other);
return invcmp(context, sites.STR_recursive_cmp, this, other);
}
return runtime.getNil();
}
@@ -1125,8 +1125,8 @@ public IRubyObject op_equal19(ThreadContext context, IRubyObject other) {

private IRubyObject op_equalCommon(ThreadContext context, IRubyObject other) {
Ruby runtime = context.runtime;
if (!other.respondsTo("to_str")) return runtime.getFalse();
return invokedynamic(context, other, OP_EQUAL, this).isTrue() ? runtime.getTrue() : runtime.getFalse();
if (!runtime.sites.STR_respond_to_to_str.respondsTo(context, this, other)) return runtime.getFalse();
return runtime.sites.STR_equals.call(context, this, other, this).isTrue() ? runtime.getTrue() : runtime.getFalse();
}

@JRubyMethod(name = "-@") // -'foo' returns frozen string
@@ -1723,7 +1723,7 @@ public IRubyObject op_lt(ThreadContext context, IRubyObject other) {
@JRubyMethod(name = "<")
public IRubyObject op_lt19(ThreadContext context, IRubyObject other) {
if (other instanceof RubyString) return context.runtime.newBoolean(op_cmp((RubyString) other) < 0);
return RubyComparable.op_lt(context, this, other);
return RubyComparable.op_lt(context, context.sites.STR_cmp, this, other);
}

public IRubyObject str_eql_p(ThreadContext context, IRubyObject other) {
15 changes: 7 additions & 8 deletions core/src/main/java/org/jruby/RubyStruct.java
Original file line number Diff line number Diff line change
@@ -50,8 +50,6 @@
import org.jruby.util.ByteList;
import org.jruby.util.IdUtil;

import java.util.concurrent.Callable;

import static org.jruby.RubyEnumerator.enumeratorizeWithSize;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.Visibility.PRIVATE;
@@ -569,15 +567,16 @@ else if (first != '#') {
@JRubyMethod(name = {"inspect", "to_s"})
public RubyString inspect(final ThreadContext context) {
final Ruby runtime = context.runtime;
final RubyStruct struct = this;
// recursion guard
return (RubyString) runtime.safeRecurse(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
return inspectStruct(context, recur);
}
}, struct, "inspect", false);
return (RubyString) runtime.safeRecurse(RECURSIVE_INSPECT, context, this, this, "inspect", false);
}

private static final Ruby.RecursiveFunctionEx RECURSIVE_INSPECT = new Ruby.RecursiveFunctionEx<RubyStruct>() {
public IRubyObject call(ThreadContext context, RubyStruct self, IRubyObject obj, boolean recur) {
return self.inspectStruct(context, recur);
}
};

@JRubyMethod(name = {"to_a", "values"})
@Override
public RubyArray to_a() {
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyTime.java
Original file line number Diff line number Diff line change
@@ -693,7 +693,7 @@ public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
return context.runtime.newFixnum(cmp((RubyTime) other));
}

return invcmp(context, this, other);
return invcmp(context, context.sites.TIME_recursive_cmp, this, other);
}

@JRubyMethod(name = "eql?", required = 1)
17 changes: 9 additions & 8 deletions core/src/main/java/org/jruby/runtime/Helpers.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.jruby.runtime;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
@@ -2735,13 +2734,8 @@ public static long hashEnd(long value) {

// MRI: rb_hash
public static RubyFixnum safeHash(final ThreadContext context, IRubyObject obj) {
final Ruby runtime = context.runtime;
IRubyObject hval = runtime.safeRecurse(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
if (recur) return RubyFixnum.zero(runtime);
return invokedynamic(context, obj, HASH);
}
}, obj, "hash", true);
Ruby runtime = context.runtime;
IRubyObject hval = runtime.safeRecurse(RECURSIVE_HASH, context, runtime, obj, "hash", true);

while (!(hval instanceof RubyFixnum)) {
if (hval instanceof RubyBignum) {
@@ -2754,6 +2748,13 @@ public IRubyObject call(IRubyObject obj, boolean recur) {
return (RubyFixnum) hval;
}

private static final Ruby.RecursiveFunctionEx<Ruby> RECURSIVE_HASH = new Ruby.RecursiveFunctionEx<Ruby>() {
public IRubyObject call(ThreadContext context, Ruby runtime, IRubyObject obj, boolean recur) {
if (recur) return RubyFixnum.zero(runtime);
return context.sites.STR_hash.call(context, obj, obj);
}
};

public static long murmurCombine(long h, long i)
{
long v = 0;
43 changes: 43 additions & 0 deletions core/src/main/java/org/jruby/runtime/JavaCallSites.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.jruby.runtime;

import org.jruby.Ruby;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.FunctionalCachingCallSite;
import org.jruby.runtime.callsite.RespondToCallSite;

import static org.jruby.runtime.invokedynamic.MethodNames.OP_CMP;

/**
* A collection of all call sites used for dynamic calls from JRuby's Java code.
*/
public class JavaCallSites {
public final CallSite BO_respond_to = new FunctionalCachingCallSite("respond_to?");
public final CallSite BO_initialize_dup = new FunctionalCachingCallSite("initialize_dup");
public final CallSite BO_initialize_clone = new FunctionalCachingCallSite("initialize_clone");

public final RespondToCallSite STR_respond_to_to_str = new RespondToCallSite("to_str");
public final RespondToCallSite STR_respond_to_cmp = new RespondToCallSite("<=>");
public final CallSite STR_to_str = new FunctionalCachingCallSite("to_str");
public final CallSite STR_equals = new FunctionalCachingCallSite("==");
public final CallSite STR_cmp = new FunctionalCachingCallSite("<=>");
public final CallSite STR_hash = new FunctionalCachingCallSite("hash");

public final RespondToCallSite TIME_respond_to_cmp = new RespondToCallSite("<=>");
public final CallSite TIME_cmp = new FunctionalCachingCallSite("<=>");

public final Ruby.RecursiveFunctionEx STR_recursive_cmp = new Ruby.RecursiveFunctionEx<IRubyObject>() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject recv, IRubyObject other, boolean recur) {
if (recur || !context.sites.STR_respond_to_cmp.respondsTo(context, other, other)) return context.nil;
return context.sites.STR_cmp.call(context, other, other, recv);
}
};

public final Ruby.RecursiveFunctionEx TIME_recursive_cmp = new Ruby.RecursiveFunctionEx<IRubyObject>() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject recv, IRubyObject other, boolean recur) {
if (recur || !context.sites.TIME_respond_to_cmp.respondsTo(context, other, other)) return context.nil;
return context.sites.TIME_cmp.call(context, other, other, recv);
}
};
}
4 changes: 3 additions & 1 deletion core/src/main/java/org/jruby/runtime/ThreadContext.java
Original file line number Diff line number Diff line change
@@ -64,7 +64,6 @@

import java.lang.ref.WeakReference;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Locale;
import java.util.Set;

@@ -142,6 +141,8 @@ public static ThreadContext newContext(Ruby runtime) {

private static boolean trySHA1PRNG = true;

public final JavaCallSites sites;

@SuppressWarnings("deprecated")
public SecureRandom getSecureRandom() {
SecureRandom secureRandom = this.secureRandom;
@@ -177,6 +178,7 @@ private ThreadContext(Ruby runtime) {
}

this.runtimeCache = runtime.getRuntimeCache();
this.sites = runtime.sites;

// TOPLEVEL self and a few others want a top-level scope. We create this one right
// away and then pass it into top-level parse so it ends up being the top level.
Original file line number Diff line number Diff line change
@@ -249,6 +249,15 @@ public IRubyObject callIter(ThreadContext context, IRubyObject caller, IRubyObje
}
}

public CacheEntry retrieveCache(RubyClass selfType) {
// This must be retrieved *once* to avoid racing with other threads.
CacheEntry cache = this.cache;
if (CacheEntry.typeOk(cache, selfType)) {
return cache;
}
return cacheAndGet(selfType, methodName);
}

public CacheEntry retrieveCache(RubyClass selfType, String methodName) {
// This must be retrieved *once* to avoid racing with other threads.
CacheEntry cache = this.cache;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jruby.runtime.callsite;

import org.jruby.Ruby;
import org.jruby.RubySymbol;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.RubyClass;
@@ -9,6 +10,8 @@

public class RespondToCallSite extends NormalCachingCallSite {
private volatile RespondToTuple respondToTuple = RespondToTuple.NULL_CACHE;
private final String respondToName;
private RubySymbol respondToNameSym;

private static class RespondToTuple {
static final RespondToTuple NULL_CACHE = new RespondToTuple("", true, CacheEntry.NULL_CACHE, CacheEntry.NULL_CACHE, null);
@@ -33,6 +36,12 @@ public boolean cacheOk(RubyClass klass) {

public RespondToCallSite() {
super("respond_to?");
respondToName = null;
}

public RespondToCallSite(String name) {
super("respond_to?");
respondToName = name;
}

@Override
@@ -59,6 +68,25 @@ public IRubyObject call(ThreadContext context, IRubyObject caller, IRubyObject s
return super.call(context, caller, self, name, bool);
}

public boolean respondsTo(ThreadContext context, IRubyObject caller, IRubyObject self) {
RubyClass klass = self.getMetaClass();
RespondToTuple tuple = respondToTuple;
if (tuple.cacheOk(klass)) {
String strName = respondToName;
if (strName.equals(tuple.name) && tuple.checkVisibility) return tuple.respondsTo.isTrue();
}
// go through normal call logic, which will hit overridden cacheAndCall
return super.call(context, caller, self, getRespondToNameSym(context)).isTrue();
}

private RubySymbol getRespondToNameSym(ThreadContext context) {
RubySymbol sym = respondToNameSym;
if (sym == null) {
respondToNameSym = sym = context.runtime.newSymbol(respondToName);
}
return sym;
}

@Override
protected IRubyObject cacheAndCall(IRubyObject caller, RubyClass selfType, ThreadContext context, IRubyObject self, IRubyObject arg) {
CacheEntry entry = selfType.searchWithCache(methodName);
59 changes: 59 additions & 0 deletions core/src/main/java/org/jruby/util/TypeConverter.java
Original file line number Diff line number Diff line change
@@ -43,10 +43,13 @@
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.ClassIndex;
import org.jruby.RubyNil;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.callsite.RespondToCallSite;

public class TypeConverter {

@@ -76,6 +79,23 @@ public static IRubyObject convertToType(IRubyObject obj, RubyClass target, Strin
return obj.callMethod(obj.getRuntime().getCurrentContext(), convertMethod);
}

public static IRubyObject convertToType(ThreadContext context, IRubyObject obj, RubyClass target, RespondToCallSite respondSite, CallSite site, boolean raise) {
if (!respondSite.respondsTo(context, obj, obj)) {
switch (site.methodName) {
case "to_int" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_ary" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_str" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_sym" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_hash" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_proc" : return handleImplicitlyUncoercibleObject(raise, obj, target);
case "to_io" : return handleImplicitlyUncoercibleObject(raise, obj, target);
}
return handleUncoercibleObject(raise, obj, target);
}

return site.call(context, obj, obj);
}

/**
* Converts this object to type 'targetType' using 'convertMethod' method (MRI: convert_type 1.9).
*
@@ -106,6 +126,21 @@ public static IRubyObject convertToType(IRubyObject obj, RubyClass target, Strin
return val;
}

/**
* Converts this object to type 'targetType' using 'convertMethod' method and raises TypeError exception on failure (MRI: rb_convert_type).
*
* @param obj the object to convert
* @param target is the type we are trying to convert to
* @param site is the call site to use to dispatch the convert method
* @return the converted value
*/
public static IRubyObject convertToType(ThreadContext context, IRubyObject obj, RubyClass target, RespondToCallSite respondSite, CallSite site) {
if (target.isInstance(obj)) return obj;
IRubyObject val = convertToType(context, obj, target, respondSite, site, true);
if (!target.isInstance(val)) throw obj.getRuntime().newTypeError(obj.getMetaClass() + "#" + site.methodName + " should return " + target.getName());
return val;
}

/**
* Converts this object to type 'targetType' using 'convertMethod' method and raises TypeError exception on failure (MRI: rb_convert_type in 1.9).
*
@@ -204,6 +239,24 @@ public static IRubyObject convertToTypeWithCheck(IRubyObject obj, RubyClass targ
return val;
}

/**
* Higher level conversion utility similar to convertToType but it can throw an
* additional TypeError during conversion (MRI: rb_check_convert_type).
*
* @param obj the object to convert
* @param target is the type we are trying to convert to
* @param respondsToSite call site to use for respond_to? check
* @param site call site to use for conversion call
* @return the converted value
*/
public static IRubyObject convertToTypeWithCheck(ThreadContext context, IRubyObject obj, RubyClass target, RespondToCallSite respondsToSite, CallSite site) {
if (target.isInstance(obj)) return obj;
IRubyObject val = TypeConverter.convertToType(context, obj, target, respondsToSite, site, false);
if (val.isNil()) return val;
if (!target.isInstance(val)) throw obj.getRuntime().newTypeError(obj.getMetaClass() + "#" + site.methodName + " should return " + target.getName());
return val;
}

/**
* Higher level conversion utility similar to convertToType but it can throw an
* additional TypeError during conversion (MRI: rb_check_convert_type).
@@ -270,6 +323,12 @@ public static IRubyObject checkStringType(Ruby runtime, IRubyObject obj) {
return TypeConverter.convertToTypeWithCheck(obj, runtime.getString(), "to_str");
}

// rb_check_string_type
public static IRubyObject checkStringType(ThreadContext context, RespondToCallSite respondToSite, CallSite site, IRubyObject obj) {
if (obj instanceof RubyString) return obj;
return TypeConverter.convertToTypeWithCheck(context, obj, context.runtime.getString(), respondToSite, site);
}

// rb_check_array_type
public static IRubyObject checkArrayType(Ruby runtime, IRubyObject obj) {
if (obj instanceof RubyArray) return obj;

0 comments on commit 0a4aa21

Please sign in to comment.