Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4c902fd61506
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ea0491535d21
Choose a head ref
  • 10 commits
  • 9 files changed
  • 1 contributor

Commits on Apr 3, 2016

  1. Copy the full SHA
    ea39307 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    38aec2f View commit details
  3. Copy the full SHA
    39e3a1a View commit details
  4. Copy the full SHA
    2bc1abb View commit details
  5. Copy the full SHA
    e3263a0 View commit details
  6. Copy the full SHA
    b09758f View commit details
  7. Copy the full SHA
    c65de45 View commit details
  8. Copy the full SHA
    64ff1ed View commit details
  9. 5
    Copy the full SHA
    33c0e45 View commit details
  10. Copy the full SHA
    ea04915 View commit details
34 changes: 18 additions & 16 deletions core/src/main/java/org/jruby/util/cli/Options.java
Original file line number Diff line number Diff line change
@@ -242,24 +242,26 @@ public class Options {

public static final Option<Integer> TRUFFLE_DEFAULT_CACHE = integer(TRUFFLE, "truffle.default_cache", 8, "Default size for caches.");

public static final Option<Integer> TRUFFLE_METHOD_LOOKUP_CACHE = integer(TRUFFLE, "truffle.method_lookup.cache", TRUFFLE_DEFAULT_CACHE.load(), "Method lookup cache size");
public static final Option<Integer> TRUFFLE_METHOD_LOOKUP_CACHE = integer(TRUFFLE, "truffle.method_lookup.cache", TRUFFLE_DEFAULT_CACHE.load(), "Method lookup cache size.");
public static final Option<Integer> TRUFFLE_DISPATCH_CACHE = integer(TRUFFLE, "truffle.dispatch.cache", TRUFFLE_DEFAULT_CACHE.load(), "Dispatch (various forms of method call) cache size.");
public static final Option<Integer> TRUFFLE_YIELD_CACHE = integer(TRUFFLE, "truffle.yield.cache", TRUFFLE_DEFAULT_CACHE.load(), "Yield cache size.");
public static final Option<Integer> TRUFFLE_METHOD_TO_PROC_CACHE = integer(TRUFFLE, "truffle.to_proc.cache", TRUFFLE_DEFAULT_CACHE.load(), "Method#to_proc cache size");
public static final Option<Integer> TRUFFLE_IS_A_CACHE = integer(TRUFFLE, "truffle.is_a.cache", TRUFFLE_DEFAULT_CACHE.load(), "Kernel#is_a? and #kind_of? cache size");
public static final Option<Integer> TRUFFLE_BIND_CACHE = integer(TRUFFLE, "truffle.bind.cache", TRUFFLE_DEFAULT_CACHE.load(), "Test for being able to bind a method to a module cache size");
public static final Option<Integer> TRUFFLE_CONSTANT_CACHE = integer(TRUFFLE, "truffle.constant.cache", TRUFFLE_DEFAULT_CACHE.load(), "Constant cache size");
public static final Option<Integer> TRUFFLE_INSTANCE_VARIABLE_CACHE = integer(TRUFFLE, "truffle.instance_variable.cache", TRUFFLE_DEFAULT_CACHE.load(), "Instance variable cache size");
public static final Option<Integer> TRUFFLE_BINDING_LOCAL_VARIABLE_CACHE = integer(TRUFFLE, "truffle.binding_local_variable.cache", TRUFFLE_DEFAULT_CACHE.load(), "Binding#local_variable_get/set cache size");
public static final Option<Integer> TRUFFLE_SYMBOL_TO_PROC_CACHE = integer(TRUFFLE, "truffle.symbol_to_proc.cache", TRUFFLE_DEFAULT_CACHE.load(), "Symbol#to_proc cache size");
public static final Option<Integer> TRUFFLE_ALLOCATE_CLASS_CACHE = integer(TRUFFLE, "truffle.allocate_class.cache", TRUFFLE_DEFAULT_CACHE.load(), "Allocation size class cache size");
public static final Option<Integer> TRUFFLE_PACK_CACHE = integer(TRUFFLE, "truffle.pack.cache", TRUFFLE_DEFAULT_CACHE.load(), "Array#pack cache size");
public static final Option<Integer> TRUFFLE_UNPACK_CACHE = integer(TRUFFLE, "truffle.unpack.cache", TRUFFLE_DEFAULT_CACHE.load(), "String#unpack cache size");
public static final Option<Integer> TRUFFLE_EVAL_CACHE = integer(TRUFFLE, "truffle.eval.cache", TRUFFLE_DEFAULT_CACHE.load(), "eval lookup cache size");
public static final Option<Integer> TRUFFLE_CLASS_CACHE = integer(TRUFFLE, "truffle.class.cache", TRUFFLE_DEFAULT_CACHE.load(), ".class and .metaclass cache size");
public static final Option<Integer> TRUFFLE_ENCODING_COMPATIBLE_QUERY_CACHE = integer(TRUFFLE, "truffle.encoding_compatible_query.cache", TRUFFLE_DEFAULT_CACHE.load(), "Encoding.compatible? cache size");
public static final Option<Integer> TRUFFLE_THREAD_CACHE = integer(TRUFFLE, "truffle.thread.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size of operations that depend on a particular thread");
public static final Option<Integer> TRUFFLE_ROPE_CLASS_CACHE = integer(TRUFFLE, "truffle.rope_class.cache", 6, "Cache size for rope operations that depend on a concrete rope implementation to avoid virtual calls");
public static final Option<Integer> TRUFFLE_METHOD_TO_PROC_CACHE = integer(TRUFFLE, "truffle.to_proc.cache", TRUFFLE_DEFAULT_CACHE.load(), "Method#to_proc cache size.");
public static final Option<Integer> TRUFFLE_IS_A_CACHE = integer(TRUFFLE, "truffle.is_a.cache", TRUFFLE_DEFAULT_CACHE.load(), "Kernel#is_a? and #kind_of? cache size.");
public static final Option<Integer> TRUFFLE_BIND_CACHE = integer(TRUFFLE, "truffle.bind.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size of test for being able to bind a method to a module.");
public static final Option<Integer> TRUFFLE_CONSTANT_CACHE = integer(TRUFFLE, "truffle.constant.cache", TRUFFLE_DEFAULT_CACHE.load(), "Constant cache size.");
public static final Option<Integer> TRUFFLE_INSTANCE_VARIABLE_CACHE = integer(TRUFFLE, "truffle.instance_variable.cache", TRUFFLE_DEFAULT_CACHE.load(), "Instance variable cache size.");
public static final Option<Integer> TRUFFLE_BINDING_LOCAL_VARIABLE_CACHE = integer(TRUFFLE, "truffle.binding_local_variable.cache", TRUFFLE_DEFAULT_CACHE.load(), "Binding#local_variable_get/set cache size.");
public static final Option<Integer> TRUFFLE_SYMBOL_TO_PROC_CACHE = integer(TRUFFLE, "truffle.symbol_to_proc.cache", TRUFFLE_DEFAULT_CACHE.load(), "Symbol#to_proc cache size.");
public static final Option<Integer> TRUFFLE_ALLOCATE_CLASS_CACHE = integer(TRUFFLE, "truffle.allocate_class.cache", TRUFFLE_DEFAULT_CACHE.load(), "Allocation size class cache size.");
public static final Option<Integer> TRUFFLE_PACK_CACHE = integer(TRUFFLE, "truffle.pack.cache", TRUFFLE_DEFAULT_CACHE.load(), "Array#pack cache size.");
public static final Option<Integer> TRUFFLE_UNPACK_CACHE = integer(TRUFFLE, "truffle.unpack.cache", TRUFFLE_DEFAULT_CACHE.load(), "String#unpack cache size.");
public static final Option<Integer> TRUFFLE_EVAL_CACHE = integer(TRUFFLE, "truffle.eval.cache", TRUFFLE_DEFAULT_CACHE.load(), "eval cache size.");
public static final Option<Integer> TRUFFLE_CLASS_CACHE = integer(TRUFFLE, "truffle.class.cache", TRUFFLE_DEFAULT_CACHE.load(), ".class and .metaclass cache size.");
public static final Option<Integer> TRUFFLE_ENCODING_COMPATIBLE_QUERY_CACHE = integer(TRUFFLE, "truffle.encoding_compatible_query.cache", TRUFFLE_DEFAULT_CACHE.load(), "Encoding.compatible? cache size.");
public static final Option<Integer> TRUFFLE_THREAD_CACHE = integer(TRUFFLE, "truffle.thread.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size of operations that depend on a particular thread.");
public static final Option<Integer> TRUFFLE_ROPE_CLASS_CACHE = integer(TRUFFLE, "truffle.rope_class.cache", 6, "Cache size for rope operations that depend on a concrete rope implementation to avoid virtual calls.");
public static final Option<Integer> TRUFFLE_INTEROP_CONVERT_CACHE = integer(TRUFFLE, "truffle.interop.convert.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size for converting values for interop.");
public static final Option<Integer> TRUFFLE_INTEROP_EXECUTE_CACHE = integer(TRUFFLE, "truffle.interop.execute.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size for interop EXECUTE messages.");

public static final Option<Boolean> TRUFFLE_CLONE_DEFAULT = bool(TRUFFLE, "truffle.clone.default", true, "Default option for cloning.");
public static final Option<Boolean> TRUFFLE_INLINE_DEFAULT = bool(TRUFFLE, "truffle.inline.default", true, "Default option for inlining.");
152 changes: 107 additions & 45 deletions truffle/src/main/java/org/jruby/truffle/interop/ForeignExecuteNode.java
Original file line number Diff line number Diff line change
@@ -11,13 +11,12 @@

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.AcceptMessage;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
@@ -32,85 +31,148 @@
import org.jruby.truffle.language.methods.DeclarationContext;
import org.jruby.truffle.language.methods.InternalMethod;

import java.util.List;

@AcceptMessage(value = "EXECUTE", receiverType = RubyObjectType.class, language = RubyLanguage.class)
public final class ForeignExecuteNode extends ForeignExecuteBaseNode {

@Child private Node findContextNode;
@Child private ExecuteMethodNode executeMethodNode;
@Child private HelperNode executeMethodNode;

@Override
public Object access(VirtualFrame frame, DynamicObject object, Object[] args) {
return getInteropNode().executeWithTarget(frame, object);
public Object access(VirtualFrame frame, DynamicObject object, Object[] arguments) {
return getHelperNode().executeCall(frame, object, arguments);
}

private ExecuteMethodNode getInteropNode() {
private HelperNode getHelperNode() {
if (executeMethodNode == null) {
CompilerDirectives.transferToInterpreter();
findContextNode = insert(RubyLanguage.INSTANCE.unprotectedCreateFindContextNode());
final RubyContext context = RubyLanguage.INSTANCE.unprotectedFindContext(findContextNode);
executeMethodNode = insert(ForeignExecuteNodeFactory.ExecuteMethodNodeGen.create(context, null, null));
executeMethodNode = insert(ForeignExecuteNodeFactory.HelperNodeGen.create(context, null, null, null));
}

return executeMethodNode;
}

@NodeChild(value="method", type = RubyNode.class)
public static abstract class ExecuteMethodNode extends RubyNode {
@NodeChildren({
@NodeChild("receiver"),
@NodeChild("arguments")
})
protected static abstract class HelperNode extends RubyNode {

@Child private IndirectCallNode callNode;
public ExecuteMethodNode(RubyContext context,
public HelperNode(RubyContext context,
SourceSection sourceSection) {
super(context, sourceSection);
callNode = Truffle.getRuntime().createIndirectCallNode();
}

@Specialization(guards = {"isRubyProc(proc)", "proc == cachedProc"})
protected Object doCallProc(VirtualFrame frame, DynamicObject proc,
@Cached("proc") DynamicObject cachedProc,
@Cached("create(getCallTarget(cachedProc))") DirectCallNode callNode) {
final List<Object> faArgs = ForeignAccess.getArguments(frame);
Object[] args = faArgs.toArray();
return callNode.call(frame, RubyArguments.pack(Layouts.PROC.getDeclarationFrame(cachedProc), null, Layouts.PROC.getMethod(cachedProc), DeclarationContext.METHOD, null, Layouts.PROC.getSelf(cachedProc), null, args));
public abstract Object executeCall(VirtualFrame frame, Object receiver, Object[] arguments);

@Specialization(
guards = {
"isRubyProc(proc)",
"proc == cachedProc"
},
limit = "getCacheLimit()"
)
protected Object callProcCached(VirtualFrame frame,
DynamicObject proc,
Object[] arguments,
@Cached("proc") DynamicObject cachedProc,
@Cached("create(getProcCallTarget(cachedProc))") DirectCallNode callNode) {
return callNode.call(
frame,
RubyArguments.pack(
Layouts.PROC.getDeclarationFrame(cachedProc),
null,
Layouts.PROC.getMethod(cachedProc),
DeclarationContext.METHOD,
null,
Layouts.PROC.getSelf(cachedProc),
null,
arguments));
}

@Specialization(guards = "isRubyProc(proc)")
protected Object doCallProc(VirtualFrame frame, DynamicObject proc) {
final List<Object> faArgs = ForeignAccess.getArguments(frame);
Object[] args = faArgs.toArray();
return callNode.call(frame, Layouts.PROC.getCallTargetForType(proc), RubyArguments.pack(Layouts.PROC.getDeclarationFrame(proc), null, Layouts.PROC.getMethod(proc), DeclarationContext.METHOD, null, Layouts.PROC.getSelf(proc), null, args));
@Specialization(
guards = "isRubyProc(proc)",
contains = "callProcCached"
)
protected Object callProcUncached(VirtualFrame frame,
DynamicObject proc,
Object[] arguments,
@Cached("create()") IndirectCallNode callNode) {
return callNode.call(
frame,
getProcCallTarget(proc),
RubyArguments.pack(
Layouts.PROC.getDeclarationFrame(proc),
null,
Layouts.PROC.getMethod(proc),
DeclarationContext.METHOD,
null,
Layouts.PROC.getSelf(proc),
null,
arguments));
}

@Specialization(guards = {"isRubyMethod(method)", "method == cachedMethod"})
protected Object doCall(VirtualFrame frame, DynamicObject method,
@Cached("method") DynamicObject cachedMethod,
@Cached("getMethod(cachedMethod)") InternalMethod internalMethod,
@Cached("create(getMethod(cachedMethod).getCallTarget())") DirectCallNode callNode) {
final List<Object> faArgs = ForeignAccess.getArguments(frame);

Object[] args = faArgs.subList(0, faArgs.size()).toArray();
return callNode.call(frame, RubyArguments.pack(null, null, internalMethod, DeclarationContext.METHOD, null, Layouts.METHOD.getReceiver(cachedMethod), null, args));
protected CallTarget getProcCallTarget(DynamicObject proc) {
return Layouts.PROC.getCallTargetForType(proc);
}

@Specialization(guards = "isRubyMethod(method)")
protected Object doCall(VirtualFrame frame, DynamicObject method) {
final InternalMethod internalMethod = Layouts.METHOD.getMethod(method);
final List<Object> faArgs = ForeignAccess.getArguments(frame);

Object[] args = faArgs.subList(0, faArgs.size()).toArray();
return callNode.call(frame, internalMethod.getCallTarget(), RubyArguments.pack(null, null, internalMethod, DeclarationContext.METHOD, null, Layouts.METHOD.getReceiver(method), null, args));
@Specialization(
guards = {
"isRubyMethod(method)",
"method == cachedMethod"
},
limit = "getCacheLimit()"
)
protected Object callMethodCached(VirtualFrame frame,
DynamicObject method,
Object[] arguments,
@Cached("method") DynamicObject cachedMethod,
@Cached("getMethod(cachedMethod)") InternalMethod cachedInternalMethod,
@Cached("create(cachedInternalMethod.getCallTarget())") DirectCallNode callNode) {
return callNode.call(
frame,
RubyArguments.pack(
null,
null,
cachedInternalMethod,
DeclarationContext.METHOD,
null,
Layouts.METHOD.getReceiver(cachedMethod),
null,
arguments));
}

protected CallTarget getCallTarget(DynamicObject proc) {
return Layouts.PROC.getCallTargetForType(proc);
@Specialization(
guards = "isRubyMethod(method)",
contains = "callMethodCached"
)
protected Object callMethodUncached(VirtualFrame frame,
DynamicObject method,
Object[] arguments,
@Cached("create()") IndirectCallNode callNode) {
final InternalMethod internalMethod = getMethod(method);
return callNode.call(
frame,
internalMethod.getCallTarget(),
RubyArguments.pack(
null,
null,
internalMethod,
DeclarationContext.METHOD,
null,
Layouts.METHOD.getReceiver(method),
null,
arguments));
}

protected InternalMethod getMethod(DynamicObject method) {
return Layouts.METHOD.getMethod(method);
}

public abstract Object executeWithTarget(VirtualFrame frame, Object method);
protected int getCacheLimit() {
return getContext().getOptions().INTEROP_EXECUTE_CACHE;
}

}

Original file line number Diff line number Diff line change
@@ -12,89 +12,35 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.AcceptMessage;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.RubyObjectType;
import org.jruby.truffle.language.dispatch.DispatchAction;
import org.jruby.truffle.language.dispatch.DispatchHeadNode;
import org.jruby.truffle.language.dispatch.MissingBehavior;

import java.util.List;

@AcceptMessage(value = "INVOKE", receiverType = RubyObjectType.class, language = RubyLanguage.class)
public final class ForeignInvokeNode extends ForeignInvokeBaseNode {

@Child private Node findContextNode;
@Child private RubyNode interopNode;
@Child private DispatchHeadNode dispatchHeadNode;

@Override
public Object access(VirtualFrame frame, DynamicObject object, String name, Object[] args) {
return getInteropNode().execute(frame);
public Object access(VirtualFrame frame, DynamicObject receiver, String name, Object[] arguments) {
return getDispatchHeadNode().dispatch(frame, receiver, name, null, arguments);
}

private RubyNode getInteropNode() {
if (interopNode == null) {
private DispatchHeadNode getDispatchHeadNode() {
if (dispatchHeadNode == null) {
CompilerDirectives.transferToInterpreter();
findContextNode = insert(RubyLanguage.INSTANCE.unprotectedCreateFindContextNode());
final RubyContext context = RubyLanguage.INSTANCE.unprotectedFindContext(findContextNode);
interopNode = insert(new UnresolvedInteropExecuteAfterReadNode(context, null, 0));
}

return interopNode;
}

public static class UnresolvedInteropExecuteAfterReadNode extends RubyNode {

private final int arity;
private final int labelIndex;

public UnresolvedInteropExecuteAfterReadNode(RubyContext context, SourceSection sourceSection, int arity){
super(context, sourceSection);
this.arity = arity;
this.labelIndex = 0;
dispatchHeadNode = insert(new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD));
}

@Override
public Object execute(VirtualFrame frame) {
if (ForeignAccess.getArguments(frame).get(labelIndex) instanceof String) {
return this.replace(new ResolvedInteropExecuteAfterReadNode(getContext(), getSourceSection(), (String) ForeignAccess.getArguments(frame).get(labelIndex), arity)).execute(frame);
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(ForeignAccess.getArguments(frame).get(0) + " not allowed as name");
}
}
}

public static class ResolvedInteropExecuteAfterReadNode extends RubyNode {

@Child private DispatchHeadNode head;
private final String name;
private final int labelIndex;
private final int receiverIndex;

public ResolvedInteropExecuteAfterReadNode(RubyContext context, SourceSection sourceSection, String name, int arity) {
super(context, sourceSection);
this.name = name;
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.labelIndex = 1;
this.receiverIndex = 0;
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(frame.getArguments()[labelIndex])) {
final List<Object> arguments = ForeignAccess.getArguments(frame);
return head.dispatch(frame, frame.getArguments()[receiverIndex], frame.getArguments()[labelIndex], null, arguments.subList(1, arguments.size()).toArray());
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Name changed");
}
}
return dispatchHeadNode;
}

}
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ public final class ForeignIsExecutableNode extends ForeignIsExecutableBaseNode {

@Override
public Object access(VirtualFrame frame, DynamicObject object) {
return RubyGuards.isRubyMethod(object);
return RubyGuards.isRubyMethod(object) || RubyGuards.isRubyProc(object);
}

}
Original file line number Diff line number Diff line change
@@ -9,14 +9,15 @@
*/
package org.jruby.truffle.interop;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyNode;

@@ -27,17 +28,36 @@ public ForeignToRubyNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract Object executeConvert(VirtualFrame frame, Object obj);
public abstract Object executeConvert(VirtualFrame frame, Object value);

@TruffleBoundary
@Specialization
public Object convert(String index) {
return createString(StringOperations.encodeRope(index, UTF8Encoding.INSTANCE));
@Specialization(guards = "stringsEquals(cachedValue, value)", limit = "getLimit()")
public DynamicObject convertStringCached(
String value,
@Cached("value") String cachedValue,
@Cached("getRope(value)") Rope cachedRope) {
return createString(cachedRope);
}

@Fallback
public Object convert(Object index) {
return index;
@Specialization(contains = "convertStringCached")
public DynamicObject convertStringUncached(String value) {
return createString(getRope(value));
}

protected boolean stringsEquals(String a, String b) {
return a.equals(b);
}

protected Rope getRope(String value) {
return StringOperations.encodeRope(value, UTF8Encoding.INSTANCE);
}

protected int getLimit() {
return getContext().getOptions().INTEROP_CONVERT_CACHE;
}

@Specialization(guards = "!isString(value)")
public Object convert(Object value) {
return value;
}

}
Original file line number Diff line number Diff line change
@@ -9,24 +9,45 @@
*/
package org.jruby.truffle.interop;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.AcceptMessage;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.RubyObjectType;

@AcceptMessage(value = "UNBOX", receiverType = RubyObjectType.class, language = RubyLanguage.class)
public final class ForeignUnboxNode extends ForeignUnboxBaseNode {

private final ConditionProfile stringProfile = ConditionProfile.createBinaryProfile();
private final ConditionProfile emptyProfile = ConditionProfile.createBinaryProfile();

@Override
public Object access(VirtualFrame frame, DynamicObject object) {
if (RubyGuards.isRubyString(object)) {
return StringOperations.getByteListReadOnly(object).get(0);
if (stringProfile.profile(RubyGuards.isRubyString(object))) {
final Rope rope = Layouts.STRING.getRope(object);

if (emptyProfile.profile(rope.byteLength() == 0)) {
unsuported();
throw new IllegalStateException();
} else {
return rope.get(0);
}
} else {
return object;
}
}

@CompilerDirectives.TruffleBoundary
private void unsuported() {
UnsupportedMessageException.raise(Message.UNBOX);
}

}
Original file line number Diff line number Diff line change
@@ -10,39 +10,64 @@
package org.jruby.truffle.interop;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.string.StringCachingGuards;
import org.jruby.truffle.language.RubyNode;

@ImportStatic(StringCachingGuards.class)
@NodeChild(value = "value", type = RubyNode.class)
public abstract class RubyToForeignNode extends RubyNode {

public RubyToForeignNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract Object executeConvert(VirtualFrame frame, Object object);
public abstract Object executeConvert(VirtualFrame frame, Object value);

@Specialization(
guards = {
"isRubyString(value)",
"ropesEqual(value, cachedRope)"
},
limit = "getLimit()")
public String convertUncached(
DynamicObject value,
@Cached("privatizeRope(value)") Rope cachedRope,
@Cached("objectToString(value)") String convertedString) {
return convertedString;
}

@TruffleBoundary
@Specialization(guards = "isRubyString(index)")
public Object convertString(DynamicObject index) {
return index.toString();
@Specialization(guards = "isRubyString(value)")
public String convertStringUncached(DynamicObject value) {
return value.toString();
}

protected int getLimit() {
return getContext().getOptions().INTEROP_CONVERT_CACHE;
}

protected String objectToString(DynamicObject object) {
return object.toString();
}

@Specialization(guards = "isRubySymbol(index)")
public Object convertSymbol(DynamicObject index) {
return Layouts.SYMBOL.getString(index);
@Specialization(guards = "isRubySymbol(value)")
public String convertSymbol(DynamicObject value) {
return Layouts.SYMBOL.getString(value);
}

@Fallback
public Object convert(Object index) {
return index;
@Specialization(guards = {"!isRubyString(value)", "!isRubySymbol(value)"})
public Object convert(Object value) {
return value;
}

}
4 changes: 4 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/language/Options.java
Original file line number Diff line number Diff line change
@@ -37,6 +37,8 @@
import static org.jruby.util.cli.Options.TRUFFLE_INLINE_NEEDS_CALLER_FRAME;
import static org.jruby.util.cli.Options.TRUFFLE_INSTANCE_VARIABLE_CACHE;
import static org.jruby.util.cli.Options.TRUFFLE_INSTRUMENTATION_SERVER_PORT;
import static org.jruby.util.cli.Options.TRUFFLE_INTEROP_CONVERT_CACHE;
import static org.jruby.util.cli.Options.TRUFFLE_INTEROP_EXECUTE_CACHE;
import static org.jruby.util.cli.Options.TRUFFLE_IS_A_CACHE;
import static org.jruby.util.cli.Options.TRUFFLE_METHODMISSING_ALWAYS_CLONE;
import static org.jruby.util.cli.Options.TRUFFLE_METHODMISSING_ALWAYS_INLINE;
@@ -109,6 +111,8 @@ public class Options {
public final int ENCODING_COMPATIBILE_QUERY_CACHE = TRUFFLE_ENCODING_COMPATIBLE_QUERY_CACHE.load();
public final int THREAD_CACHE = TRUFFLE_THREAD_CACHE.load();
public final int ROPE_CLASS_CACHE = TRUFFLE_ROPE_CLASS_CACHE.load();
public final int INTEROP_CONVERT_CACHE = TRUFFLE_INTEROP_CONVERT_CACHE.load();
public final int INTEROP_EXECUTE_CACHE = TRUFFLE_INTEROP_EXECUTE_CACHE.load();

// Cloning and inlining

Original file line number Diff line number Diff line change
@@ -34,6 +34,10 @@ public static boolean isDouble(Object value) {
return value instanceof Double;
}

public static boolean isString(Object value) {
return value instanceof String;
}

public static boolean isJavaCharSequence(Object value) {
return value instanceof CharSequence;
}