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: 3088a62ee1de
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a1e5aec97ada
Choose a head ref
  • 5 commits
  • 4 files changed
  • 1 contributor

Commits on Apr 4, 2016

  1. Copy the full SHA
    acf1f2a View commit details
  2. Copy the full SHA
    52fd1b2 View commit details
  3. Copy the full SHA
    46ce80d View commit details
  4. Copy the full SHA
    3a0d3e3 View commit details
  5. Copy the full SHA
    a1e5aec View commit details
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/util/cli/Options.java
Original file line number Diff line number Diff line change
@@ -262,6 +262,7 @@ public class Options {
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<Integer> TRUFFLE_INTEROP_READ_CACHE = integer(TRUFFLE, "truffle.interop.read.cache", TRUFFLE_DEFAULT_CACHE.load(), "Cache size for interop READ 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.");
382 changes: 218 additions & 164 deletions truffle/src/main/java/org/jruby/truffle/interop/ForeignReadNode.java
Original file line number Diff line number Diff line change
@@ -10,249 +10,303 @@
package org.jruby.truffle.interop;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
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.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.Node;
import com.oracle.truffle.api.nodes.Node.Child;
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.core.Layouts;
import org.jruby.truffle.core.module.ModuleOperations;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.string.StringCachingGuards;
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 org.jruby.truffle.language.methods.InternalMethod;
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.language.dispatch.DoesRespondDispatchHeadNode;
import org.jruby.truffle.language.objects.ReadObjectFieldNode;
import org.jruby.truffle.language.objects.ReadObjectFieldNodeGen;

@AcceptMessage(value = "READ", receiverType = RubyObjectType.class, language = RubyLanguage.class)
public final class ForeignReadNode extends ForeignReadBaseNode {

@Child private Node findContextNode;
@Child private RubyNode interopNode;
@Child private StringCachingHelperNode helperNode;

@Override
public Object access(VirtualFrame frame, DynamicObject object, Object name) {
return getInteropNode().execute(frame);
public Object access(VirtualFrame frame, DynamicObject object, Object label) {
return getHelperNode().executeStringCachingHelper(frame, object, label);
}

private RubyNode getInteropNode() {
if (interopNode == null) {
private StringCachingHelperNode getHelperNode() {
if (helperNode == null) {
CompilerDirectives.transferToInterpreter();
findContextNode = insert(RubyLanguage.INSTANCE.unprotectedCreateFindContextNode());
final RubyContext context = RubyLanguage.INSTANCE.unprotectedFindContext(findContextNode);
interopNode = insert(new UnresolvedInteropReadNode(context, null));
helperNode = insert(ForeignReadNodeFactory.StringCachingHelperNodeGen.create(context, null, null, null));
}

return interopNode;
return helperNode;
}

public static class UnresolvedInteropReadNode extends RubyNode {
@ImportStatic(StringCachingGuards.class)
@NodeChildren({
@NodeChild("receiver"),
@NodeChild("label")
})
protected static abstract class StringCachingHelperNode extends RubyNode {

private final int labelIndex;

public UnresolvedInteropReadNode(RubyContext context, SourceSection sourceSection) {
public StringCachingHelperNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
this.labelIndex = 0;
}

@Override
public Object execute(VirtualFrame frame) {
Object label = ForeignAccess.getArguments(frame).get(labelIndex);
if (label instanceof String || RubyGuards.isRubySymbol(label) || label instanceof Integer) {
if (label instanceof String) {
String name = (String) label;
if (name.startsWith("@")) {
return this.replace(new InteropInstanceVariableReadNode(getContext(), getSourceSection(), name, labelIndex)).execute(frame);
}
}
DynamicObject receiver = (DynamicObject) ForeignAccess.getReceiver(frame);
public abstract Object executeStringCachingHelper(VirtualFrame frame, DynamicObject receiver, Object label);

@Specialization(
guards = {
"isRubyString(label)",
"ropesEqual(label, cachedRope)"
},
limit = "getCacheLimit()"
)
public Object cacheStringAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
@Cached("privatizeRope(label)") Rope cachedRope,
@Cached("ropeToString(cachedRope)") String cachedString,
@Cached("startsWithAt(cachedString)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, label, cachedString, cachedStartsWithAt);
}

if (RubyGuards.isRubyString(receiver)) {
// TODO CS 22-Mar-16 monomorphic, what happens if it fails for other objects?
return this.replace(new UnresolvedInteropStringReadNode(getContext(), getSourceSection())).execute(frame);
}
@Specialization(
guards = "isRubyString(label)",
contains = "cacheStringAndForward"
)
public Object uncachedStringAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
final String labelString = objectToString(label);
return nextHelper.executeStringCachedHelper(frame, receiver, label, labelString, startsWithAt(labelString));
}

InternalMethod labelMethod = ModuleOperations.lookupMethod(coreLibrary().getMetaClass(receiver), label.toString());
InternalMethod indexedSetter = ModuleOperations.lookupMethod(coreLibrary().getMetaClass(receiver), "[]=");
if (labelMethod == null && indexedSetter != null) {
return this.replace(new ResolvedInteropIndexedReadNode(getContext(), getSourceSection(), labelIndex)).execute(frame);
} else if (label instanceof String) {
return this.replace(new ResolvedInteropReadNode(getContext(), getSourceSection(), (String) label, labelIndex)).execute(frame);
} else if (RubyGuards.isRubySymbol(label)) {
return this.replace(new ResolvedInteropReadFromSymbolNode(getContext(), getSourceSection(), (DynamicObject) label, labelIndex)).execute(frame);
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
}
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
}
@Specialization(
guards = {
"isRubySymbol(label)",
"label == cachedLabel"
},
limit = "getCacheLimit()"
)
public Object cacheSymbolAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
@Cached("label") DynamicObject cachedLabel,
@Cached("objectToString(cachedLabel)") String cachedString,
@Cached("startsWithAt(cachedString)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, cachedLabel, cachedString, cachedStartsWithAt);
}
}

public static class ResolvedInteropReadNode extends RubyNode {
@Specialization(
guards = "isRubySymbol(label)",
contains = "cacheSymbolAndForward"
)
public Object uncachedSymbolAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
final String labelString = objectToString(label);
return nextHelper.executeStringCachedHelper(frame, receiver, label, labelString, startsWithAt(labelString));
}

@Child private DispatchHeadNode head;
private final String name;
private final int labelIndex;
@Specialization(
guards = "label == cachedLabel",
limit = "getCacheLimit()"
)
public Object cacheJavaStringAndForward(VirtualFrame frame,
DynamicObject receiver,
String label,
@Cached("label") String cachedLabel,
@Cached("startsWithAt(cachedLabel)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, cachedLabel, cachedLabel, cachedStartsWithAt);
}

public ResolvedInteropReadNode(RubyContext context, SourceSection sourceSection, String name, int labelIndex) {
super(context, sourceSection);
this.name = name;
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.labelIndex = labelIndex;
@Specialization(contains = "cacheJavaStringAndForward")
public Object uncachedJavaStringAndForward(VirtualFrame frame,
DynamicObject receiver,
String label,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, label, label, startsWithAt(label));
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
return head.dispatch(frame, ForeignAccess.getReceiver(frame), name, null, new Object[]{});
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Name changed");
}
protected StringCachedHelperNode createNextHelper() {
return ForeignReadNodeFactory.StringCachedHelperNodeGen.create(getContext(), null, null, null, null, null);
}
}

public static class ResolvedInteropIndexedReadNode extends RubyNode {
@TruffleBoundary
protected String objectToString(DynamicObject string) {
return string.toString();
}

private final String name;
@Child private DispatchHeadNode head;
@Child private ForeignToRubyNode toRubyIndex;
private final int indexIndex;
protected String ropeToString(Rope rope) {
return RopeOperations.decodeRope(getContext().getJRubyRuntime(), rope);
}

public ResolvedInteropIndexedReadNode(RubyContext context, SourceSection sourceSection, int indexIndex) {
super(context, sourceSection);
this.name = "[]";
this.indexIndex = indexIndex;
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.toRubyIndex = ForeignToRubyNodeGen.create(context, sourceSection, null);
@TruffleBoundary
protected boolean startsWithAt(String label) {
return !label.isEmpty() && label.charAt(0) == '@';
}

@Override
public Object execute(VirtualFrame frame) {
Object index = toRubyIndex.executeConvert(frame, ForeignAccess.getArguments(frame).get(indexIndex));
return head.dispatch(frame, ForeignAccess.getReceiver(frame), name, null, new Object[] {index});
@Specialization(guards = {
"isRubyString(receiver)",
"index < 0"
})
public int indexStringNegative(DynamicObject receiver, int index) {
return 0;
}
}

public static class ResolvedInteropReadFromSymbolNode extends RubyNode {
@Specialization(guards = {
"isRubyString(receiver)",
"index >= 0",
"!inRange(receiver, index)"
})
public int indexStringOutOfRange(DynamicObject receiver, int index) {
return 0;
}

@Child private DispatchHeadNode head;
private final DynamicObject name;
private final int labelIndex;
@Specialization(guards = {
"isRubyString(receiver)",
"index >= 0",
"inRange(receiver, index)"
})
public int indexString(DynamicObject receiver, int index) {
return Layouts.STRING.getRope(receiver).get(index);
}

public ResolvedInteropReadFromSymbolNode(RubyContext context, SourceSection sourceSection, DynamicObject name, int labelIndex) {
super(context, sourceSection);
this.name = name;
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.labelIndex = labelIndex;
protected boolean inRange(DynamicObject string, int index) {
return index < Layouts.STRING.getRope(string).byteLength();
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
return head.dispatch(frame, ForeignAccess.getReceiver(frame), name, null, new Object[]{});
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Name changed");
}
protected int getCacheLimit() {
return getContext().getOptions().INTEROP_READ_CACHE;
}

}

public static class InteropInstanceVariableReadNode extends RubyNode {
@NodeChildren({
@NodeChild("receiver"),
@NodeChild("label"),
@NodeChild("stringLabel"),
@NodeChild("startsAt")
})
protected static abstract class StringCachedHelperNode extends RubyNode {

@Child private DoesRespondDispatchHeadNode definedNode;
@Child private DoesRespondDispatchHeadNode indexDefinedNode;
@Child private CallDispatchHeadNode callNode;

@Child private ReadObjectFieldNode read;
private final String name;
private final int labelIndex;
protected final static String INDEX_METHOD_NAME = "[]";

public InteropInstanceVariableReadNode(RubyContext context, SourceSection sourceSection, String name, int labelIndex) {
public StringCachedHelperNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
this.name = name;
this.read = ReadObjectFieldNodeGen.create(context, name, nil());
this.labelIndex = labelIndex;
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
return read.execute((DynamicObject) ForeignAccess.getReceiver(frame));
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Not implemented");
}
}
}
public abstract Object executeStringCachedHelper(VirtualFrame frame, DynamicObject receiver, Object label,
String stringLabel, boolean startsAt);

public static class UnresolvedInteropStringReadNode extends RubyNode {
@Specialization(guards = "startsAt(startsAt)")
public Object readInstanceVariable(DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt,
@Cached("createReadObjectFieldNode(stringLabel)") ReadObjectFieldNode readObjectFieldNode) {
return readObjectFieldNode.execute(receiver);
}

private final int labelIndex;
protected boolean startsAt(boolean startsAt) {
return startsAt;
}

public UnresolvedInteropStringReadNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
this.labelIndex = 0;
protected ReadObjectFieldNode createReadObjectFieldNode(String label) {
return ReadObjectFieldNodeGen.create(getContext(), label, nil());
}

@Override
public Object execute(VirtualFrame frame) {
Object label = ForeignAccess.getArguments(frame).get(labelIndex);
if (label instanceof String || RubyGuards.isRubySymbol(label) || label instanceof Integer) {
if (label instanceof String) {
String name = (String) label;
if (name.startsWith("@")) {
return this.replace(new InteropInstanceVariableReadNode(getContext(), getSourceSection(), name, labelIndex)).execute(frame);
}
@Specialization(
guards = {
"notStartsAt(startsAt)",
"methodDefined(frame, receiver, stringLabel, getDefinedNode())"
}
if (label instanceof Integer || label instanceof Long) {
return this.replace(new InteropReadStringByteNode(getContext(), getSourceSection(), labelIndex)).execute(frame);
} else if (label instanceof String) {
return this.replace(new ResolvedInteropReadNode(getContext(), getSourceSection(), (String) label, labelIndex)).execute(frame);
} else if (RubyGuards.isRubySymbol(label)) {
return this.replace(new ResolvedInteropReadFromSymbolNode(getContext(), getSourceSection(), (DynamicObject) label, labelIndex)).execute(frame);
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
)
public Object callMethod(VirtualFrame frame,
DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt) {
return getCallNode().call(frame, receiver, stringLabel, null);
}

@Specialization(
guards = {
"notStartsAt(startsAt)",
"!methodDefined(frame, receiver, stringLabel, getDefinedNode())",
"methodDefined(frame, receiver, INDEX_METHOD_NAME, getIndexDefinedNode())"
}
} else {
)
public Object index(VirtualFrame frame,
DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt) {
return getCallNode().call(frame, receiver, "[]", null, label);
}

protected boolean notStartsAt(boolean startsAt) {
return !startsAt;
}

protected DoesRespondDispatchHeadNode getDefinedNode() {
if (definedNode == null) {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
definedNode = insert(new DoesRespondDispatchHeadNode(getContext(), true));
}

return definedNode;
}
}

public static class InteropReadStringByteNode extends RubyNode {
protected DoesRespondDispatchHeadNode getIndexDefinedNode() {
if (indexDefinedNode == null) {
CompilerDirectives.transferToInterpreter();
indexDefinedNode = insert(new DoesRespondDispatchHeadNode(getContext(), true));
}

private final int labelIndex;
return indexDefinedNode;
}

public InteropReadStringByteNode(RubyContext context, SourceSection sourceSection, int labelIndex) {
super(context, sourceSection);
this.labelIndex = labelIndex;
protected boolean methodDefined(VirtualFrame frame, DynamicObject receiver, String stringLabel,
DoesRespondDispatchHeadNode definedNode) {
return definedNode.doesRespondTo(frame, stringLabel, receiver);
}

@Override
public Object execute(VirtualFrame frame) {
if (RubyGuards.isRubyString(ForeignAccess.getReceiver(frame))) {
final DynamicObject string = (DynamicObject) ForeignAccess.getReceiver(frame);
final int index = (int) ForeignAccess.getArguments(frame).get(labelIndex);
if (index >= Layouts.STRING.getRope(string).byteLength()) {
return 0;
} else {
return (byte) StringOperations.getByteListReadOnly(string).get(index);
}
} else {
protected CallDispatchHeadNode getCallNode() {
if (callNode == null) {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Not implemented");
callNode = insert(DispatchHeadNodeFactory.createMethodCall(getContext(), true));
}

return callNode;
}

}

}
375 changes: 236 additions & 139 deletions truffle/src/main/java/org/jruby/truffle/interop/ForeignWriteNode.java
Original file line number Diff line number Diff line change
@@ -10,199 +10,296 @@
package org.jruby.truffle.interop;

import com.oracle.truffle.api.CompilerDirectives;
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.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.Node;
import com.oracle.truffle.api.nodes.Node.Child;
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.core.Layouts;
import org.jruby.truffle.core.module.ModuleOperations;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.string.StringCachingGuards;
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 org.jruby.truffle.language.methods.InternalMethod;
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.language.dispatch.DoesRespondDispatchHeadNode;
import org.jruby.truffle.language.objects.WriteObjectFieldNode;
import org.jruby.truffle.language.objects.WriteObjectFieldNodeGen;

@AcceptMessage(value = "WRITE", receiverType = RubyObjectType.class, language = RubyLanguage.class)
public final class ForeignWriteNode extends ForeignWriteBaseNode {

@Child private Node findContextNode;
@Child private RubyNode interopNode;
@Child private StringCachingHelperNode helperNode;

@Override
public Object access(VirtualFrame frame, DynamicObject object, Object name, Object value) {
return getInteropNode().execute(frame);
return getHelperNode().executeStringCachingHelper(frame, object, name, value);
}

private RubyNode getInteropNode() {
if (interopNode == null) {
private StringCachingHelperNode getHelperNode() {
if (helperNode == null) {
CompilerDirectives.transferToInterpreter();
findContextNode = insert(RubyLanguage.INSTANCE.unprotectedCreateFindContextNode());
final RubyContext context = RubyLanguage.INSTANCE.unprotectedFindContext(findContextNode);
interopNode = insert(new UnresolvedInteropWriteNode(context, null));
helperNode = insert(ForeignWriteNodeFactory.StringCachingHelperNodeGen.create(
context, null, null, null, null));
}

return interopNode;
return helperNode;
}

public static class UnresolvedInteropWriteNode extends RubyNode {
@ImportStatic(StringCachingGuards.class)
@NodeChildren({
@NodeChild("receiver"),
@NodeChild("label"),
@NodeChild("value")
})
protected static abstract class StringCachingHelperNode extends RubyNode {

private final int labelIndex;
private final int valueIndex;

public UnresolvedInteropWriteNode(RubyContext context, SourceSection sourceSection) {
public StringCachingHelperNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
this.labelIndex = 0;
this.valueIndex = 1;
}

@Override
public Object execute(VirtualFrame frame) {
Object label = ForeignAccess.getArguments(frame).get(labelIndex);
if (label instanceof String || RubyGuards.isRubySymbol(label) || label instanceof Integer) {
if (label instanceof String) {
String name = (String) label;
if (name.startsWith("@")) {
return this.replace(new InteropInstanceVariableWriteNode(getContext(), getSourceSection(), name, labelIndex, valueIndex)).execute(frame);
}
}
DynamicObject receiver = (DynamicObject) ForeignAccess.getReceiver(frame);
InternalMethod labelMethod = ModuleOperations.lookupMethod(coreLibrary().getMetaClass(receiver), label.toString());
InternalMethod indexedSetter = ModuleOperations.lookupMethod(coreLibrary().getMetaClass(receiver), "[]=");
if (labelMethod == null && indexedSetter != null) {
return this.replace(new ResolvedInteropIndexedWriteNode(getContext(), getSourceSection(), labelIndex, valueIndex)).execute(frame);
} else if (label instanceof String) {
return this.replace(new ResolvedInteropWriteNode(getContext(), getSourceSection(), (String) label, labelIndex, valueIndex)).execute(frame);
} else if (RubyGuards.isRubySymbol(label)) {
return this.replace(new ResolvedInteropWriteToSymbolNode(getContext(), getSourceSection(), (DynamicObject) label, labelIndex, valueIndex)).execute(frame);
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
}
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException(label + " not allowed as name");
}
}
}

public static class ResolvedInteropWriteNode extends RubyNode {
public abstract Object executeStringCachingHelper(VirtualFrame frame, DynamicObject receiver,
Object label, Object value);

@Child private DispatchHeadNode head;
private final String name;
private final String accessName;
private final int labelIndex;
private final int valueIndex;
@Specialization(
guards = {
"isRubyString(label)",
"ropesEqual(label, cachedRope)"
},
limit = "getCacheLimit()"
)
public Object cacheStringAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
Object value,
@Cached("privatizeRope(label)") Rope cachedRope,
@Cached("ropeToString(cachedRope)") String cachedString,
@Cached("startsWithAt(cachedString)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, label, cachedString, cachedStartsWithAt, value);
}

public ResolvedInteropWriteNode(RubyContext context, SourceSection sourceSection, String name, int labelIndex, int valueIndex) {
super(context, sourceSection);
this.name = name;
this.accessName = name + "=";
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.labelIndex = labelIndex;
this.valueIndex = valueIndex;
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
Object value = ForeignAccess.getArguments(frame).get(valueIndex);
return head.dispatch(frame, ForeignAccess.getReceiver(frame), accessName, null, new Object[]{value});
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Name changed");
}
@Specialization(
guards = "isRubyString(label)",
contains = "cacheStringAndForward"
)
public Object uncachedStringAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
Object value,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
final String labelString = objectToString(label);
return nextHelper.executeStringCachedHelper(frame, receiver, label, labelString,
startsWithAt(labelString), value);
}
}

public static class ResolvedInteropWriteToSymbolNode extends RubyNode {
@Specialization(
guards = {
"isRubySymbol(label)",
"label == cachedLabel"
},
limit = "getCacheLimit()"
)
public Object cacheSymbolAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
Object value,
@Cached("label") DynamicObject cachedLabel,
@Cached("objectToString(cachedLabel)") String cachedString,
@Cached("startsWithAt(cachedString)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, cachedLabel, cachedString,
cachedStartsWithAt, value);
}

@Child private DispatchHeadNode head;
private final DynamicObject name;
private final DynamicObject accessName;
private final int labelIndex;
private final int valueIndex;
@Specialization(
guards = "isRubySymbol(label)",
contains = "cacheSymbolAndForward"
)
public Object uncachedSymbolAndForward(VirtualFrame frame,
DynamicObject receiver,
DynamicObject label,
Object value,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
final String labelString = objectToString(label);
return nextHelper.executeStringCachedHelper(frame, receiver, label, labelString,
startsWithAt(labelString), value);
}

public ResolvedInteropWriteToSymbolNode(RubyContext context, SourceSection sourceSection, DynamicObject name, int labelIndex, int valueIndex) {
super(context, sourceSection);
this.name = name;
this.accessName = context.getSymbolTable().getSymbol(Layouts.SYMBOL.getString(name) + "=");
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.labelIndex = labelIndex;
this.valueIndex = valueIndex;
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
Object value = ForeignAccess.getArguments(frame).get(valueIndex);
return head.dispatch(frame, ForeignAccess.getReceiver(frame), accessName, null, new Object[]{value});
} else {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Name changed");
}
@Specialization(
guards = "label == cachedLabel",
limit = "getCacheLimit()"
)
public Object cacheJavaStringAndForward(VirtualFrame frame,
DynamicObject receiver,
String label,
Object value,
@Cached("label") String cachedLabel,
@Cached("startsWithAt(cachedLabel)") boolean cachedStartsWithAt,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, cachedLabel, cachedLabel,
cachedStartsWithAt, value);
}

@Specialization(contains = "cacheJavaStringAndForward")
public Object uncachedJavaStringAndForward(VirtualFrame frame,
DynamicObject receiver,
String label,
Object value,
@Cached("createNextHelper()") StringCachedHelperNode nextHelper) {
return nextHelper.executeStringCachedHelper(frame, receiver, label, label, startsWithAt(label), value);
}

protected StringCachedHelperNode createNextHelper() {
return ForeignWriteNodeFactory.StringCachedHelperNodeGen.create(
getContext(), null, null, null, null, null, null);
}

@CompilerDirectives.TruffleBoundary
protected String objectToString(DynamicObject string) {
return string.toString();
}

protected String ropeToString(Rope rope) {
return RopeOperations.decodeRope(getContext().getJRubyRuntime(), rope);
}

@CompilerDirectives.TruffleBoundary
protected boolean startsWithAt(String label) {
return !label.isEmpty() && label.charAt(0) == '@';
}

protected int getCacheLimit() {
return getContext().getOptions().INTEROP_READ_CACHE;
}

}

public static class InteropInstanceVariableWriteNode extends RubyNode {
@NodeChildren({
@NodeChild("receiver"),
@NodeChild("label"),
@NodeChild("stringLabel"),
@NodeChild("startsAt"),
@NodeChild("value")
})
protected static abstract class StringCachedHelperNode extends RubyNode {

@Child private DoesRespondDispatchHeadNode definedNode;
@Child private DoesRespondDispatchHeadNode indexDefinedNode;
@Child private CallDispatchHeadNode callNode;

@Child private WriteObjectFieldNode write;
private final String name;
private final int labelIndex;
private final int valueIndex;
protected final static String INDEX_METHOD_NAME = "[]=";

public InteropInstanceVariableWriteNode(RubyContext context, SourceSection sourceSection, String name, int labelIndex, int valueIndex) {
public StringCachedHelperNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
this.name = name;
this.labelIndex = labelIndex;
this.valueIndex = valueIndex;
this.write = WriteObjectFieldNodeGen.create(context, name);
}

@Override
public Object execute(VirtualFrame frame) {
if (name.equals(ForeignAccess.getArguments(frame).get(labelIndex))) {
final Object value = ForeignAccess.getArguments(frame).get(valueIndex);
write.execute((DynamicObject) ForeignAccess.getReceiver(frame), value);
return value;
} else {
}

public abstract Object executeStringCachedHelper(VirtualFrame frame, DynamicObject receiver, Object label,
String stringLabel, boolean startsAt, Object value);

@Specialization(guards = "startsAt(startsAt)")
public Object readInstanceVariable(DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt,
Object value,
@Cached("createWriteObjectFieldNode(stringLabel)") WriteObjectFieldNode writeObjectFieldNode) {
writeObjectFieldNode.execute(receiver, value);
return value;
}

protected boolean startsAt(boolean startsAt) {
return startsAt;
}

protected WriteObjectFieldNode createWriteObjectFieldNode(String label) {
return WriteObjectFieldNodeGen.create(getContext(), label);
}

@Specialization(
guards = {
"notStartsAt(startsAt)",
"methodDefined(frame, receiver, writeMethodName, getDefinedNode())"
}
)
public Object callMethod(VirtualFrame frame,
DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt,
Object value,
@Cached("createWriteMethodName(stringLabel)") String writeMethodName) {
return getCallNode().call(frame, receiver, writeMethodName, null, value);
}

protected String createWriteMethodName(String label) {
return label + "=";
}

@Specialization(
guards = {
"notStartsAt(startsAt)",
"!methodDefined(frame, receiver, writeMethodName, getDefinedNode())",
"methodDefined(frame, receiver, INDEX_METHOD_NAME, getIndexDefinedNode())"
}
)
public Object index(VirtualFrame frame,
DynamicObject receiver,
Object label,
String stringLabel,
boolean startsAt,
Object value,
@Cached("createWriteMethodName(stringLabel)") String writeMethodName) {
return getCallNode().call(frame, receiver, "[]", null, label, value);
}

protected boolean notStartsAt(boolean startsAt) {
return !startsAt;
}

protected DoesRespondDispatchHeadNode getDefinedNode() {
if (definedNode == null) {
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Not implemented");
definedNode = insert(new DoesRespondDispatchHeadNode(getContext(), true));
}

return definedNode;
}
}

public static class ResolvedInteropIndexedWriteNode extends RubyNode {
protected DoesRespondDispatchHeadNode getIndexDefinedNode() {
if (indexDefinedNode == null) {
CompilerDirectives.transferToInterpreter();
indexDefinedNode = insert(new DoesRespondDispatchHeadNode(getContext(), true));
}

private final String name;
@Child private DispatchHeadNode head;
@Child private ForeignToRubyNode toRubyIndex;
private final int indexIndex;
private final int valueIndex;
return indexDefinedNode;
}

public ResolvedInteropIndexedWriteNode(RubyContext context, SourceSection sourceSection, int indexIndex, int valueIndex) {
super(context, sourceSection);
this.name = "[]=";
this.indexIndex = indexIndex;
this.valueIndex = valueIndex;
this.head = new DispatchHeadNode(context, true, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD);
this.toRubyIndex = ForeignToRubyNodeGen.create(context, sourceSection, null);
protected boolean methodDefined(VirtualFrame frame, DynamicObject receiver, String stringLabel,
DoesRespondDispatchHeadNode definedNode) {
return definedNode.doesRespondTo(frame, stringLabel, receiver);
}

@Override
public Object execute(VirtualFrame frame) {
Object index = toRubyIndex.executeConvert(frame, ForeignAccess.getArguments(frame).get(indexIndex));
Object value = ForeignAccess.getArguments(frame).get(valueIndex);
return head.dispatch(frame, ForeignAccess.getReceiver(frame), name, null, new Object[] {index, value});
protected CallDispatchHeadNode getCallNode() {
if (callNode == null) {
CompilerDirectives.transferToInterpreter();
callNode = insert(DispatchHeadNodeFactory.createMethodCall(getContext(), true));
}

return callNode;
}

}

}
2 changes: 2 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/language/Options.java
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@
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_INTEROP_READ_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;
@@ -113,6 +114,7 @@ public class Options {
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();
public final int INTEROP_READ_CACHE = TRUFFLE_INTEROP_READ_CACHE.load();

// Cloning and inlining