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: 1f3821639090
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 9ecd3ddbd01c
Choose a head ref
  • 3 commits
  • 10 files changed
  • 1 contributor

Commits on Mar 20, 2015

  1. Copy the full SHA
    d1dd8ab View commit details
  2. Copy the full SHA
    78024be View commit details
  3. Copy the full SHA
    9ecd3dd View commit details
182 changes: 181 additions & 1 deletion truffle/src/main/java/org/jruby/truffle/nodes/RubyCallNode.java
Original file line number Diff line number Diff line change
@@ -20,21 +20,32 @@
import org.jruby.truffle.nodes.cast.ProcOrNullNode;
import org.jruby.truffle.nodes.cast.ProcOrNullNodeFactory;
import org.jruby.truffle.nodes.dispatch.*;
import org.jruby.truffle.nodes.literal.HashLiteralNode;
import org.jruby.truffle.nodes.literal.ObjectLiteralNode;
import org.jruby.truffle.nodes.methods.MarkerNode;
import org.jruby.truffle.nodes.methods.arguments.OptionalKeywordArgMissingNode;
import org.jruby.truffle.nodes.methods.arguments.UnknownArgumentErrorNode;
import org.jruby.truffle.runtime.ModuleOperations;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyProc;
import org.jruby.truffle.runtime.core.RubySymbol;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.util.ArrayUtils;

import java.util.ArrayList;
import java.util.List;

public class RubyCallNode extends RubyNode {

private final String methodName;

@Child private RubyNode receiver;
@Child private ProcOrNullNode block;
@Children private final RubyNode[] arguments;
@Children private final RubyNode[] keywordOptimizedArguments;
@CompilerDirectives.CompilationFinal private int keywordOptimizedArgumentsLength;

private final boolean isSplatted;
private final boolean isVCall;
@@ -54,6 +65,8 @@ public class RubyCallNode extends RubyNode {

private final boolean ignoreVisibility;

@CompilerDirectives.CompilationFinal private boolean cannotOptimize;

public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, RubyNode... arguments) {
this(context, section, methodName, receiver, block, isSplatted, false, false, arguments);
}
@@ -84,12 +97,53 @@ public RubyCallNode(RubyContext context, SourceSection section, String methodNam
respondToMissingCast = BooleanCastNodeFactory.create(context, section, null);

this.ignoreVisibility = ignoreVisibility;

/*
* TODO CS 19-Mar-15 we currently can't swap an @Children array out
* so we just allocate a lot up-front. In a future version of Truffle
* @Children might not need to be final, which would fix this.
*/
keywordOptimizedArguments = new RubyNode[arguments.length + 32];
}

@Override
public Object execute(VirtualFrame frame) {
final Object receiverObject = receiver.execute(frame);
final Object[] argumentsObjects = executeArguments(frame);

final Object[] argumentsObjects;

if (dispatchHead.getFirstDispatchNode().couldOptimizeKeywordArguments() && !cannotOptimize) {
final CachedBoxedDispatchNode dispatchNode = (CachedBoxedDispatchNode) dispatchHead.getFirstDispatchNode();

if (keywordOptimizedArguments[0] == null) {
CompilerDirectives.transferToInterpreter();

System.err.println("optimizing for keyword arguments!");

final RubyNode[] optimized = expandedArgumentNodes(dispatchNode.getMethod(), arguments, isSplatted);

if (optimized == null || optimized.length > keywordOptimizedArguments.length) {
System.err.println("couldn't optimize :(");
cannotOptimize = true;
} else {
keywordOptimizedArgumentsLength = optimized.length;

for (int n = 0; n < keywordOptimizedArgumentsLength; n++) {
keywordOptimizedArguments[n] = optimized[n];
}
}
}

if (dispatchNode.guard(methodName, receiverObject) && dispatchNode.getUnmodifiedAssumption().isValid()) {
argumentsObjects = executeKeywordOptimizedArguments(frame);
} else {
argumentsObjects = executeArguments(frame);
}

} else {
argumentsObjects = executeArguments(frame);
}

final RubyProc blockObject = executeBlock(frame);

return dispatchHead.call(frame, receiverObject, methodName, blockObject, argumentsObjects);
@@ -118,6 +172,21 @@ private Object[] executeArguments(VirtualFrame frame) {
}
}

@ExplodeLoop
private Object[] executeKeywordOptimizedArguments(VirtualFrame frame) {
final Object[] argumentsObjects = new Object[keywordOptimizedArgumentsLength];

for (int i = 0; i < keywordOptimizedArgumentsLength; i++) {
argumentsObjects[i] = keywordOptimizedArguments[i].execute(frame);
}

if (isSplatted) {
return splat(argumentsObjects[0]);
} else {
return argumentsObjects;
}
}

private Object[] splat(Object argument) {
// TODO(CS): what happens if isn't just one argument, or it isn't an Array?

@@ -165,6 +234,117 @@ private Object[] splat(Object argument) {
throw new UnsupportedOperationException();
}

public RubyNode[] expandedArgumentNodes(InternalMethod method, RubyNode[] argumentNodes, boolean isSplatted) {
final RubyNode[] result;

boolean shouldExpand = true;
if (method == null
|| method.getSharedMethodInfo().getArity().getKeywordArguments() == null) {
// no keyword arguments in method definition
shouldExpand = false;
} else if (argumentNodes.length != 0
&& !(argumentNodes[argumentNodes.length - 1] instanceof HashLiteralNode)) {
// last argument is not a Hash that could be expanded
shouldExpand = false;
} else if (method.getSharedMethodInfo().getArity() == null
|| method.getSharedMethodInfo().getArity().getRequired() >= argumentNodes.length) {
shouldExpand = false;
} else if (isSplatted
|| method.getSharedMethodInfo().getArity().allowsMore()) {
// TODO: make optimization work if splat arguments are involed
// the problem is that Markers and keyword args are used when
// reading splatted args
shouldExpand = false;
}

if (shouldExpand) {
List<String> kwargs = method.getSharedMethodInfo().getArity().getKeywordArguments();

int countArgNodes = argumentNodes.length + kwargs.size() + 1;
if (argumentNodes.length == 0) {
countArgNodes++;
}

result = new RubyNode[countArgNodes];
int i;

for (i = 0; i < argumentNodes.length - 1; ++i) {
result[i] = argumentNodes[i];
}

int firstMarker = i++;
result[firstMarker] = new MarkerNode(getContext(), null);

HashLiteralNode hashNode;
if (argumentNodes.length > 0) {
hashNode = (HashLiteralNode) argumentNodes[argumentNodes.length - 1];
} else {
hashNode = HashLiteralNode.create(getContext(), null,
new RubyNode[0]);
}

List<String> restKeywordLabels = new ArrayList<>();
for (int j = 0; j < hashNode.size(); j++) {
Object key = hashNode.getKey(j);
boolean keyIsSymbol = key instanceof ObjectLiteralNode &&
((ObjectLiteralNode) key).getObject() instanceof RubySymbol;

if (!keyIsSymbol) {
// cannot optimize case where keyword label is dynamic (not a fixed RubySymbol)
return argumentNodes;
}

final String label = ((ObjectLiteralNode) hashNode.getKey(j)).getObject().toString();
restKeywordLabels.add(label);
}

for (String kwarg : kwargs) {
result[i] = new OptionalKeywordArgMissingNode(getContext(), null);
for (int j = 0; j < hashNode.size(); j++) {
final String label = ((ObjectLiteralNode) hashNode.getKey(j)).getObject().toString();

if (label.equals(kwarg)) {
result[i] = hashNode.getValue(j);
restKeywordLabels.remove(label);
break;
}
}
i++;
}
result[i++] = new MarkerNode(getContext(), null);

if (restKeywordLabels.size() > 0
&& !method.getSharedMethodInfo().getArity().hasKeyRest()) {
result[firstMarker] = new UnknownArgumentErrorNode(getContext(), null, restKeywordLabels.get(0));
} else if (restKeywordLabels.size() > 0) {
i = 0;
RubyNode[] keyValues = new RubyNode[2 * restKeywordLabels
.size()];

for (String label : restKeywordLabels) {
for (int j = 0; j < hashNode.size(); j++) {
final String argLabel = ((ObjectLiteralNode) hashNode.getKey(j)).getObject().toString();

if (argLabel.equals(label)) {
keyValues[i++] = hashNode.getKey(j);
keyValues[i++] = hashNode.getValue(j);
}
}
}

HashLiteralNode restHash = HashLiteralNode.create(getContext(), null, keyValues);
result[firstMarker] = restHash;
}

}
else {
cannotOptimize = true;
result = null;
}

return result;
}

@Override
public Object isDefined(VirtualFrame frame) {
notDesignedForCompilation();
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ public CachedBoxedDispatchNode(
}

@Override
protected boolean guard(Object methodName, Object receiver) {
public boolean guard(Object methodName, Object receiver) {
return guardName(methodName) &&
(receiver instanceof RubyBasicObject) &&
((RubyBasicObject) receiver).getMetaClass() == expectedClass;
@@ -173,4 +173,15 @@ public String toString() {
method == null ? "null" : method.toString());
}

public boolean couldOptimizeKeywordArguments() {
return method.getSharedMethodInfo().getArity().getKeywordArguments() != null && next instanceof UnresolvedDispatchNode;
}

public InternalMethod getMethod() {
return method;
}

public Assumption getUnmodifiedAssumption() {
return unmodifiedAssumption;
}
}
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.core.RubySymbol;
import org.jruby.truffle.runtime.methods.InternalMethod;

public abstract class CachedDispatchNode extends DispatchNode {

@@ -89,5 +90,4 @@ protected RubySymbol getCachedNameAsSymbol() {
public boolean isIndirect() {
return indirect;
}

}
Original file line number Diff line number Diff line change
@@ -152,4 +152,8 @@ public DispatchAction getDispatchAction() {
return dispatchAction;
}

public boolean couldOptimizeKeywordArguments() {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -69,6 +69,7 @@ public void executeVoid(VirtualFrame frame) {
if (!keywordsRest && keywordArguments != null) {
for (KeyValue keyValue : HashOperations.verySlowToKeyValues(keywordArguments)) {
if (!keywordAllowed(keyValue.getKey().toString())) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + keyValue.getKey().toString(), this));
}
}
Original file line number Diff line number Diff line change
@@ -9,8 +9,10 @@
*/
package org.jruby.truffle.nodes.methods.arguments;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.ConditionProfile;
import com.oracle.truffle.api.utilities.ValueProfile;

import org.jruby.truffle.nodes.RubyNode;
@@ -26,6 +28,9 @@ public class ReadKeywordArgumentNode extends RubyNode {
private final String name;
private final int kwIndex;
private final ValueProfile argumentValueProfile = ValueProfile.createPrimitiveProfile();

private ConditionProfile optimizedProfile = ConditionProfile.createBinaryProfile();
private ConditionProfile defaultProfile = ConditionProfile.createBinaryProfile();

@Child private RubyNode defaultValue;

@@ -39,44 +44,42 @@ public ReadKeywordArgumentNode(RubyContext context, SourceSection sourceSection,

@Override
public Object execute(VirtualFrame frame) {
if (RubyArguments.isKwOptimized(frame.getArguments())) {
if (optimizedProfile.profile(RubyArguments.isKwOptimized(frame.getArguments()))) {
Object kwarg = argumentValueProfile
.profile(RubyArguments.getOptimizedKeywordArgument(
frame.getArguments(), kwIndex));

if (kwarg instanceof OptionalKeywordArgMissingNode.OptionalKeywordArgMissing) {
if (defaultProfile.profile(kwarg instanceof OptionalKeywordArgMissingNode.OptionalKeywordArgMissing)) {
return defaultValue.execute(frame);
} else {
return kwarg;
}
} else {
return lookupKeywordInHash(frame);
}
}
final RubyHash hash = RubyArguments.getUserKeywordsHash(frame.getArguments(), minimum);

public Object lookupKeywordInHash(VirtualFrame frame) {
notDesignedForCompilation();
if (defaultProfile.profile(hash == null)) {
return defaultValue.execute(frame);
}

final RubyHash hash = RubyArguments.getUserKeywordsHash(frame.getArguments(), minimum);
Object value = lookupKeywordInHash(hash);

if (hash == null) {
return defaultValue.execute(frame);
}
if (defaultProfile.profile(value == null)) {
return defaultValue.execute(frame);
}

Object value = null;
return value;
}
}

@CompilerDirectives.TruffleBoundary
private Object lookupKeywordInHash(RubyHash hash) {
for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) {
if (keyValue.getKey().toString().equals(name)) {
value = keyValue.getValue();
break;
return keyValue.getValue();
}
}

if (value == null) {
return defaultValue.execute(frame);
}

return value;
return null;
}

}
Original file line number Diff line number Diff line change
@@ -99,7 +99,7 @@ public static int getUserArgumentsCount(Object[] internalArguments) {
public static int getNamedUserArgumentsCount(Object[] internalArguments) {
if (isKwOptimized(internalArguments)) {
return getUserArgumentsCount(internalArguments)
- getMethod(internalArguments).getSharedMethodInfo()
- getMethod(internalArguments).getSharedMethodInfo().getArity()
.getKeywordArguments().size() - 1;
} else {
return getUserArgumentsCount(internalArguments);
Loading