Skip to content

Commit

Permalink
Implement fully-cached "checked" methods and type conversions.
Browse files Browse the repository at this point in the history
This batch of changes implements cached logic for the "checked"
type conversions and calls, and wires that logic up for many
places in core. CheckedSites is introduced into JavaSites as a
carrier for the four call site types needed to do a checked
invocation. With this change, checked calls can now be fully
cached.

This also introduces a few new cached unchecked paths and wires
them up in a few places as well. I will let this bake and then
ensure all places using the old logic now use the new logic in an
uncoming commit.
headius committed Jul 19, 2016
1 parent 0a4aa21 commit 44e9d92
Showing 22 changed files with 496 additions and 218 deletions.
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -59,7 +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.JavaSites;
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
import org.jruby.util.ClassDefiningClassLoader;
import org.objectweb.asm.util.TraceClassVisitor;
@@ -5434,5 +5434,5 @@ protected TypePopulator computeValue(Class<?> type) {
}
};

public final JavaCallSites sites = new JavaCallSites();
public final JavaSites sites = new JavaSites();
}
6 changes: 5 additions & 1 deletion core/src/main/java/org/jruby/RubyArray.java
Original file line number Diff line number Diff line change
@@ -45,8 +45,10 @@
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.JavaSites;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
@@ -3652,7 +3654,9 @@ public IRubyObject product(ThreadContext context, IRubyObject[] args, Block bloc
int counters[] = new int[n];

arrays[0] = this;
for (int i = 1; i < n; i++) arrays[i] = TypeConverter.to_ary(context, args[i - 1]);
RubyClass array = runtime.getArray();
JavaSites.CheckedSites sites = context.sites.ARY_to_ary_checked;
for (int i = 1; i < n; i++) arrays[i] = (RubyArray) TypeConverter.convertToType19(context, args[i - 1], array, sites);

int resultLen = 1;
for (int i = 0; i < n; i++) {
83 changes: 67 additions & 16 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -30,7 +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.JavaSites;
import org.jruby.runtime.ivars.VariableAccessor;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -331,6 +331,15 @@ public final IRubyObject checkCallMethod(ThreadContext context, String name) {
return Helpers.invokeChecked(context, this, name);
}

/**
* Will invoke a named method with no arguments and no block if that method or a custom
* method missing exists. Otherwise returns null. 1.9: rb_check_funcall
*/
@Override
public final IRubyObject checkCallMethod(ThreadContext context, JavaSites.CheckedSites sites) {
return Helpers.invokeChecked(context, this, sites);
}

/**
* Will invoke a named method with no arguments and no block.
*/
@@ -669,7 +678,10 @@ public String asJavaString() {
*/
@Override
public RubyString asString() {
IRubyObject str = Helpers.invoke(getRuntime().getCurrentContext(), this, "to_s");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
IRubyObject str = sites.BO_to_s.call(context, this, this);

if (!(str instanceof RubyString)) return (RubyString)anyToString();
if (isTaint()) str.setTaint(true);
@@ -682,7 +694,10 @@ public RubyString asString() {
*/
@Override
public RubyArray convertToArray() {
return (RubyArray) TypeConverter.convertToType(this, getRuntime().getArray(), "to_ary");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
return (RubyArray) TypeConverter.convertToType(context, this, runtime.getArray(), sites.BO_to_ary_checked);
}

/**
@@ -691,7 +706,10 @@ public RubyArray convertToArray() {
*/
@Override
public RubyHash convertToHash() {
return (RubyHash)TypeConverter.convertToType(this, getRuntime().getHash(), "to_hash");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
return (RubyHash) TypeConverter.convertToType(context, this, runtime.getHash(), sites.BO_to_hash_checked);
}

/**
@@ -700,7 +718,10 @@ public RubyHash convertToHash() {
*/
@Override
public RubyFloat convertToFloat() {
return (RubyFloat) TypeConverter.convertToType(this, getRuntime().getFloat(), "to_f");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
return (RubyFloat) TypeConverter.convertToType(context, this, runtime.getFloat(), sites.BO_to_f_checked);
}

/**
@@ -709,7 +730,15 @@ public RubyFloat convertToFloat() {
*/
@Override
public RubyInteger convertToInteger() {
return convertToInteger("to_int");
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;

IRubyObject result = TypeConverter.convertToType(context, this, runtime.getInteger(), sites.BO_to_int_checked, true);

if (!(result instanceof RubyInteger)) throw getRuntime().newTypeError(getMetaClass().getName() + "#to_int should return Integer");

return (RubyInteger) result;
}

/**
@@ -718,9 +747,21 @@ public RubyInteger convertToInteger() {
*/
@Override
public RubyInteger convertToInteger(String convertMethod) {
IRubyObject val = TypeConverter.convertToType(this, getRuntime().getInteger(), convertMethod, true);
if (!(val instanceof RubyInteger)) throw getRuntime().newTypeError(getMetaClass().getName() + '#' + convertMethod + " should return Integer");
return (RubyInteger)val;
if (convertMethod.equals("to_int")) return convertToInteger();

IRubyObject result;
if (convertMethod.equals("to_i")) {
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
result = TypeConverter.convertToType(context, this, runtime.getInteger(), sites.BO_to_i_checked, true);
} else {
result = TypeConverter.convertToType(this, getRuntime().getInteger(), convertMethod, true);
}

if (!(result instanceof RubyInteger)) throw getRuntime().newTypeError(getMetaClass().getName() + "#to_int should return Integer");

return (RubyInteger) result;
}

/**
@@ -731,8 +772,8 @@ public RubyInteger convertToInteger(String convertMethod) {
public RubyString convertToString() {
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);
JavaSites sites = context.sites;
return (RubyString) TypeConverter.convertToType(context, this, getRuntime().getString(), sites.BO_to_str_checked);
}

/**
@@ -761,7 +802,10 @@ public IRubyObject anyToString() {
*/
@Override
public IRubyObject checkStringType() {
return TypeConverter.checkStringType(getRuntime(), this);
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
return TypeConverter.checkStringType(context, sites.BO_to_str_checked, this);
}

/** rb_check_string_type
@@ -773,7 +817,7 @@ public IRubyObject checkStringType() {
*/
@Override
public IRubyObject checkStringType19() {
return TypeConverter.checkStringType(getRuntime(), this);
return checkStringType();
}

/** rb_check_array_type
@@ -783,7 +827,10 @@ public IRubyObject checkStringType19() {
*/
@Override
public IRubyObject checkArrayType() {
return TypeConverter.checkArrayType(getRuntime(), this);
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
return TypeConverter.checkArrayType(context, sites.BO_to_ary_checked, this);
}

/**
@@ -2833,7 +2880,10 @@ public boolean equals(Object other) {
if (other == this) return true;

if (other instanceof IRubyObject) {
IRubyObject equals = invokeChecked(getRuntime().getCurrentContext(), this, "==", (IRubyObject)other);
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
JavaSites sites = context.sites;
IRubyObject equals = invokeChecked(context, this, sites.BO_equals_checked, (IRubyObject)other);
if (equals == null) return false;
return equals.isTrue();
}
@@ -2849,7 +2899,8 @@ public boolean equals(Object other) {
*/
@Override
public int hashCode() {
IRubyObject hashValue = invokeChecked(getRuntime().getCurrentContext(), this, "hash");
ThreadContext context = getRuntime().getCurrentContext();
IRubyObject hashValue = invokeChecked(context, this, context.sites.BO_hash_checked);
if (hashValue == null) return super.hashCode();
if (hashValue instanceof RubyFixnum) return (int) RubyNumeric.fix2long(hashValue);
return nonFixnumHashCode(hashValue);
108 changes: 108 additions & 0 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
@@ -31,6 +31,9 @@
package org.jruby;

import org.jruby.runtime.Arity;
import org.jruby.runtime.JavaSites;
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.callsite.RespondToCallSite;
import org.jruby.runtime.ivars.VariableAccessor;
import static org.jruby.util.CodegenUtils.ci;
import static org.jruby.util.CodegenUtils.p;
@@ -627,6 +630,15 @@ public final IRubyObject finvokeChecked(ThreadContext context, IRubyObject self,
return checkFuncallDefault(context, self, name, IRubyObject.NULL_ARRAY);
}

/**
* Safely attempt to invoke the given method name on self, using respond_to? and method_missing as appropriate.
*
* MRI: rb_check_funcall
*/
public final IRubyObject finvokeChecked(ThreadContext context, IRubyObject self, JavaSites.CheckedSites sites) {
return checkFuncallDefault(context, self, sites);
}

/**
* Safely attempt to invoke the given method name on self, using respond_to? and method_missing as appropriate.
*
@@ -636,6 +648,15 @@ public final IRubyObject finvokeChecked(ThreadContext context, IRubyObject self,
return checkFuncallDefault(context, self, name, args);
}

/**
* Safely attempt to invoke the given method name on self, using respond_to? and method_missing as appropriate.
*
* MRI: rb_check_funcall
*/
public final IRubyObject finvokeChecked(ThreadContext context, IRubyObject self, JavaSites.CheckedSites sites, IRubyObject... args) {
return checkFuncallDefault(context, self, sites, args);
}

// MRI: rb_check_funcall_default
private IRubyObject checkFuncallDefault(ThreadContext context, IRubyObject self, String name, IRubyObject[] args) {
final RubyClass klass = self.getMetaClass();
@@ -648,6 +669,30 @@ private IRubyObject checkFuncallDefault(ThreadContext context, IRubyObject self,
return me.call(context, self, klass, name, args);
}

// MRI: rb_check_funcall_default
private IRubyObject checkFuncallDefault(ThreadContext context, IRubyObject self, JavaSites.CheckedSites sites, IRubyObject[] args) {
final RubyClass klass = self.getMetaClass();
if (!checkFuncallRespondTo(context, klass, self, sites.respond_to_X)) return null; // return def;

DynamicMethod me = sites.site.retrieveCache(klass).method;
if (!checkFuncallCallable(context, me, CallType.FUNCTIONAL, self)) {
return checkFuncallMissing(context, klass, self, sites.methodName, sites.respond_to_missing, sites.method_missing, args);
}
return me.call(context, self, klass, sites.methodName, args);
}

// MRI: rb_check_funcall_default
private IRubyObject checkFuncallDefault(ThreadContext context, IRubyObject self, JavaSites.CheckedSites sites) {
final RubyClass klass = self.getMetaClass();
if (!checkFuncallRespondTo(context, klass, self, sites.respond_to_X)) return null; // return def;

DynamicMethod me = sites.site.retrieveCache(klass).method;
if (!checkFuncallCallable(context, me, CallType.FUNCTIONAL, self)) {
return checkFuncallMissing(context, klass, self, sites.methodName, sites.respond_to_missing, sites.method_missing);
}
return me.call(context, self, klass, sites.methodName);
}

// MRI: check_funcall_exec
private static IRubyObject checkFuncallExec(ThreadContext context, IRubyObject self, String name, IRubyObject... args) {
IRubyObject[] newArgs = new IRubyObject[args.length + 1];
@@ -656,6 +701,14 @@ private static IRubyObject checkFuncallExec(ThreadContext context, IRubyObject s
return self.callMethod(context, "method_missing", newArgs);
}

// MRI: check_funcall_exec
private static IRubyObject checkFuncallExec(ThreadContext context, IRubyObject self, String name, CallSite methodMissingSite, IRubyObject... args) {
IRubyObject[] newArgs = new IRubyObject[args.length + 1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = context.runtime.newSymbol(name);
return methodMissingSite.call(context, self, self, newArgs);
}

// MRI: check_funcall_failed
private static IRubyObject checkFuncallFailed(ThreadContext context, IRubyObject self, String name, RubyClass expClass, IRubyObject... args) {
if (self.respondsTo(name)) {
@@ -691,6 +744,33 @@ private static boolean checkFuncallRespondTo(ThreadContext context, RubyClass kl
return true;
}

/**
* Check if the method has a custom respond_to? and call it if so with the method ID we're hoping to call.
*
* MRI: check_funcall_respond_to
*/
private static boolean checkFuncallRespondTo(ThreadContext context, RubyClass klass, IRubyObject recv, RespondToCallSite respondToSite) {
final Ruby runtime = context.runtime;
DynamicMethod me = respondToSite.retrieveCache(klass).method;

// NOTE: isBuiltin here would be NOEX_BASIC in MRI, a flag only added to respond_to?, method_missing, and
// respond_to_missing? Same effect, I believe.
if (me != null && !me.isUndefined() && !me.isBuiltin()) {
int arityValue = me.getArity().getValue();

if (arityValue > 2) throw runtime.newArgumentError("respond_to? must accept 1 or 2 arguments (requires " + arityValue + ")");

boolean result;
if (arityValue == 1) {
result = respondToSite.respondsTo(context, recv, recv);
} else {
result = respondToSite.respondsTo(context, recv, recv, true);
}
return result;
}
return true;
}

// MRI: check_funcall_callable
public static boolean checkFuncallCallable(ThreadContext context, DynamicMethod method, CallType callType, IRubyObject self) {
return rbMethodCallStatus(context, method, callType, self);
@@ -730,6 +810,34 @@ private static IRubyObject checkFuncallMissing(ThreadContext context, RubyClass
}
}

// MRI: check_funcall_missing
private static IRubyObject checkFuncallMissing(ThreadContext context, RubyClass klass, IRubyObject self, String method, CachingCallSite respondToMissingSite, CachingCallSite methodMissingSite, IRubyObject... args) {
final Ruby runtime = context.runtime;

DynamicMethod me = respondToMissingSite.retrieveCache(klass).method;
// MRI: basic_obj_respond_to_missing ...
if ( me != null && ! me.isUndefined() && ! me.isBuiltin() ) {
IRubyObject ret;
if (me.getArity().getValue() == 1) {
ret = me.call(context, self, klass, "respond_to_missing?", runtime.newSymbol(method));
} else {
ret = me.call(context, self, klass, "respond_to_missing?", runtime.newSymbol(method), runtime.getTrue());
}
if ( ! ret.isTrue() ) return null;
}

if (methodMissingSite.retrieveCache(klass).method.isBuiltin()) return null;

final IRubyObject $ex = context.getErrorInfo();
try {
return checkFuncallExec(context, self, method, methodMissingSite, args);
}
catch (RaiseException e) {
context.setErrorInfo($ex); // restore $!
return checkFuncallFailed(context, self, method, runtime.getNoMethodError(), args);
}
}

public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
IRubyObject[] args, CallType callType) {
assert args != null;
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyEnumerable.java
Original file line number Diff line number Diff line change
@@ -1864,7 +1864,7 @@ private static SizeFn enumSizeFn(final ThreadContext context, final IRubyObject
return new SizeFn() {
@Override
public IRubyObject size(IRubyObject[] args) {
return self.checkCallMethod(context, "size");
return self.checkCallMethod(context, context.sites.ENUMBL_size_checked);
}
};
}
Loading

0 comments on commit 44e9d92

Please sign in to comment.