Skip to content

Commit

Permalink
Showing 53 changed files with 1,369 additions and 284 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyGenerator.java
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block b
IRubyObject proc;

if (args.length == 0) {
proc = RubyProc.newProc(runtime, block, Block.Type.LAMBDA);
proc = RubyProc.newProc(runtime, block, Block.Type.PROC);
} else {
if (!(args[0] instanceof RubyProc)) {
throw runtime.newTypeError(args[0], runtime.getProc());
42 changes: 3 additions & 39 deletions core/src/main/java/org/jruby/RubyString.java
Original file line number Diff line number Diff line change
@@ -1402,45 +1402,9 @@ public IRubyObject reverse(ThreadContext context) {

@JRubyMethod(name = "reverse")
public IRubyObject reverse19(ThreadContext context) {
Ruby runtime = context.runtime;
if (value.getRealSize() <= 1) return strDup(context.runtime);

byte[]bytes = value.getUnsafeBytes();
int p = value.getBegin();
int len = value.getRealSize();
byte[]obytes = new byte[len];

boolean single = true;
Encoding enc = value.getEncoding();
// this really needs to be inlined here
if (singleByteOptimizable(enc)) {
for (int i = 0; i <= len >> 1; i++) {
obytes[i] = bytes[p + len - i - 1];
obytes[len - i - 1] = bytes[p + i];
}
} else {
int end = p + len;
int op = len;
while (p < end) {
int cl = StringSupport.length(enc, bytes, p, end);
if (cl > 1 || (bytes[p] & 0x80) != 0) {
single = false;
op -= cl;
System.arraycopy(bytes, p, obytes, op, cl);
p += cl;
} else {
obytes[--op] = bytes[p++];
}
}
}

RubyString result = new RubyString(runtime, getMetaClass(), new ByteList(obytes, false));

if (getCodeRange() == CR_UNKNOWN) setCodeRange(single ? CR_7BIT : CR_VALID);
Encoding encoding = value.getEncoding();
result.value.setEncoding(encoding);
result.copyCodeRangeForSubstr(this, encoding);
return result.infectBy(this);
RubyString str = strDup(context.runtime);
str.reverse_bang19(context);
return str;
}

public RubyString reverse_bang(ThreadContext context) {
16 changes: 16 additions & 0 deletions core/src/main/java/org/jruby/ast/ArgsNode.java
Original file line number Diff line number Diff line change
@@ -154,6 +154,18 @@ protected Arity calculateArity() {
public boolean hasKwargs() {
return hasKwargs;
}

public int countKeywords() {
if (hasKwargs) {
if (keywords == null) {
// Rest keyword argument
return 0;
}
return keywords.size();
} else {
return 0;
}
}

protected boolean hasMasgnArgs() {
if (preCount > 0) for (Node node : pre.childNodes()) {
@@ -254,6 +266,10 @@ public KeywordRestArgNode getKeyRest() {
return keyRest;
}

public boolean hasKeyRest() {
return keyRest != null;
}

public void checkArgCount(Ruby runtime, int argsLength) {
Arity.checkArgumentCount(runtime, argsLength, requiredArgsCount, maxArgsCount, hasKwargs);
}
18 changes: 8 additions & 10 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Original file line number Diff line number Diff line change
@@ -187,23 +187,21 @@ public static IRubyObject initiateBreak(ThreadContext context, DynamicScope dynS
}
}

@Interp @JIT
@JIT
public static IRubyObject handleBreakAndReturnsInLambdas(ThreadContext context, StaticScope scope, DynamicScope dynScope, Object exc, Block.Type blockType) throws RuntimeException {
if ((exc instanceof IRBreakJump) && inNonMethodBodyLambda(scope, blockType)) {
// We just unwound all the way up because of a non-local break
if (((IRBreakJump)exc).scopeToReturnTo == dynScope) throw IRException.BREAK_LocalJumpError.getException(context.getRuntime());
}

if (exc instanceof IRReturnJump && (blockType == null || inLambda(blockType))) {
throw IRException.BREAK_LocalJumpError.getException(context.getRuntime());
} else if (exc instanceof IRReturnJump && (blockType == null || inLambda(blockType))) {
// Ignore non-local return processing in non-lambda blocks.
// Methods have a null blocktype
return handleNonlocalReturn(scope, dynScope, exc, blockType);
} else {
// Propagate
Helpers.throwException((Throwable)exc);
// should not get here
return null;
}

// Propagate
Helpers.throwException((Throwable)exc);
// should not get here
return null;
}

@JIT
1 change: 1 addition & 0 deletions spec/truffle/tags/core/array/delete_if_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fails:Array#delete_if returns self when called on an Array emptied with #shift
1 change: 0 additions & 1 deletion spec/truffle/tags/core/array/delete_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Array#delete may be given a block that is executed if no element matches object
fails:Array#delete raises a RuntimeError on a frozen array
13 changes: 0 additions & 13 deletions spec/truffle/tags/core/array/pop_tags.txt

This file was deleted.

4 changes: 1 addition & 3 deletions spec/truffle/tags/core/array/reject_tags.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
fails:Array#reject! removes elements for which block is true
fails:Array#reject! properly handles recursive arrays
fails:Array#reject! returns nil when called on an Array emptied with #shift
fails:Array#reject! returns nil if no changes are made

1 change: 0 additions & 1 deletion spec/truffle/tags/core/array/rindex_tags.txt

This file was deleted.

5 changes: 0 additions & 5 deletions spec/truffle/tags/core/array/sort_by_tags.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
fails:Array#sort_by! sorts array in place by passing each element to the given block
fails:Array#sort_by! returns an Enumerator if not given a block
fails:Array#sort_by! completes when supplied a block that always returns the same result
fails:Array#sort_by! raises a RuntimeError on a frozen array
fails:Array#sort_by! raises a RuntimeError on an empty frozen array
fails:Array#sort_by! returns the specified value when it would break in the given block
fails:Array#sort_by! makes some modification even if finished sorting when it would break in the given block
3 changes: 0 additions & 3 deletions spec/truffle/tags/core/string/append_tags.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
fails:String#<< with Integer concatencates the argument interpreted as a codepoint
fails:String#<< with Integer returns a ASCII-8BIT string if self is US-ASCII and the argument is between 128-255 (inclusive)
fails:String#<< with Integer raises RangeError if the argument is an invalid codepoint for self's encoding
fails:String#<< with Integer raises RangeError if the argument is negative
3 changes: 0 additions & 3 deletions spec/truffle/tags/core/string/chop_tags.txt

This file was deleted.

3 changes: 0 additions & 3 deletions spec/truffle/tags/core/string/concat_tags.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
fails:String#concat with Integer concatencates the argument interpreted as a codepoint
fails:String#concat with Integer returns a ASCII-8BIT string if self is US-ASCII and the argument is between 128-255 (inclusive)
fails:String#concat with Integer raises RangeError if the argument is an invalid codepoint for self's encoding
fails:String#concat with Integer raises RangeError if the argument is negative
1 change: 0 additions & 1 deletion spec/truffle/tags/core/string/delete_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:String#delete deletes multibyte characters
fails:String#delete deletes all chars in a sequence
1 change: 0 additions & 1 deletion spec/truffle/tags/core/string/dump_tags.txt

This file was deleted.

2 changes: 0 additions & 2 deletions spec/truffle/tags/core/string/force_encoding_tags.txt

This file was deleted.

2 changes: 0 additions & 2 deletions spec/truffle/tags/core/string/reverse_tags.txt

This file was deleted.

18 changes: 8 additions & 10 deletions tool/jt.rb
Original file line number Diff line number Diff line change
@@ -16,6 +16,9 @@

JRUBY_DIR = File.expand_path('../..', __FILE__)

# wait for sub-processes to handle the interrupt
trap(:INT) {}

module Utilities

def self.graal_version
@@ -70,7 +73,7 @@ def self.igv_running?

def self.ensure_igv_running
unless igv_running?
spawn "#{find_graal_mx} igv"
spawn "#{find_graal_mx} igv", pgroup: true
sleep 5
puts
puts
@@ -111,15 +114,10 @@ module ShellUtils
private

def raw_sh(*args)
begin
result = system(*args)
rescue Interrupt
abort # Ignore Ctrl+C
else
unless result
$stderr.puts "FAILED (#{$?}): #{args * ' '}"
exit $?.exitstatus
end
result = system(*args)
unless result
$stderr.puts "FAILED (#{$?}): #{args * ' '}"
exit $?.exitstatus
end
end

4 changes: 4 additions & 0 deletions tool/truffle-findbugs-exclude.xml
Original file line number Diff line number Diff line change
@@ -90,4 +90,8 @@
<Class name="org.jruby.truffle.nodes.debug.AssertNotCompiledNode" />
<Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" />
</Match>
<Match>
<Class name="org.jruby.truffle.nodes.RubyCallNode" />
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
</Match>
</FindBugsFilter>
Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ public void init() {
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, SymbolNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, ThreadNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, TrueClassNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, PrimitiveNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, TrufflePrimitiveNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, EncodingNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, EncodingConverterNodesFactory.getFactories());
CoreMethodNodeManager.addCoreMethodNodes(rubyObjectClass, MethodNodesFactory.getFactories());
@@ -169,7 +169,7 @@ public void init() {

@Override
public Object execute(final Object self, final org.jruby.ast.RootNode rootNode) {
return execute(TranslatorDriver.ParserContext.TOP_LEVEL, self, null, rootNode);
return execute(TranslatorDriver.ParserContext.TOP_LEVEL, self, null, rootNode);
}

public Object execute(final TranslatorDriver.ParserContext parserContext, final Object self, final MaterializedFrame parentFrame, final org.jruby.ast.RootNode rootNode) {
184 changes: 183 additions & 1 deletion truffle/src/main/java/org/jruby/truffle/nodes/RubyCallNode.java
Original file line number Diff line number Diff line change
@@ -13,28 +13,40 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.truffle.nodes.cast.BooleanCastNode;
import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
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 +66,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 +98,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] = insert(NodeUtil.cloneNode(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 +173,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 +235,118 @@ 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)
cannotOptimize = true;
return null;
}

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
@@ -26,6 +26,10 @@ public static boolean isNull(RubyArray array) {
return array.getStore() == null;
}

public static boolean isNullOrEmpty(RubyArray array) {
return array.getStore() == null || array.getSize() == 0;
}

public static boolean isIntegerFixnum(RubyArray array) {
return array.getStore() instanceof int[];
}
614 changes: 588 additions & 26 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -138,7 +138,7 @@ private static RubyRootNode makeGenericMethod(RubyContext context, MethodDetails
optional = methodDetails.getMethodAnnotation().optional();
}

final Arity arity = new Arity(required, optional, methodDetails.getMethodAnnotation().argumentsAsArray(), false);
final Arity arity = new Arity(required, optional, methodDetails.getMethodAnnotation().argumentsAsArray(), false, false, 0);

final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(sourceSection, null, arity, methodDetails.getIndicativeName(), false, null, true);

Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@
import com.oracle.truffle.api.dsl.CreateCast;
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.source.SourceSection;

import org.jcodings.specific.ASCIIEncoding;
@@ -24,7 +23,6 @@
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyFile;
import org.jruby.truffle.runtime.core.RubyProc;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.subsystems.ThreadManager;
import org.jruby.util.ByteList;
Original file line number Diff line number Diff line change
@@ -399,7 +399,7 @@ public RubyNilClass attrReader(RubyModule module, Object[] args) {
public static void attrReader(Node currentNode, RubyContext context, SourceSection sourceSection, RubyModule module, String name) {
CompilerDirectives.transferToInterpreter();

final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(0, 0, false, false));
final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(0, 0, false, false, false, 0));

final SelfNode self = new SelfNode(context, sourceSection);
final ReadInstanceVariableNode readInstanceVariable = new ReadInstanceVariableNode(context, sourceSection, "@" + name, self, false);
@@ -451,7 +451,7 @@ public RubyNilClass attrWriter(RubyModule module, Object[] args) {
public static void attrWriter(Node currentNode, RubyContext context, SourceSection sourceSection, RubyModule module, String name) {
CompilerDirectives.transferToInterpreter();

final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(1, 0, false, false));
final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(1, 0, false, false, false, 0));

final SelfNode self = new SelfNode(context, sourceSection);
final ReadPreArgumentNode readArgument = new ReadPreArgumentNode(context, sourceSection, 0, MissingArgumentBehaviour.RUNTIME_ERROR);
@@ -684,7 +684,7 @@ public ClassExecNode(ClassExecNode prev) {
yield = prev.yield;
}

public abstract Object executeClassEval(VirtualFrame frame, RubyModule self, Object[] args, RubyProc block);
public abstract Object executeClassExec(VirtualFrame frame, RubyModule self, Object[] args, RubyProc block);

@Specialization
public Object classExec(VirtualFrame frame, RubyModule self, Object[] args, RubyProc block) {
@@ -1002,7 +1002,7 @@ void classEval(VirtualFrame frame, RubyModule module, RubyProc block) {
CompilerDirectives.transferToInterpreterAndInvalidate();
classExecNode = insert(ModuleNodesFactory.ClassExecNodeFactory.create(getContext(), getSourceSection(), new RubyNode[]{null,null,null}));
}
classExecNode.executeClassEval(frame, module, new Object[]{}, block);
classExecNode.executeClassExec(frame, module, new Object[]{}, block);
}

@Specialization
Original file line number Diff line number Diff line change
@@ -19,4 +19,8 @@ public static boolean isSingleByteOptimizable(RubyString string) {
return StringSupport.isSingleByteOptimizable(string, string.getBytes().getEncoding());
}

public static boolean isAsciiCompatible(RubyString string) {
return string.getByteList().getEncoding().isAsciiCompatible();
}

}
222 changes: 181 additions & 41 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/StringNodes.java
Original file line number Diff line number Diff line change
@@ -37,7 +37,9 @@

import com.oracle.truffle.api.utilities.ConditionProfile;
import org.jcodings.Encoding;
import org.jcodings.exception.EncodingException;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.joni.Matcher;
import org.joni.Option;
import org.jruby.Ruby;
@@ -67,6 +69,7 @@
import org.jruby.util.StringSupport;
import org.jruby.util.io.EncodingUtils;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Locale;

@@ -321,22 +324,41 @@ public ConcatNode(ConcatNode prev) {

@Specialization
public RubyString concat(RubyString string, int other) {
string.getByteList().append((byte) other);
return string;
if (other < 0) {
CompilerDirectives.transferToInterpreter();

throw new RaiseException(charRangeException(other));
}

return concatNumeric(string, other);
}

@Specialization
public RubyString concat(RubyString string, long other) {
string.getByteList().append((byte) other);
return string;
if (other < 0) {
CompilerDirectives.transferToInterpreter();

throw new RaiseException(charRangeException(other));
}

return concatNumeric(string, (int) other);
}

@Specialization
public RubyString concat(RubyString string, RubyBignum other) {
if (other.bigIntegerValue().signum() < 0) {
CompilerDirectives.transferToInterpreter();

throw new RaiseException(
getContext().getCoreLibrary().rangeError("bignum out of char range", this));
}

return concatNumeric(string, other.bigIntegerValue().intValue());
}

@TruffleBoundary
@Specialization
public RubyString concat(RubyString string, RubyString other) {
// TODO (nirvdrum 06-Feb-15) This shouldn't be designed for compilation because we don't support all the String semantics yet, but a bench9000 benchmark has it on a hot path, so commenting out for now.
//notDesignedForCompilation();

final int codeRange = other.getCodeRange();
final int[] ptr_cr_ret = { codeRange };

@@ -356,11 +378,50 @@ public RubyString concat(RubyString string, RubyString other) {
return string;
}

@Specialization(guards = {"!isInteger(other)", "!isLong(other)", "!isRubyString(other)"})
@Specialization(guards = {"!isInteger(other)", "!isLong(other)", "!isRubyBignum(other)", "!isRubyString(other)"})
public Object concat(VirtualFrame frame, RubyString string, Object other) {
notDesignedForCompilation();
return ruby(frame, "concat StringValue(other)", "other", other);
}

@TruffleBoundary
private RubyString concatNumeric(RubyString string, int c) {
// Taken from org.jruby.RubyString#concatNumeric

final ByteList value = string.getByteList();
Encoding enc = value.getEncoding();
int cl;

try {
cl = StringSupport.codeLength(getContext().getRuntime(), enc, c);
string.modify(value.getRealSize() + cl);
string.clearCodeRange();

if (enc == USASCIIEncoding.INSTANCE) {
if (c > 0xff) {
throw new RaiseException(charRangeException(c));

}
if (c > 0x79) {
value.setEncoding(ASCIIEncoding.INSTANCE);
enc = value.getEncoding();
}
}

enc.codeToMbc(c, value.getUnsafeBytes(), value.getBegin() + value.getRealSize());
} catch (EncodingException e) {
throw new RaiseException(charRangeException(c));
}

value.setRealSize(value.getRealSize() + cl);

return string;
}

private RubyException charRangeException(Number value) {
return getContext().getCoreLibrary().rangeError(
String.format("%d out of char range", value), this);
}
}

@CoreMethod(names = "%", required = 1, argumentsAsArray = true)
@@ -1192,12 +1253,15 @@ public boolean endWith(RubyString string, RubyString b) {
@CoreMethod(names = "force_encoding", required = 1)
public abstract static class ForceEncodingNode extends CoreMethodNode {

@Child private ToStrNode toStrNode;

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

public ForceEncodingNode(ForceEncodingNode prev) {
super(prev);
toStrNode = prev.toStrNode;
}

@TruffleBoundary
@@ -1213,6 +1277,16 @@ public RubyString forceEncoding(RubyString string, RubyEncoding encoding) {
return string;
}

@Specialization(guards = { "!isRubyString(encoding)", "!isRubyEncoding(encoding)" })
public RubyString forceEncoding(VirtualFrame frame, RubyString string, Object encoding) {
if (toStrNode == null) {
CompilerDirectives.transferToInterpreter();
toStrNode = insert(ToStrNodeFactory.create(getContext(), getSourceSection(), null));
}

return forceEncoding(string, toStrNode.executeRubyString(frame, encoding));
}

}

@CoreMethod(names = "getbyte", required = 1)
@@ -1341,7 +1415,9 @@ public Object initializeCopy(RubyString self, RubyString from) {
return self;
}

self.getBytes().replace(from.getBytes().bytes());
self.getByteList().replace(from.getByteList().bytes());
self.getByteList().setEncoding(from.getByteList().getEncoding());
self.setCodeRange(from.getCodeRange());

return self;
}
@@ -1658,6 +1734,7 @@ public RubyString rstrip(RubyString string) {
}

@CoreMethod(names = "dump", taintFromSelf = true)
@ImportStatic(StringGuards.class)
public abstract static class DumpNode extends CoreMethodNode {

public DumpNode(RubyContext context, SourceSection sourceSection) {
@@ -1668,13 +1745,46 @@ public DumpNode(DumpNode prev) {
super(prev);
}

@Specialization
public RubyString rstrip(RubyString string) {
notDesignedForCompilation();
@Specialization(guards = "isAsciiCompatible(string)")
public RubyString dumpAsciiCompatible(RubyString string) {
// Taken from org.jruby.RubyString#dump

ByteList outputBytes = dumpCommon(string);

final RubyString result = getContext().makeString(string.getLogicalClass(), outputBytes);
result.getByteList().setEncoding(string.getByteList().getEncoding());
result.setCodeRange(StringSupport.CR_7BIT);

return result;
}

return string.dump();
@Specialization(guards = "!isAsciiCompatible(string)")
public RubyString dump(RubyString string) {
// Taken from org.jruby.RubyString#dump

ByteList outputBytes = dumpCommon(string);

try {
outputBytes.append(".force_encoding(\"".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new UnsupportedOperationException(e);
}

outputBytes.append(string.getByteList().getEncoding().getName());
outputBytes.append((byte) '"');
outputBytes.append((byte) ')');

final RubyString result = getContext().makeString(string.getLogicalClass(), outputBytes);
result.getByteList().setEncoding(ASCIIEncoding.INSTANCE);
result.setCodeRange(StringSupport.CR_7BIT);

return result;
}

@TruffleBoundary
private ByteList dumpCommon(RubyString string) {
return StringSupport.dumpCommon(getContext().getRuntime(), string.getByteList());
}
}

@CoreMethod(names = "scan", required = 1, needsBlock = true, taintFromParameters = 0)
@@ -1999,43 +2109,81 @@ public RubySymbol toSym(RubyString string) {
}
}

@CoreMethod(names = "reverse", taintFromSelf = true)
public abstract static class ReverseNode extends CoreMethodNode {
@CoreMethod(names = "reverse!", raiseIfFrozenSelf = true)
@ImportStatic(StringGuards.class)
public abstract static class ReverseBangNode extends CoreMethodNode {

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

public ReverseNode(ReverseNode prev) {
public ReverseBangNode(ReverseBangNode prev) {
super(prev);
}

@Specialization
public RubyString reverse(RubyString string) {
notDesignedForCompilation();

return RubyString.fromByteList(string.getLogicalClass(), StringNodesHelper.reverse(string));
@Specialization(guards = "reverseIsEqualToSelf(string)")
public RubyString reverseNoOp(RubyString string) {
return string;
}
}

@CoreMethod(names = "reverse!", raiseIfFrozenSelf = true)
public abstract static class ReverseBangNode extends CoreMethodNode {
@Specialization(guards = { "!reverseIsEqualToSelf(string)", "isSingleByteOptimizable(string)" })
public RubyString reverseSingleByteOptimizable(RubyString string) {
// Taken from org.jruby.RubyString#reverse!

public ReverseBangNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
string.modify();

public ReverseBangNode(ReverseBangNode prev) {
super(prev);
final byte[] bytes = string.getByteList().getUnsafeBytes();
final int p = string.getByteList().getBegin();
final int len = string.getByteList().getRealSize();

for (int i = 0; i < len >> 1; i++) {
byte b = bytes[p + i];
bytes[p + i] = bytes[p + len - i - 1];
bytes[p + len - i - 1] = b;
}

return string;
}

@Specialization
@Specialization(guards = { "!reverseIsEqualToSelf(string)", "!isSingleByteOptimizable(string)" })
public RubyString reverse(RubyString string) {
notDesignedForCompilation();
// Taken from org.jruby.RubyString#reverse!

string.modify();

final byte[] bytes = string.getByteList().getUnsafeBytes();
int p = string.getByteList().getBegin();
final int len = string.getByteList().getRealSize();

final Encoding enc = string.getByteList().getEncoding();
final int end = p + len;
int op = len;
final byte[] obytes = new byte[len];
boolean single = true;

while (p < end) {
int cl = StringSupport.length(enc, bytes, p, end);
if (cl > 1 || (bytes[p] & 0x80) != 0) {
single = false;
op -= cl;
System.arraycopy(bytes, p, obytes, op, cl);
p += cl;
} else {
obytes[--op] = bytes[p++];
}
}

string.getByteList().setUnsafeBytes(obytes);
if (string.getCodeRange() == StringSupport.CR_UNKNOWN) {
string.setCodeRange(single ? StringSupport.CR_7BIT : StringSupport.CR_VALID);
}

string.set(StringNodesHelper.reverse(string));
return string;
}

public static boolean reverseIsEqualToSelf(RubyString string) {
return string.getByteList().getRealSize() <= 1;
}
}

@CoreMethod(names = "unpack", required = 1)
@@ -2266,14 +2414,6 @@ public static ByteList chompWithString(RubyString string, RubyString stringToCho
return byteList;
}

@TruffleBoundary
public static ByteList reverse(RubyString string) {
ByteList byteListString = ByteList.create(new StringBuilder(string.toString()).reverse().toString());
byteListString.setEncoding(string.getBytes().getEncoding());

return byteListString;
}

@TruffleBoundary
public static ByteList swapcase(RubyString string) {
char[] charArray = string.toString().toCharArray();
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@
import java.util.*;

@CoreClass(name = "Truffle::Primitive")
public abstract class PrimitiveNodes {
public abstract class TrufflePrimitiveNodes {

@CoreMethod(names = "binding_of_caller", onSingleton = true)
public abstract static class BindingOfCallerNode extends CoreMethodNode {
Original file line number Diff line number Diff line change
@@ -94,7 +94,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;
@@ -174,4 +174,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
@@ -14,6 +14,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 {

@@ -90,5 +91,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
@@ -41,6 +41,18 @@ protected HashLiteralNode(RubyContext context, SourceSection sourceSection, Ruby
freezeNode = DispatchHeadNodeFactory.createMethodCall(context);
}

public int size() {
return keyValues.length / 2;
}

public RubyNode getKey(int index) {
return keyValues[2 * index];
}

public RubyNode getValue(int index) {
return keyValues[2 * index + 1];
}

public static HashLiteralNode create(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) {
if (keyValues.length == 0) {
return new EmptyHashLiteralNode(context, sourceSection);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jruby.truffle.nodes.methods;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;

public class MarkerNode extends RubyNode {

private static Marker instance = new Marker();

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

@Override
public Object execute(VirtualFrame frame) {
return instance;
}

public static class Marker {}

}
Original file line number Diff line number Diff line change
@@ -44,8 +44,16 @@ public CheckArityNode(RubyContext context, SourceSection sourceSection, Arity ar
@Override
public void executeVoid(VirtualFrame frame) {
final Object[] frameArguments = frame.getArguments();
final int given = RubyArguments.getUserArgumentsCount(frameArguments);
final int given;
final RubyHash keywordArguments;

//TODO (MS): Check merge
if (RubyArguments.isKwOptimized(frame.getArguments())) {
given = RubyArguments.getUserArgumentsCount(frame.getArguments())
- arity.getCountKeywords() - 2;
} else {
given = RubyArguments.getUserArgumentsCount(frame.getArguments());
}

if (arity.hasKeywords()) {
keywordArguments = RubyArguments.getUserKeywordsHash(frameArguments, arity.getRequired());
@@ -61,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
@@ -0,0 +1,25 @@
package org.jruby.truffle.nodes.methods.arguments;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;

public class OptionalKeywordArgMissingNode extends RubyNode {

private static OptionalKeywordArgMissing instance = new OptionalKeywordArgMissing();

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

public static class OptionalKeywordArgMissing {}

@Override
public Object execute(VirtualFrame frame) {
return instance;
}

}
Original file line number Diff line number Diff line change
@@ -9,8 +9,12 @@
*/
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;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
@@ -22,39 +26,60 @@ public class ReadKeywordArgumentNode extends RubyNode {

private final int minimum;
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;

public ReadKeywordArgumentNode(RubyContext context, SourceSection sourceSection, int minimum, String name, RubyNode defaultValue) {
public ReadKeywordArgumentNode(RubyContext context, SourceSection sourceSection, int minimum, String name, RubyNode defaultValue, int kwIndex) {
super(context, sourceSection);
this.minimum = minimum;
this.name = name;
this.defaultValue = defaultValue;
this.kwIndex = kwIndex;
}

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

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

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

Object value = null;
Object value = lookupKeywordInHash(hash);

for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) {
if (keyValue.getKey().toString().equals(name)) {
value = keyValue.getValue();
break;
if (defaultProfile.profile(value == null)) {
return defaultValue.execute(frame);
}

return value;
}
}

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

return value;
return null;
}

}
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.literal.HashLiteralNode;
import org.jruby.truffle.nodes.methods.MarkerNode;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyHash;
@@ -25,15 +27,34 @@ public class ReadKeywordRestArgumentNode extends RubyNode {

private final int minimum;
private final String[] excludedKeywords;
private final int kwIndex;

public ReadKeywordRestArgumentNode(RubyContext context, SourceSection sourceSection, int minimum, String[] excludedKeywords) {
public ReadKeywordRestArgumentNode(RubyContext context, SourceSection sourceSection, int minimum, String[] excludedKeywords, int kwIndex) {
super(context, sourceSection);
this.minimum = minimum;
this.excludedKeywords = excludedKeywords;
this.kwIndex = kwIndex;
}

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

if (restHash instanceof MarkerNode.Marker) {
// no rest keyword args hash passed
return HashLiteralNode.create(getContext(), null,
new RubyNode[0]).execute(frame);
} else {
return restHash;
}
} else {
return lookupRestKeywordArgumentHash(frame);
}
}

private Object lookupRestKeywordArgumentHash(VirtualFrame frame) {
notDesignedForCompilation();

final RubyHash hash = RubyArguments.getUserKeywordsHash(frame.getArguments(), minimum);
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ public ReadOptionalArgumentNode(RubyContext context, SourceSection sourceSection

@Override
public Object execute(VirtualFrame frame) {
if (RubyArguments.getUserArgumentsCount(frame.getArguments()) < minimum) {
if (RubyArguments.getNamedUserArgumentsCount(frame.getArguments()) < minimum) {
defaultValueProfile.enter();
return defaultValue.execute(frame);
} else {
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.jruby.truffle.nodes.methods.arguments;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;

public class UnknownArgumentErrorNode extends RubyNode {

private final String label;

public UnknownArgumentErrorNode(RubyContext context,
SourceSection sourceSection, String label) {
super(context, sourceSection);
this.label = label;
}

@Override
public Object execute(VirtualFrame frame) {
throw new RaiseException(getContext().getCoreLibrary().argumentError(
"unknown keyword: " + label, this));
}

}
Original file line number Diff line number Diff line change
@@ -62,8 +62,7 @@ public Object execute(VirtualFrame frame) {
RubyClass superClassObject = getRubySuperClass(frame, context);

if (constant == null) {
definingClass = new RubyClass(context, lexicalParent, superClassObject, name);
definingClass.setAllocator(superClassObject.getAllocator());
definingClass = new RubyClass(context, lexicalParent, superClassObject, name, superClassObject.getAllocator());
callInherited(frame, superClassObject, definingClass);
} else {
if (constant.getValue() instanceof RubyClass) {
Original file line number Diff line number Diff line change
@@ -12,8 +12,6 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.dispatch.DispatchAction;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.DoesRespondDispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.MissingBehavior;
import org.jruby.truffle.runtime.RubyContext;
Original file line number Diff line number Diff line change
@@ -318,29 +318,36 @@ public StringChrAtPrimitiveNode(StringChrAtPrimitiveNode prev) {
@CompilerDirectives.TruffleBoundary
@Specialization
public Object stringChrAt(RubyString string, int byteIndex) {
if (stringByteSubstringNode == null) {
CompilerDirectives.transferToInterpreter();
// Taken from Rubinius's Character::create_from.

stringByteSubstringNode = insert(
StringPrimitiveNodesFactory.StringByteSubstringPrimitiveNodeFactory.create(
getContext(),
getSourceSection(),
new RubyNode[]{})
);
final ByteList bytes = string.getByteList();

if (byteIndex < 0 || byteIndex >= bytes.getRealSize()) {
return nil();
}

final ByteList bytes = string.getByteList();
final int p = bytes.getBegin();
final int end = p + bytes.getRealSize();
final int c = StringSupport.preciseLength(bytes.getEncoding(), bytes.getUnsafeBytes(), p, end);

if (! StringSupport.MBCLEN_CHARFOUND_P(c)) {
return getContext().getCoreLibrary().getNilObject();
return nil();
}

final int n = StringSupport.MBCLEN_CHARFOUND_LEN(c);
if (n + byteIndex > end) {
return getContext().getCoreLibrary().getNilObject();
return nil();
}

if (stringByteSubstringNode == null) {
CompilerDirectives.transferToInterpreter();

stringByteSubstringNode = insert(
StringPrimitiveNodesFactory.StringByteSubstringPrimitiveNodeFactory.create(
getContext(),
getSourceSection(),
new RubyNode[]{})
);
}

return stringByteSubstringNode.stringByteSubstring(string, byteIndex, n);
@@ -691,6 +698,8 @@ public StringPreviousByteIndexPrimitiveNode(StringPreviousByteIndexPrimitiveNode

@Specialization
public Object stringPreviousByteIndex(RubyString string, int index) {
// Port of Rubinius's String::previous_byte_index.

if (index < 0) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().argumentError("negative index given", this));
@@ -700,17 +709,13 @@ public Object stringPreviousByteIndex(RubyString string, int index) {
final int p = bytes.getBegin();
final int end = p + bytes.getRealSize();

if (p > end) {
return 0;
}

final int s = bytes.getEncoding().prevCharHead(bytes.getUnsafeBytes(), p, p + index, end);
final int b = bytes.getEncoding().prevCharHead(bytes.getUnsafeBytes(), p, p + index, end);

if (s == -1) {
return 0;
if (b == -1) {
return nil();
}

return s - p;
return b - p;
}

}
28 changes: 24 additions & 4 deletions truffle/src/main/java/org/jruby/truffle/runtime/RubyArguments.java
Original file line number Diff line number Diff line change
@@ -9,15 +9,16 @@
*/
package org.jruby.truffle.runtime;

import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;

import org.jruby.truffle.nodes.methods.MarkerNode;
import org.jruby.truffle.runtime.core.RubyHash;
import org.jruby.truffle.runtime.core.RubyProc;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.util.ArrayUtils;

import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;

/**
* Pack and unpack Ruby method arguments to and from an array of objects.
*/
@@ -40,6 +41,15 @@ public static Object[] pack(InternalMethod method, MaterializedFrame declaration

return packed;
}

public static Object getOptimizedKeywordArgument(Object[] arguments,
int index) {
return arguments[arguments.length - 1 + index];
}

public static boolean isKwOptimized(Object[] arguments) {
return arguments[arguments.length - 1] instanceof MarkerNode.Marker;
}

public static InternalMethod getMethod(Object[] arguments) {
return (InternalMethod) arguments[METHOD_INDEX];
@@ -67,6 +77,16 @@ public static int getUserArgumentsCount(Object[] internalArguments) {
return internalArguments.length - RUNTIME_ARGUMENT_COUNT;
}

public static int getNamedUserArgumentsCount(Object[] internalArguments) {
if (isKwOptimized(internalArguments)) {
return getUserArgumentsCount(internalArguments)
- getMethod(internalArguments).getSharedMethodInfo().getArity()
.getKeywordArguments().size() - 1;
} else {
return getUserArgumentsCount(internalArguments);
}
}

public static Object getUserArgument(Object[] internalArguments, int index) {
return internalArguments[RUNTIME_ARGUMENT_COUNT + index];
}
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@

import org.jcodings.Encoding;
import org.jcodings.EncodingDB;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.runtime.Constants;
import org.jruby.runtime.encoding.EncodingService;
import org.jruby.runtime.load.LoadServiceResource;
@@ -34,7 +33,6 @@
import org.jruby.truffle.runtime.hash.KeyValue;
import org.jruby.truffle.runtime.signal.SignalOperations;
import org.jruby.truffle.translator.NodeWrapper;
import org.jruby.truffle.translator.TranslatorDriver;
import org.jruby.util.cli.Options;
import org.jruby.util.cli.OutputStrings;

@@ -157,17 +155,10 @@ public CoreLibrary(RubyContext context) {

// Create the cyclic classes and modules

classClass = new RubyClass(context, null, null, null, "Class", false, null);
classClass.setAllocator(new RubyClass.ClassAllocator());

basicObjectClass = RubyClass.createBootClass(context, classClass, "BasicObject");
basicObjectClass.setAllocator(new RubyBasicObject.BasicObjectAllocator());

objectClass = RubyClass.createBootClass(context, classClass, "Object");
objectClass.setAllocator(basicObjectClass.getAllocator());

moduleClass = new RubyClass(context, classClass, null, null, "Module", false, null);
moduleClass.setAllocator(new RubyModule.ModuleAllocator());
classClass = RubyClass.createBootClass(context, null, "Class", new RubyClass.ClassAllocator());
basicObjectClass = RubyClass.createBootClass(context, classClass, "BasicObject", new RubyBasicObject.BasicObjectAllocator());
objectClass = RubyClass.createBootClass(context, classClass, "Object", basicObjectClass.getAllocator());
moduleClass = RubyClass.createBootClass(context, classClass, "Module", new RubyModule.ModuleAllocator());

// Close the cycles
classClass.unsafeSetLogicalClass(classClass);
@@ -188,8 +179,7 @@ public CoreLibrary(RubyContext context) {
// Create Exception classes

// Exception
exceptionClass = defineClass("Exception");
exceptionClass.setAllocator(new RubyException.ExceptionAllocator());
exceptionClass = defineClass("Exception", new RubyException.ExceptionAllocator());

// EncodingError
encodingErrorClass = defineClass(exceptionClass, "EncodingError");
@@ -232,14 +222,14 @@ public CoreLibrary(RubyContext context) {
// StandardError > SystemCallError
systemCallErrorClass = defineClass(standardErrorClass, "SystemCallError");
errnoModule = defineModule("Errno");
new RubyClass(context, errnoModule, systemCallErrorClass, "EACCES");
edomClass = new RubyClass(context, errnoModule, systemCallErrorClass, "EDOM");
new RubyClass(context, errnoModule, systemCallErrorClass, "EEXIST");
enoentClass = new RubyClass(context, errnoModule, systemCallErrorClass, "ENOENT");
enotemptyClass = new RubyClass(context, errnoModule, systemCallErrorClass, "ENOTEMPTY");
new RubyClass(context, errnoModule, systemCallErrorClass, "EPERM");
new RubyClass(context, errnoModule, systemCallErrorClass, "EXDEV");
einvalClass = new RubyClass(context, errnoModule, systemCallErrorClass, "EINVAL");
defineClass(errnoModule, systemCallErrorClass, "EACCES");
edomClass = defineClass(errnoModule, systemCallErrorClass, "EDOM");
defineClass(errnoModule, systemCallErrorClass, "EEXIST");
enoentClass = defineClass(errnoModule, systemCallErrorClass, "ENOENT");
enotemptyClass = defineClass(errnoModule, systemCallErrorClass, "ENOTEMPTY");
defineClass(errnoModule, systemCallErrorClass, "EPERM");
defineClass(errnoModule, systemCallErrorClass, "EXDEV");
einvalClass = defineClass(errnoModule, systemCallErrorClass, "EINVAL");

// ScriptError
RubyClass scriptErrorClass = defineClass(exceptionClass, "ScriptError");
@@ -311,18 +301,17 @@ public CoreLibrary(RubyContext context) {

// The rest

encodingCompatibilityErrorClass = new RubyClass(context, encodingClass, standardErrorClass, "CompatibilityError");
encodingCompatibilityErrorClass = defineClass(encodingClass, standardErrorClass, "CompatibilityError");

encodingConverterClass = new RubyClass(context, encodingClass, objectClass, "Converter");
encodingConverterClass.setAllocator(new RubyEncodingConverter.EncodingConverterAllocator());
encodingConverterClass = defineClass(encodingClass, objectClass, "Converter", new RubyEncodingConverter.EncodingConverterAllocator());

truffleModule = defineModule("Truffle");
truffleDebugModule = defineModule(truffleModule, "Debug");
defineModule(truffleModule, "Primitive");

rubiniusModule = defineModule("Rubinius");
byteArrayClass = new RubyClass(context, rubiniusModule, objectClass, "ByteArray");
stringDataClass = new RubyClass(context, rubiniusModule, objectClass, "StringData");
byteArrayClass = defineClass(rubiniusModule, objectClass, "ByteArray");
stringDataClass = defineClass(rubiniusModule, objectClass, "StringData");

// Include the core modules

@@ -455,13 +444,19 @@ private RubyClass defineClass(String name, Allocator allocator) {
}

private RubyClass defineClass(RubyClass superclass, String name) {
return new RubyClass(context, objectClass, superclass, name);
return new RubyClass(context, objectClass, superclass, name, superclass.getAllocator());
}

private RubyClass defineClass(RubyClass superclass, String name, Allocator allocator) {
RubyClass rubyClass = new RubyClass(context, objectClass, superclass, name);
rubyClass.setAllocator(allocator);
return rubyClass;
return new RubyClass(context, objectClass, superclass, name, allocator);
}

private RubyClass defineClass(RubyModule lexicalParent, RubyClass superclass, String name) {
return new RubyClass(context, lexicalParent, superclass, name, superclass.getAllocator());
}

private RubyClass defineClass(RubyModule lexicalParent, RubyClass superclass, String name, Allocator allocator) {
return new RubyClass(context, lexicalParent, superclass, name, allocator);
}

private RubyModule defineModule(String name) {
23 changes: 10 additions & 13 deletions truffle/src/main/java/org/jruby/truffle/runtime/core/RubyClass.java
Original file line number Diff line number Diff line change
@@ -39,33 +39,34 @@ public class RubyClass extends RubyModule {
* This constructor supports initialization and solves boot-order problems and should not
* normally be used from outside this class.
*/
public static RubyClass createBootClass(RubyContext context, RubyClass classClass, String name) {
return new RubyClass(context, classClass, null, null, name, false, null);
public static RubyClass createBootClass(RubyContext context, RubyClass classClass, String name, Allocator allocator) {
return new RubyClass(context, classClass, null, null, name, false, null, allocator);
}

public RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name) {
this(context, superclass.getLogicalClass(), lexicalParent, superclass, name, false, null);
public RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name, Allocator allocator) {
this(context, superclass.getLogicalClass(), lexicalParent, superclass, name, false, null, allocator);
// Always create a class singleton class for normal classes for consistency.
ensureSingletonConsistency();
}

protected static RubyClass createSingletonClassOfObject(RubyContext context, RubyClass superclass, RubyModule attached, String name) {
// We also need to create the singleton class of a singleton class for proper lookup and consistency.
// See rb_singleton_class() documentation in MRI.
return new RubyClass(context, superclass.getLogicalClass(), null, superclass, name, true, attached).ensureSingletonConsistency();
// Allocator is null here, we cannot create instances of singleton classes.
return new RubyClass(context, superclass.getLogicalClass(), null, superclass, name, true, attached, null).ensureSingletonConsistency();
}

protected RubyClass(RubyContext context, RubyClass classClass, RubyModule lexicalParent, RubyClass superclass, String name, boolean isSingleton, RubyModule attached) {
private RubyClass(RubyContext context, RubyClass classClass, RubyModule lexicalParent, RubyClass superclass, String name, boolean isSingleton, RubyModule attached, Allocator allocator) {
super(context, classClass, lexicalParent, name, null);

assert isSingleton || attached == null;

this.allocator = allocator;
this.isSingleton = isSingleton;
this.attached = attached;

if (superclass != null) {
unsafeSetSuperclass(superclass);
allocator = superclass.allocator;
}
}

@@ -75,10 +76,6 @@ public void initialize(RubyClass superclass) {
allocator = superclass.allocator;
}

public void setAllocator(Allocator allocator) {
this.allocator = allocator;
}

/**
* This method supports initialization and solves boot-order problems and should not normally be
* used.
@@ -129,7 +126,7 @@ private RubyClass createOneSingletonClass() {
}

metaClass = new RubyClass(getContext(),
getLogicalClass(), null, singletonSuperclass, String.format("#<Class:%s>", getName()), true, this);
getLogicalClass(), null, singletonSuperclass, String.format("#<Class:%s>", getName()), true, this, null);

return metaClass;
}
@@ -166,7 +163,7 @@ public static class ClassAllocator implements Allocator {

@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, Node currentNode) {
return new RubyClass(context, context.getCoreLibrary().getClassClass(), null, null, null, false, null);
return new RubyClass(context, context.getCoreLibrary().getClassClass(), null, null, null, false, null, null);
}

}
Original file line number Diff line number Diff line change
@@ -124,14 +124,6 @@ public int count(RubyString[] otherStrings) {
return StringSupport.countCommon19(getBytes(), getContext().getRuntime(), table, tables, enc);
}

public RubyString dump() {
ByteList outputBytes = StringSupport.dumpCommon(getContext().getRuntime(), bytes);

final RubyString result = getContext().makeString(getLogicalClass(), outputBytes);

return result;
}

@Override
@TruffleBoundary
public String toString() {
65 changes: 61 additions & 4 deletions truffle/src/main/java/org/jruby/truffle/runtime/methods/Arity.java
Original file line number Diff line number Diff line change
@@ -9,21 +9,65 @@
*/
package org.jruby.truffle.runtime.methods;

import org.jruby.ast.*;

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

public class Arity {

public static final Arity NO_ARGUMENTS = new Arity(0, 0, false, false);
public static final Arity ONE_REQUIRED = new Arity(1, 0, false, false);
public static final Arity NO_ARGUMENTS = new Arity(0, 0, false, false, false, 0);
public static final Arity ONE_REQUIRED = new Arity(1, 0, false, false, false, 0);

private final int required;
private final int optional;
private final boolean allowsMore;
private final int definedKeywords;
private final boolean hasKeywords;
private final boolean hasKeyRest;

private final List<String> keywordArguments;

public Arity(int required, int optional, boolean allowsMore, boolean hasKeywords) {
public Arity(int required, int optional, boolean allowsMore, boolean hasKeywords, boolean hasKeyRest, int definedKeywords) {
this.required = required;
this.optional = optional;
this.allowsMore = allowsMore;
this.definedKeywords = definedKeywords;
this.hasKeywords = hasKeywords;
this.hasKeyRest = hasKeyRest;
keywordArguments = null;
}

public Arity(int required, int optional, boolean allowsMore, boolean hasKeywords, boolean hasKeyRest, int definedKeywords, ArgsNode argsNode) {
this.required = required;
this.optional = optional;
this.allowsMore = allowsMore;
this.definedKeywords = definedKeywords;
this.hasKeywords = hasKeywords;
this.hasKeyRest = hasKeyRest;

if (argsNode.hasKwargs()) {
keywordArguments = new ArrayList<>();
if (argsNode.getKeywords() != null) {
for (Node node : argsNode.getKeywords().childNodes()) {
final KeywordArgNode kwarg = (KeywordArgNode) node;
final AssignableNode assignableNode = kwarg.getAssignable();

if (assignableNode instanceof LocalAsgnNode) {
keywordArguments.add(((LocalAsgnNode) assignableNode)
.getName());
} else if (assignableNode instanceof DAsgnNode) {
keywordArguments.add(((DAsgnNode) assignableNode)
.getName());
} else {
throw new UnsupportedOperationException(
"unsupported keyword arg " + node);
}
}
}
} else {
keywordArguments = null;
}
}

public int getRequired() {
@@ -42,6 +86,14 @@ public boolean hasKeywords() {
return hasKeywords;
}

public int getCountKeywords() {
return definedKeywords;
}

public boolean hasKeyRest() {
return hasKeyRest;
}

public int getArityNumber() {
int count = required;

@@ -56,14 +108,19 @@ public int getArityNumber() {
return count;
}

public List<String> getKeywordArguments() {
return keywordArguments;
}

@Override
public String toString() {
return "Arity{" +
"required=" + required +
", optional=" + optional +
", allowsMore=" + allowsMore +
", definedKeywords=" + definedKeywords +
", hasKeywords=" + hasKeywords +
", hasKeyRest=" + hasKeyRest +
'}';
}

}
Original file line number Diff line number Diff line change
@@ -9,6 +9,16 @@
*/
package org.jruby.truffle.runtime.methods;

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

import org.jruby.ast.ArgsNode;
import org.jruby.ast.AssignableNode;
import org.jruby.ast.DAsgnNode;
import org.jruby.ast.KeywordArgNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.Node;

import com.oracle.truffle.api.source.SourceSection;
import org.jruby.ast.Node;
import org.jruby.truffle.runtime.LexicalScope;
Original file line number Diff line number Diff line change
@@ -66,6 +66,8 @@ private enum State {

private int required;
private int index;
private int kwIndex;
private int countKwArgs;
private int indexFromEnd = 1;
private State state;
private boolean hasKeywordArguments;
@@ -127,8 +129,15 @@ public RubyNode visitArgsNode(org.jruby.ast.ArgsNode node) {
}

if (hasKeywordArguments) {
kwIndex = 0;
countKwArgs = 0;
for (org.jruby.ast.Node arg : node.getKeywords().childNodes()) {
countKwArgs++;
}

for (org.jruby.ast.Node arg : node.getKeywords().childNodes()) {
sequence.add(arg.accept(this));
kwIndex++;
}
}

@@ -147,7 +156,7 @@ public RubyNode visitArgsNode(org.jruby.ast.ArgsNode node) {
public RubyNode visitKeywordRestArgNode(org.jruby.ast.KeywordRestArgNode node) {
final SourceSection sourceSection = translate(node.getPosition());

final RubyNode readNode = new ReadKeywordRestArgumentNode(context, sourceSection, required, excludedKeywords.toArray(new String[excludedKeywords.size()]));
final RubyNode readNode = new ReadKeywordRestArgumentNode(context, sourceSection, required, excludedKeywords.toArray(new String[excludedKeywords.size()]), -countKwArgs - 1);
final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findOrAddFrameSlot(node.getName());

return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
@@ -192,7 +201,7 @@ public RubyNode visitKeywordArgNode(org.jruby.ast.KeywordArgNode node) {

excludedKeywords.add(name);

final RubyNode readNode = new ReadKeywordArgumentNode(context, sourceSection, required, name, defaultValue);
final RubyNode readNode = new ReadKeywordArgumentNode(context, sourceSection, required, name, defaultValue, kwIndex - countKwArgs);
final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findOrAddFrameSlot(name);

return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
Original file line number Diff line number Diff line change
@@ -20,7 +20,6 @@
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.nodes.cast.ArrayCastNodeFactory;
import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
import org.jruby.truffle.nodes.control.IfNode;
import org.jruby.truffle.nodes.control.SequenceNode;
import org.jruby.truffle.nodes.literal.ObjectLiteralNode;
@@ -69,7 +68,7 @@ public RubyNode compileFunctionNode(SourceSection sourceSection, String methodNa
*/

if (isBlock && argsNode.childNodes().size() == 2 && argsNode.getRestArgNode() instanceof org.jruby.ast.UnnamedRestArgNode) {
arityForCheck = new Arity(arity.getRequired(), 0, false, false);
arityForCheck = new Arity(arity.getRequired(), 0, false, false, false, 0);
} else {
arityForCheck = arity;
}
@@ -242,7 +241,8 @@ public RubyNode compileFunctionNode(SourceSection sourceSection, String methodNa
public static Arity getArity(org.jruby.ast.ArgsNode argsNode) {
final int minimum = argsNode.getRequiredArgsCount();
final int maximum = argsNode.getMaxArgumentsCount();
return new Arity(minimum, argsNode.getOptionalArgsCount(), maximum == -1, argsNode.hasKwargs());
// TODO CS 19-Mar-15 collect up the keyword argument names here
return new Arity(minimum, argsNode.getOptionalArgsCount(), maximum == -1, argsNode.hasKwargs(), argsNode.hasKeyRest(), argsNode.countKeywords(), argsNode);
}

@Override
8 changes: 8 additions & 0 deletions truffle/src/main/ruby/core/rubinius/common/array.rb
Original file line number Diff line number Diff line change
@@ -1080,6 +1080,14 @@ def transpose
out
end

def sort_by!(&block)
Rubinius.check_frozen

return to_enum :sort_by! unless block_given?

replace sort_by(&block)
end

# Insertion sort in-place between the given indexes.
def isort!(left, right)
i = left + 1
4 changes: 4 additions & 0 deletions truffle/src/main/ruby/core/rubinius/common/string.rb
Original file line number Diff line number Diff line change
@@ -52,6 +52,10 @@ def hex
to_inum(16, false)
end

def reverse
dup.reverse!
end

def split(pattern=nil, limit=undefined)
Rubinius::Splitter.split(self, pattern, limit)
end

0 comments on commit 3b85723

Please sign in to comment.