Skip to content

Commit

Permalink
[Truffle] Initial work on supporting 64-bit long ropes.
Browse files Browse the repository at this point in the history
nirvdrum committed Apr 8, 2016
1 parent caff747 commit 4710381
Showing 28 changed files with 683 additions and 242 deletions.
20 changes: 17 additions & 3 deletions truffle/src/main/java/org/jruby/truffle/core/array/ArrayNodes.java
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.CoreMethodNode;
@@ -56,6 +57,7 @@
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeNodesFactory;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringCachingGuards;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.NotProvided;
@@ -2438,7 +2440,7 @@ public DynamicObject packCached(
DynamicObject array,
DynamicObject format,
@Cached("privatizeRope(format)") Rope cachedFormat,
@Cached("ropeLength(cachedFormat)") int cachedFormatLength,
@Cached("ropeLength(cachedFormat)") long cachedFormatLength,
@Cached("create(compileFormat(format))") DirectCallNode callPackNode) {
final BytesResult result;

@@ -2450,7 +2452,12 @@ public DynamicObject packCached(
throw FormatExceptionTranslator.translate(this, e);
}

return finishPack(cachedFormatLength, result);
if (!CoreLibrary.fitsIntoInteger(cachedFormatLength)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with format strings larger than int range");
}

return finishPack((int) cachedFormatLength, result);
}

@Specialization(contains = "packCached", guards = "isRubyString(format)")
@@ -2469,7 +2476,14 @@ public DynamicObject packUncached(
throw FormatExceptionTranslator.translate(this, e);
}

return finishPack(Layouts.STRING.getRope(format).byteLength(), result);
final Rope rope = StringOperations.rope(format);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with format strings longer than int range");
}

return finishPack((int) rope.byteLength(), result);
}

private DynamicObject finishPack(int formatLength, BytesResult result) {
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
import org.jruby.runtime.Visibility;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.CoreMethodNode;
@@ -77,6 +78,7 @@
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeNodesFactory;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.rubinius.ObjectPrimitiveNodes;
import org.jruby.truffle.core.rubinius.ObjectPrimitiveNodesFactory;
import org.jruby.truffle.core.string.StringCachingGuards;
@@ -1959,7 +1961,7 @@ public DynamicObject formatCached(
DynamicObject format,
Object[] arguments,
@Cached("privatizeRope(format)") Rope cachedFormat,
@Cached("ropeLength(cachedFormat)") int cachedFormatLength,
@Cached("ropeLength(cachedFormat)") long cachedFormatLength,
@Cached("create(compileFormat(format))") DirectCallNode callPackNode) {
final BytesResult result;

@@ -1971,7 +1973,12 @@ public DynamicObject formatCached(
throw FormatExceptionTranslator.translate(this, e);
}

return finishFormat(cachedFormatLength, result);
if (!CoreLibrary.fitsIntoInteger(cachedFormatLength)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with format strings larger than int range");
}

return finishFormat((int) cachedFormatLength, result);
}

@Specialization(guards = "isRubyString(format)", contains = "formatCached")
@@ -1990,7 +1997,14 @@ public DynamicObject formatUncached(
throw FormatExceptionTranslator.translate(this, e);
}

return finishFormat(Layouts.STRING.getRope(format).byteLength(), result);
final Rope rope = StringOperations.rope(format);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with format strings larger than int range");
}

return finishFormat((int) rope.byteLength(), result);
}

private DynamicObject finishFormat(int formatLength, BytesResult result) {
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
import org.joni.exception.ValueException;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
@@ -32,6 +33,7 @@
import org.jruby.truffle.core.coerce.ToIntNode;
import org.jruby.truffle.core.coerce.ToIntNodeGen;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringGuards;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.NotProvided;
@@ -205,7 +207,13 @@ public Object getIndex(DynamicObject matchData, int index, int length) {
public Object getIndexSymbol(DynamicObject matchData, DynamicObject index, NotProvided length) {
try {
final Rope value = Layouts.SYMBOL.getRope(index);
final int i = Layouts.REGEXP.getRegex(Layouts.MATCH_DATA.getRegexp(matchData)).nameToBackrefNumber(value.getBytes(), value.begin(), value.begin() + value.byteLength(), Layouts.MATCH_DATA.getRegion(matchData));

if (!CoreLibrary.fitsIntoInteger(value.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final int i = Layouts.REGEXP.getRegex(Layouts.MATCH_DATA.getRegexp(matchData)).nameToBackrefNumber(value.getBytes(), value.begin(), (int) (value.begin() + value.byteLength()), Layouts.MATCH_DATA.getRegion(matchData));

return getIndex(matchData, i, NotProvided.INSTANCE);
} catch (final ValueException e) {
@@ -221,7 +229,13 @@ public Object getIndexSymbol(DynamicObject matchData, DynamicObject index, NotPr
public Object getIndexString(DynamicObject matchData, DynamicObject index, NotProvided length) {
try {
final Rope value = StringOperations.rope(index);
final int i = Layouts.REGEXP.getRegex(Layouts.MATCH_DATA.getRegexp(matchData)).nameToBackrefNumber(value.getBytes(), value.begin(), value.begin() + value.byteLength(), Layouts.MATCH_DATA.getRegion(matchData));

if (!CoreLibrary.fitsIntoInteger(value.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final int i = Layouts.REGEXP.getRegex(Layouts.MATCH_DATA.getRegexp(matchData)).nameToBackrefNumber(value.getBytes(), value.begin(), (int) (value.begin() + value.byteLength()), Layouts.MATCH_DATA.getRegion(matchData));

return getIndex(matchData, i, NotProvided.INSTANCE);
}
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
import org.joni.exception.ValueException;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
@@ -54,6 +55,7 @@
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeNodesFactory;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.rubinius.RegexpPrimitiveNodes.RegexpSetLastMatchPrimitiveNode;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyGuards;
@@ -81,13 +83,18 @@ public static Object matchCommon(RubyContext context, RopeNodes.MakeSubstringNod

final Rope sourceRope = StringOperations.rope(source);

if (!CoreLibrary.fitsIntoInteger(sourceRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final Rope regexpSourceRope = Layouts.REGEXP.getSource(regexp);
final Encoding enc = checkEncoding(regexp, sourceRope, true);
final ByteList preprocessed = RegexpSupport.preprocess(context.getJRubyRuntime(), RopeOperations.getByteListReadOnly(regexpSourceRope), enc, new Encoding[] { null }, RegexpSupport.ErrorMode.RAISE);

final Regex r = new Regex(preprocessed.getUnsafeBytes(), preprocessed.getBegin(), preprocessed.getBegin() + preprocessed.getRealSize(), Layouts.REGEXP.getOptions(regexp).toJoniOptions(), checkEncoding(regexp, sourceRope, true));
final Matcher matcher = r.matcher(sourceRope.getBytes(), sourceRope.begin(), sourceRope.begin() + sourceRope.byteLength());
int range = sourceRope.begin() + sourceRope.byteLength();
final Matcher matcher = r.matcher(sourceRope.getBytes(), sourceRope.begin(), (int) (sourceRope.begin() + sourceRope.byteLength()));
int range = (int) (sourceRope.begin() + sourceRope.byteLength());

return matchCommon(context, makeSubstringNode, regexp, source, operator, setNamedCaptures, matcher, sourceRope.begin() + startPos, range);
}
@@ -99,6 +106,11 @@ public static Object matchCommon(RubyContext context, RopeNodes.MakeSubstringNod

final Rope sourceRope = StringOperations.rope(source);

if (!CoreLibrary.fitsIntoInteger(sourceRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final int match = matcher.search(startPos, range, Option.DEFAULT);

final DynamicObject nil = context.getCoreLibrary().getNilObject();
@@ -145,7 +157,7 @@ public static Object matchCommon(RubyContext context, RopeNodes.MakeSubstringNod
}

final DynamicObject pre = createSubstring(makeSubstringNode, source, 0, region.beg[0]);
final DynamicObject post = createSubstring(makeSubstringNode, source, region.end[0], sourceRope.byteLength() - region.end[0]);
final DynamicObject post = createSubstring(makeSubstringNode, source, region.end[0], (int) (sourceRope.byteLength() - region.end[0]));
final DynamicObject global = createSubstring(makeSubstringNode, source, region.beg[0], region.end[0] - region.beg[0]);

final DynamicObject matchObject = Layouts.MATCH_DATA.createMatchData(Layouts.CLASS.getInstanceFactory(context.getCoreLibrary().getMatchDataClass()),
Original file line number Diff line number Diff line change
@@ -11,6 +11,6 @@

public interface BytesVisitor {

void accept(byte[] bytes, int offset, int length);
void accept(byte[] bytes, long offset, long length);

}
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.jcodings.Encoding;
import org.jruby.util.func.Function1;

public class ConcatRope extends Rope {

@@ -48,7 +47,7 @@ public Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange) {

@Override
@TruffleBoundary
public byte getByteSlow(int index) {
public byte getByteSlow(long index) {
if (index < left.byteLength()) {
return left.getByteSlow(index);
}
13 changes: 10 additions & 3 deletions truffle/src/main/java/org/jruby/truffle/core/rope/LeafRope.java
Original file line number Diff line number Diff line change
@@ -9,17 +9,24 @@
*/
package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives;
import org.jcodings.Encoding;
import org.jruby.truffle.core.CoreLibrary;

public abstract class LeafRope extends Rope {

public LeafRope(byte[] bytes, Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, int characterLength) {
public LeafRope(byte[] bytes, Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, long characterLength) {
super(encoding, codeRange, singleByteOptimizable, bytes.length, characterLength, 1, bytes);
}

@Override
public byte getByteSlow(int index) {
return getRawBytes()[index];
public byte getByteSlow(long index) {
if (!CoreLibrary.fitsIntoInteger(index)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Index outside of int range");
}

return getRawBytes()[(int) index];
}

@Override
13 changes: 10 additions & 3 deletions truffle/src/main/java/org/jruby/truffle/core/rope/MutableRope.java
Original file line number Diff line number Diff line change
@@ -10,14 +10,16 @@

package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives;
import org.jcodings.Encoding;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.util.ByteList;

public class MutableRope extends LeafRope {

private final ByteList byteList;

protected MutableRope(byte[] bytes, Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, int characterLength) {
protected MutableRope(byte[] bytes, Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, long characterLength) {
super(bytes, encoding, codeRange, singleByteOptimizable, characterLength);
this.byteList = new ByteList(bytes, encoding, true);
}
@@ -37,8 +39,13 @@ public Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange) {
}

@Override
public byte getByteSlow(int index) {
return (byte) byteList.get(index);
public byte getByteSlow(long index) {
if (!CoreLibrary.fitsIntoInteger(index)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Index outside of int range");
}

return (byte) byteList.get((int) index);
}

public ByteList getByteList() {
Original file line number Diff line number Diff line change
@@ -10,14 +10,16 @@

package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives;
import org.jcodings.Encoding;
import org.jruby.truffle.core.CoreLibrary;

public class RepeatingRope extends Rope {

private final Rope child;
private final int times;
private final long times;

public RepeatingRope(Rope child, int times) {
public RepeatingRope(Rope child, long times) {
super(child.getEncoding(), child.getCodeRange(), child.isSingleByteOptimizable(), child.byteLength() * times, child.characterLength() * times, child.depth() + 1, null);
this.child = child;
this.times = times;
@@ -29,22 +31,27 @@ public Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange) {
}

@Override
protected byte getByteSlow(int index) {
protected byte getByteSlow(long index) {
return child.getByteSlow(index % child.byteLength());
}

public Rope getChild() {
return child;
}

public int getTimes() {
public long getTimes() {
return times;
}

@Override
public String toString() {
if (!CoreLibrary.fitsIntoInteger(byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't convert larger than int range to a Java String");
}

final String childString = child.toString();
final StringBuilder builder = new StringBuilder(childString.length() * times);
final StringBuilder builder = new StringBuilder((int) (childString.length() * times));

for (int i = 0; i < times; i++) {
builder.append(childString);
12 changes: 6 additions & 6 deletions truffle/src/main/java/org/jruby/truffle/core/rope/Rope.java
Original file line number Diff line number Diff line change
@@ -18,13 +18,13 @@ public abstract class Rope {
private final Encoding encoding;
private final CodeRange codeRange;
private final boolean singleByteOptimizable;
private final int byteLength;
private final int characterLength;
private final long byteLength;
private final long characterLength;
private final int ropeDepth;
private int hashCode = 0;
private byte[] bytes;

protected Rope(Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, int byteLength, int characterLength, int ropeDepth, byte[] bytes) {
protected Rope(Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, long byteLength, long characterLength, int ropeDepth, byte[] bytes) {
this.encoding = encoding;
this.codeRange = codeRange;
this.singleByteOptimizable = singleByteOptimizable;
@@ -36,19 +36,19 @@ protected Rope(Encoding encoding, CodeRange codeRange, boolean singleByteOptimiz

public abstract Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange);

public final int characterLength() {
public final long characterLength() {
return characterLength;
}

public final int byteLength() {
public final long byteLength() {
return byteLength;
}

public boolean isEmpty() {
return byteLength == 0;
}

protected abstract byte getByteSlow(int index);
protected abstract byte getByteSlow(long index);

public final byte[] getRawBytes() {
return bytes;
60 changes: 38 additions & 22 deletions truffle/src/main/java/org/jruby/truffle/core/rope/RopeNodes.java
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
import org.jcodings.specific.USASCIIEncoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;
import org.jruby.util.ByteList;
@@ -56,10 +57,10 @@ public MakeSubstringNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract Rope executeMake(Rope base, int offset, int byteLength);
public abstract Rope executeMake(Rope base, long offset, long byteLength);

@Specialization(guards = "byteLength == 0")
public Rope substringZeroBytes(Rope base, int offset, int byteLength,
public Rope substringZeroBytes(Rope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile isUTF8,
@Cached("createBinaryProfile()") ConditionProfile isUSAscii,
@Cached("createBinaryProfile()") ConditionProfile isAscii8Bit) {
@@ -79,7 +80,7 @@ public Rope substringZeroBytes(Rope base, int offset, int byteLength,
}

@Specialization(guards = "byteLength == 1")
public Rope substringOneByte(Rope base, int offset, int byteLength,
public Rope substringOneByte(Rope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile isUTF8,
@Cached("createBinaryProfile()") ConditionProfile isUSAscii,
@Cached("createBinaryProfile()") ConditionProfile isAscii8Bit,
@@ -102,26 +103,26 @@ public Rope substringOneByte(Rope base, int offset, int byteLength,
}

@Specialization(guards = { "byteLength > 1", "sameAsBase(base, offset, byteLength)" })
public Rope substringSameAsBase(Rope base, int offset, int byteLength) {
public Rope substringSameAsBase(Rope base, long offset, long byteLength) {
return base;
}

@Specialization(guards = { "byteLength > 1", "!sameAsBase(base, offset, byteLength)" })
public Rope substringLeafRope(LeafRope base, int offset, int byteLength,
public Rope substringLeafRope(LeafRope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile is7BitProfile,
@Cached("createBinaryProfile()") ConditionProfile isBinaryStringProfile) {
return makeSubstring(base, offset, byteLength, is7BitProfile, isBinaryStringProfile);
}

@Specialization(guards = { "byteLength > 1", "!sameAsBase(base, offset, byteLength)" })
public Rope substringSubstringRope(SubstringRope base, int offset, int byteLength,
public Rope substringSubstringRope(SubstringRope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile is7BitProfile,
@Cached("createBinaryProfile()") ConditionProfile isBinaryStringProfile) {
return makeSubstring(base.getChild(), offset + base.getOffset(), byteLength, is7BitProfile, isBinaryStringProfile);
}

@Specialization(guards = { "byteLength > 1", "!sameAsBase(base, offset, byteLength)" })
public Rope substringMultiplyRope(RepeatingRope base, int offset, int byteLength,
public Rope substringMultiplyRope(RepeatingRope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile is7BitProfile,
@Cached("createBinaryProfile()") ConditionProfile isBinaryStringProfile,
@Cached("createBinaryProfile()") ConditionProfile matchesChildProfile) {
@@ -137,7 +138,7 @@ public Rope substringMultiplyRope(RepeatingRope base, int offset, int byteLength
}

@Specialization(guards = { "byteLength > 1", "!sameAsBase(base, offset, byteLength)" })
public Rope substringConcatRope(ConcatRope base, int offset, int byteLength,
public Rope substringConcatRope(ConcatRope base, long offset, long byteLength,
@Cached("createBinaryProfile()") ConditionProfile is7BitProfile,
@Cached("createBinaryProfile()") ConditionProfile isBinaryStringProfile) {
Rope root = base;
@@ -171,7 +172,7 @@ public Rope substringConcatRope(ConcatRope base, int offset, int byteLength,
return makeSubstring(root, offset, byteLength, is7BitProfile, isBinaryStringProfile);
}

private Rope makeSubstring(Rope base, int offset, int byteLength, ConditionProfile is7BitProfile, ConditionProfile isBinaryStringProfile) {
private Rope makeSubstring(Rope base, long offset, long byteLength, ConditionProfile is7BitProfile, ConditionProfile isBinaryStringProfile) {
if (is7BitProfile.profile(base.getCodeRange() == CR_7BIT)) {
if (getContext().getOptions().ROPE_LAZY_SUBSTRINGS) {
return new SubstringRope(base, offset, byteLength, byteLength, CR_7BIT);
@@ -194,8 +195,18 @@ private Rope makeSubstring(Rope base, int offset, int byteLength, ConditionProfi
}

@TruffleBoundary
private Rope makeSubstringNon7Bit(Rope base, int offset, int byteLength) {
final long packedLengthAndCodeRange = RopeOperations.calculateCodeRangeAndLength(base.getEncoding(), base.getBytes(), offset, offset + byteLength);
private Rope makeSubstringNon7Bit(Rope base, long offset, long byteLength) {
if (!CoreLibrary.fitsIntoInteger(offset)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't index byte array outside of int range");
}

if (!CoreLibrary.fitsIntoInteger(byteLength)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't figure code range or length for array larger than int size");
}

final long packedLengthAndCodeRange = RopeOperations.calculateCodeRangeAndLength(base.getEncoding(), base.getBytes(), (int) offset, (int) (offset + byteLength));
final CodeRange codeRange = CodeRange.fromInt(StringSupport.unpackArg(packedLengthAndCodeRange));
final int characterLength = StringSupport.unpackResult(packedLengthAndCodeRange);

@@ -219,7 +230,7 @@ private Rope makeSubstringNon7Bit(Rope base, int offset, int byteLength) {
}
}

protected static boolean sameAsBase(Rope base, int offset, int byteLength) {
protected static boolean sameAsBase(Rope base, long offset, long byteLength) {
return (byteLength - offset) == base.byteLength();
}

@@ -654,35 +665,38 @@ public GetByteNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract int executeGetByte(Rope rope, int index);
public abstract int executeGetByte(Rope rope, long index);

@Specialization(guards = "rope.getRawBytes() != null")
public int getByte(Rope rope, int index) {
return rope.getRawBytes()[index] & 0xff;
public int getByte(Rope rope, long index) {
// If the raw bytes aren't null, then by definition the byteLength must be in the int range.
return rope.getRawBytes()[(int) index] & 0xff;
}

@Specialization(guards = "rope.getRawBytes() == null")
public int getByteSubstringRope(SubstringRope rope, int index,
public int getByteSubstringRope(SubstringRope rope, long index,
@Cached("createBinaryProfile()") ConditionProfile childRawBytesNullProfile) {
if (childRawBytesNullProfile.profile(rope.getChild().getRawBytes() == null)) {
return rope.getByteSlow(index) & 0xff;
}

return rope.getChild().getRawBytes()[index + rope.getOffset()] & 0xff;
// If the raw bytes aren't null, then by definition the byteLength must be in the int range.
return rope.getChild().getRawBytes()[(int) (index + rope.getOffset())] & 0xff;
}

@Specialization(guards = "rope.getRawBytes() == null")
public int getByteRepeatingRope(RepeatingRope rope, int index,
public int getByteRepeatingRope(RepeatingRope rope, long index,
@Cached("createBinaryProfile()") ConditionProfile childRawBytesNullProfile) {
if (childRawBytesNullProfile.profile(rope.getChild().getRawBytes() == null)) {
return rope.getByteSlow(index) & 0xff;
}

return rope.getChild().getRawBytes()[index % rope.getChild().byteLength()] & 0xff;
// If the raw bytes aren't null, then by definition the byteLength must be in the int range.
return rope.getChild().getRawBytes()[(int) (index % rope.getChild().byteLength())] & 0xff;
}

@Specialization(guards = "rope.getRawBytes() == null")
public int getByteConcatRope(ConcatRope rope, int index,
public int getByteConcatRope(ConcatRope rope, long index,
@Cached("createBinaryProfile()") ConditionProfile chooseLeftChildProfile,
@Cached("createBinaryProfile()") ConditionProfile leftChildRawBytesNullProfile,
@Cached("createBinaryProfile()") ConditionProfile rightChildRawBytesNullProfile) {
@@ -691,14 +705,16 @@ public int getByteConcatRope(ConcatRope rope, int index,
return rope.getLeft().getByteSlow(index) & 0xff;
}

return rope.getLeft().getRawBytes()[index] & 0xff;
// If the raw bytes aren't null, then by definition the byteLength must be in the int range.
return rope.getLeft().getRawBytes()[(int) index] & 0xff;
}

if (rightChildRawBytesNullProfile.profile(rope.getRight().getRawBytes() == null)) {
return rope.getRight().getByteSlow(index - rope.getLeft().byteLength()) & 0xff;
}

return rope.getRight().getRawBytes()[index - rope.getLeft().byteLength()] & 0xff;
// If the raw bytes aren't null, then by definition the byteLength must be in the int range.
return rope.getRight().getRawBytes()[(int) (index - rope.getLeft().byteLength())] & 0xff;
}

}
126 changes: 91 additions & 35 deletions truffle/src/main/java/org/jruby/truffle/core/rope/RopeOperations.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.jruby.truffle.core.rope;

public class RopeTooLongException extends UnsupportedOperationException {
public RopeTooLongException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -11,20 +11,20 @@
package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.jcodings.Encoding;
import org.jruby.truffle.core.CoreLibrary;

public class SubstringRope extends Rope {

private final Rope child;
private final int offset;
private final long offset;

public SubstringRope(Rope child, int offset, int byteLength, int characterLength, CodeRange codeRange) {
public SubstringRope(Rope child, long offset, long byteLength, long characterLength, CodeRange codeRange) {
// TODO (nirvdrum 07-Jan-16) Verify that this rope is only used for character substrings and not arbitrary byte slices. The former should always have the child's code range while the latter may not.
this(child, child.getEncoding(), offset, byteLength, characterLength, codeRange);
}

private SubstringRope(Rope child, Encoding encoding, int offset, int byteLength, int characterLength, CodeRange codeRange) {
private SubstringRope(Rope child, Encoding encoding, long offset, long byteLength, long characterLength, CodeRange codeRange) {
// TODO (nirvdrum 07-Jan-16) Verify that this rope is only used for character substrings and not arbitrary byte slices. The former should always have the child's code range while the latter may not.
super(encoding, codeRange, child.isSingleByteOptimizable(), byteLength, characterLength, child.depth() + 1, null);
this.child = child;
@@ -42,22 +42,32 @@ public Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange) {
}

@Override
public byte getByteSlow(int index) {
public byte getByteSlow(long index) {
return child.getByteSlow(index + offset);
}

public Rope getChild() {
return child;
}

public int getOffset() {
public long getOffset() {
return offset;
}

@Override
public String toString() {
if (!CoreLibrary.fitsIntoInteger(this.offset)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't create string from rope out of int range");
}

if (!CoreLibrary.fitsIntoInteger(byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't create string from rope larger than int range");
}

// This should be used for debugging only.
return child.toString().substring(offset, offset + byteLength());
return child.toString().substring((int) offset, (int) (offset + byteLength()));
}

}
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@

public class ValidLeafRope extends LeafRope {

public ValidLeafRope(byte[] bytes, Encoding encoding, int characterLength) {
public ValidLeafRope(byte[] bytes, Encoding encoding, long characterLength) {
super(bytes, encoding, CodeRange.CR_VALID, encoding.isSingleByte(), characterLength);
}

Original file line number Diff line number Diff line change
@@ -19,11 +19,13 @@
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.UnaryCoreMethodNode;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.util.ByteList;
@@ -72,7 +74,13 @@ public PrependNode(RubyContext context, SourceSection sourceSection) {
@Specialization(guards = "isRubyString(string)")
public DynamicObject prepend(DynamicObject bytes, DynamicObject string) {
final Rope rope = StringOperations.rope(string);
final int prependLength = rope.byteLength();

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final int prependLength = (int) rope.byteLength();
final int originalLength = Layouts.BYTE_ARRAY.getBytes(bytes).getUnsafeBytes().length;
final int newLength = prependLength + originalLength;
final byte[] prependedBytes = new byte[newLength];
Original file line number Diff line number Diff line change
@@ -21,13 +21,15 @@
import org.jcodings.transcode.EConv;
import org.jcodings.transcode.EConvResult;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.encoding.EncodingConverterNodes;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeNodesFactory;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyGuards;
@@ -69,19 +71,19 @@ public PrimitiveConvertNode(RubyContext context, SourceSection sourceSection) {

@Specialization(guards = {"isRubyString(source)", "isRubyString(target)", "isRubyHash(options)"})
public Object encodingConverterPrimitiveConvert(DynamicObject encodingConverter, DynamicObject source,
DynamicObject target, int offset, int size, DynamicObject options) {
DynamicObject target, long offset, long size, DynamicObject options) {
throw new UnsupportedOperationException("not implemented");
}

@Specialization(guards = {"isNil(source)", "isRubyString(target)"})
public Object primitiveConvertNilSource(DynamicObject encodingConverter, DynamicObject source,
DynamicObject target, int offset, int size, int options) {
DynamicObject target, long offset, long size, int options) {
return primitiveConvertHelper(encodingConverter, source, target, offset, size, options);
}

@Specialization(guards = {"isRubyString(source)", "isRubyString(target)"})
public Object encodingConverterPrimitiveConvert(DynamicObject encodingConverter, DynamicObject source,
DynamicObject target, int offset, int size, int options) {
DynamicObject target, long offset, long size, int options) {

// Taken from org.jruby.RubyConverter#primitive_convert.

@@ -90,11 +92,17 @@ public Object encodingConverterPrimitiveConvert(DynamicObject encodingConverter,

@TruffleBoundary
private Object primitiveConvertHelper(DynamicObject encodingConverter, DynamicObject source,
DynamicObject target, int offset, int size, int options) {
DynamicObject target, long offset, long size, int options) {
// Taken from org.jruby.RubyConverter#primitive_convert.

final boolean nonNullSource = source != nil();
Rope sourceRope = nonNullSource ? rope(source) : RopeConstants.EMPTY_UTF8_ROPE;

if (!CoreLibrary.fitsIntoInteger(sourceRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final Rope targetRope = rope(target);
final ByteList outBytes = RopeOperations.toByteListCopy(targetRope);

@@ -140,9 +148,9 @@ private Object primitiveConvertHelper(DynamicObject encodingConverter, DynamicOb
outBytes.ensure((int) outputByteEnd);

inPtr.p = 0;
outPtr.p = offset;
int os = outPtr.p + size;
EConvResult res = ec.convert(sourceRope.getBytes(), inPtr, sourceRope.byteLength() + inPtr.p, outBytes.getUnsafeBytes(), outPtr, os, options);
outPtr.p = (int) offset;
int os = (int) (outPtr.p + size);
EConvResult res = ec.convert(sourceRope.getBytes(), inPtr, (int) (sourceRope.byteLength() + inPtr.p), outBytes.getUnsafeBytes(), outPtr, os, options);

outBytes.setRealSize(outPtr.p - outBytes.begin());

Original file line number Diff line number Diff line change
@@ -45,9 +45,11 @@
import com.oracle.truffle.api.source.SourceSection;
import jnr.constants.platform.Errno;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.exception.ExceptionNodes;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.objects.AllocateObjectNode;
@@ -93,7 +95,13 @@ public int unshift(VirtualFrame frame, DynamicObject ioBuffer, DynamicObject str
Layouts.IO_BUFFER.setWriteSynced(ioBuffer, false);

final Rope rope = StringOperations.rope(string);
int stringSize = rope.byteLength() - startPosition;

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

int stringSize = (int) (rope.byteLength() - startPosition);
final int usedSpace = Layouts.IO_BUFFER.getUsed(ioBuffer);
final int availableSpace = IOBUFFER_SIZE - usedSpace;

Original file line number Diff line number Diff line change
@@ -48,10 +48,12 @@
import jnr.posix.DefaultNativeTimeval;
import jnr.posix.Timeval;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.array.ArrayOperations;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.core.thread.ThreadManager;
import org.jruby.truffle.language.RubyGuards;
@@ -211,12 +213,22 @@ public boolean fnmatch(DynamicObject pattern, DynamicObject path, int flags) {
final Rope patternRope = rope(pattern);
final Rope pathRope = rope(path);

if (!CoreLibrary.fitsIntoInteger(patternRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with patterns larger than int range");
}

if (!CoreLibrary.fitsIntoInteger(pathRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with paths larger than int range");
}

return Dir.fnmatch(patternRope.getBytes(),
patternRope.begin(),
patternRope.begin() + patternRope.byteLength(),
(int) (patternRope.begin() + patternRope.byteLength()),
pathRope.getBytes(),
pathRope.begin(),
pathRope.begin() + pathRope.byteLength(),
(int) (pathRope.begin() + pathRope.byteLength()),
flags) != Dir.FNM_NOMATCH;
}

@@ -408,13 +420,18 @@ public int write(DynamicObject file, DynamicObject string) {

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't write strings larger than int range");
}

if (getContext().getDebugStandardOut() != null && fd == STDOUT) {
getContext().getDebugStandardOut().write(rope.getBytes(), rope.begin(), rope.byteLength());
return rope.byteLength();
getContext().getDebugStandardOut().write(rope.getBytes(), rope.begin(), (int) rope.byteLength());
return (int) rope.byteLength();
}

// TODO (eregon, 11 May 2015): review consistency under concurrent modification
final ByteBuffer buffer = ByteBuffer.wrap(rope.getBytes(), rope.begin(), rope.byteLength());
final ByteBuffer buffer = ByteBuffer.wrap(rope.getBytes(), rope.begin(), (int) rope.byteLength());

int total = 0;

Original file line number Diff line number Diff line change
@@ -19,10 +19,12 @@
import jnr.ffi.Pointer;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.ffi.PointerGuards;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.objects.AllocateObjectNode;
import org.jruby.truffle.language.objects.AllocateObjectNodeGen;
@@ -342,7 +344,13 @@ public PointerWriteStringPrimitiveNode(RubyContext context, SourceSection source
@Specialization(guards = "isRubyString(string)")
public DynamicObject address(DynamicObject pointer, DynamicObject string, int maxLength) {
final Rope rope = StringOperations.rope(string);
final int length = Math.min(rope.byteLength(), maxLength);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final int length = (int) Math.min(rope.byteLength(), maxLength);
Layouts.POINTER.getPointer(pointer).put(0, rope.getBytes(), 0, length);
return pointer;
}
Original file line number Diff line number Diff line change
@@ -20,12 +20,14 @@
import org.joni.Matcher;
import org.joni.Regex;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.regexp.RegexpGuards;
import org.jruby.truffle.core.regexp.RegexpNodes;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.util.ByteList;
@@ -145,8 +147,14 @@ public Object searchRegion(DynamicObject regexp, DynamicObject string, int start
final Encoding enc = RegexpNodes.checkEncoding(regexp, stringRope, true);
ByteList preprocessed = RegexpSupport.preprocess(getContext().getJRubyRuntime(), RopeOperations.getByteListReadOnly(regexpSourceRope), enc, new Encoding[]{null}, RegexpSupport.ErrorMode.RAISE);
Rope preprocessedRope = RegexpNodes.shimModifiers(StringOperations.ropeFromByteList(preprocessed));
final Regex r = new Regex(preprocessedRope.getBytes(), preprocessedRope.begin(), preprocessedRope.begin() + preprocessedRope.byteLength(), Layouts.REGEXP.getRegex(regexp).getOptions(), RegexpNodes.checkEncoding(regexp, stringRope, true));
final Matcher matcher = r.matcher(stringRope.getBytes(), stringRope.begin(), stringRope.begin() + stringRope.byteLength());

if (!CoreLibrary.fitsIntoInteger(preprocessedRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't work with strings larger than int range");
}

final Regex r = new Regex(preprocessedRope.getBytes(), preprocessedRope.begin(), (int) (preprocessedRope.begin() + preprocessedRope.byteLength()), Layouts.REGEXP.getRegex(regexp).getOptions(), RegexpNodes.checkEncoding(regexp, stringRope, true));
final Matcher matcher = r.matcher(stringRope.getBytes(), stringRope.begin(), (int) (stringRope.begin() + stringRope.byteLength()));

if (forward) {
// Search forward through the string.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ public static boolean ropesEqual(DynamicObject string, Rope rope) {
}
}

public static int ropeLength(Rope rope) {
public static long ropeLength(Rope rope) {
return rope.byteLength();
}

114 changes: 86 additions & 28 deletions truffle/src/main/java/org/jruby/truffle/core/string/StringNodes.java
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.CoreMethodNode;
@@ -76,6 +77,7 @@
import org.jruby.truffle.core.rope.RopeNodes;
import org.jruby.truffle.core.rope.RopeNodesFactory;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.rubinius.StringPrimitiveNodes;
import org.jruby.truffle.core.rubinius.StringPrimitiveNodesFactory;
import org.jruby.truffle.language.NotProvided;
@@ -426,8 +428,8 @@ public GetIndexNode(RubyContext context, SourceSection sourceSection) {
@Specialization(guards = "wasNotProvided(length) || isRubiniusUndefined(length)")
public Object getIndex(VirtualFrame frame, DynamicObject string, int index, Object length) {
final Rope rope = rope(string);
final int stringLength = rope.characterLength();
int normalizedIndex = StringOperations.normalizeIndex(stringLength, index);
final long stringLength = rope.characterLength();
long normalizedIndex = StringOperations.normalizeIndex(stringLength, index);

if (normalizedIndex < 0 || normalizedIndex >= rope.byteLength()) {
outOfBounds.enter();
@@ -462,10 +464,10 @@ public Object sliceObjectRange(VirtualFrame frame, DynamicObject string, Dynamic
return sliceRange(frame, string, coercedBegin, coercedEnd, Layouts.OBJECT_RANGE.getExcludedEnd(range));
}

private Object sliceRange(VirtualFrame frame, DynamicObject string, int begin, int end, boolean doesExcludeEnd) {
private Object sliceRange(VirtualFrame frame, DynamicObject string, long begin, long end, boolean doesExcludeEnd) {
assert RubyGuards.isRubyString(string);

final int stringLength = rope(string).characterLength();
final long stringLength = rope(string).characterLength();
begin = StringOperations.normalizeIndex(stringLength, begin);

if (begin < 0 || begin > stringLength) {
@@ -480,7 +482,7 @@ private Object sliceRange(VirtualFrame frame, DynamicObject string, int begin, i
}

end = StringOperations.normalizeIndex(stringLength, end);
int length = StringOperations.clampExclusiveIndex(string, doesExcludeEnd ? end : end + 1);
long length = StringOperations.clampExclusiveIndex(string, doesExcludeEnd ? end : end + 1);

if (length > stringLength) {
length = stringLength;
@@ -644,7 +646,7 @@ public ByteSizeNode(RubyContext context, SourceSection sourceSection) {
}

@Specialization
public int byteSize(DynamicObject string) {
public long byteSize(DynamicObject string) {
return rope(string).byteLength();
}

@@ -815,15 +817,25 @@ public Object crypt(DynamicObject string, DynamicObject salt) {
final Rope value = rope(string);
final Rope other = rope(salt);

if (!CoreLibrary.fitsIntoInteger(value.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't crypt strings larger than int range");
}

if (!CoreLibrary.fitsIntoInteger(other.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't crypt ropes with salt larger than int range");
}

final Encoding ascii8bit = getContext().getJRubyRuntime().getEncodingService().getAscii8bitEncoding();
if (other.byteLength() < 2) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(coreLibrary().argumentError("salt too short (need >= 2 bytes)", this));
}

final TrufflePosix posix = posix();
final byte[] keyBytes = Arrays.copyOfRange(value.getBytes(), 0, value.byteLength());
final byte[] saltBytes = Arrays.copyOfRange(other.getBytes(), 0, other.byteLength());
final byte[] keyBytes = Arrays.copyOfRange(value.getBytes(), 0, (int) value.byteLength());
final byte[] saltBytes = Arrays.copyOfRange(other.getBytes(), 0, (int) other.byteLength());

if (saltBytes[0] == 0 || saltBytes[1] == 0) {
CompilerDirectives.transferToInterpreter();
@@ -1083,15 +1095,15 @@ private int multiByteStringLength(Encoding enc, byte[] bytes, int p, int end) {
// source string, you'll get a different rope. Unlike String#each_byte, String#each_char does not make
// modifications to the string visible to the rest of the iteration.
private Object substr(Rope rope, DynamicObject string, int beg, int len) {
int length = rope.byteLength();
long length = rope.byteLength();
if (len < 0 || beg > length) return nil();

if (beg < 0) {
beg += length;
if (beg < 0) return nil();
}

int end = Math.min(length, beg + len);
long end = Math.min(length, beg + len);

final Rope substringRope = makeSubstringNode.executeMake(rope, beg, end - beg);

@@ -1385,9 +1397,9 @@ public Object insert(VirtualFrame frame, DynamicObject string, int index, Dynami
String.format("incompatible encodings: %s and %s", source.getEncoding(), insert.getEncoding()), this));
}

final int stringLength = source.characterLength();
final int normalizedIndex = StringNodesHelper.checkIndex(stringLength, index, this);
final int byteIndex = characterByteIndexNode.executeInt(frame, string, normalizedIndex, 0);
final long stringLength = source.characterLength();
final long normalizedIndex = StringNodesHelper.checkIndex(stringLength, index, this);
final long byteIndex = characterByteIndexNode.executeLong(frame, string, normalizedIndex, 0);

final Rope splitLeft = leftMakeSubstringNode.executeMake(source, 0, byteIndex);
final Rope splitRight = rightMakeSubstringNode.executeMake(source, byteIndex, source.byteLength() - byteIndex);
@@ -1435,8 +1447,14 @@ public Object lstripBangSingleByte(DynamicObject string) {
// Taken from org.jruby.RubyString#lstrip_bang19 and org.jruby.RubyString#singleByteLStrip.

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't lstrip strings larger than int range");
}

final int s = rope.begin();
final int end = s + rope.byteLength();
final int end = (int) (s + rope.byteLength());
final byte[] bytes = rope.getBytes();

int p = s;
@@ -1456,9 +1474,15 @@ public Object lstripBang(DynamicObject string) {
// Taken from org.jruby.RubyString#lstrip_bang19 and org.jruby.RubyString#multiByteLStrip.

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't lstrip strings larger than int range");
}

final Encoding enc = RopeOperations.STR_ENC_GET(rope);
final int s = rope.begin();
final int end = s + rope.byteLength();
final int end = (int) (s + rope.byteLength());
final byte[] bytes = rope.getBytes();

int p = s;
@@ -1561,8 +1585,13 @@ public int ord(DynamicObject string) {
}

@TruffleBoundary
private int codePoint(Encoding encoding, byte[] bytes, int p, int end) {
return StringSupport.codePoint(encoding, bytes, p, end);
private int codePoint(Encoding encoding, byte[] bytes, long p, long end) {
if (!CoreLibrary.fitsIntoInteger(p) || !CoreLibrary.fitsIntoInteger(end)) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't offset into array outside int range");
}

return StringSupport.codePoint(encoding, bytes, (int) p, (int) end);
}

}
@@ -1619,9 +1648,15 @@ public Object rstripBangSingleByte(DynamicObject string) {
// Taken from org.jruby.RubyString#rstrip_bang19 and org.jruby.RubyString#singleByteRStrip19.

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't rstrip strings larger than int range");
}

final byte[] bytes = rope.getBytes();
final int start = 0;
final int end = rope.byteLength();
final int end = (int) rope.byteLength();
int endp = end - 1;
while (endp >= start && (bytes[endp] == 0 ||
ASCIIEncoding.INSTANCE.isSpace(bytes[endp] & 0xff))) endp--;
@@ -1641,10 +1676,16 @@ public Object rstripBang(DynamicObject string) {
// Taken from org.jruby.RubyString#rstrip_bang19 and org.jruby.RubyString#multiByteRStrip19.

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't rstrip strings larger than int range");
}

final Encoding enc = RopeOperations.STR_ENC_GET(rope);
final byte[] bytes = rope.getBytes();
final int start = 0;
final int end = rope.byteLength();
final int end = (int) rope.byteLength();

int endp = end;
int prev;
@@ -1700,8 +1741,13 @@ public DynamicObject swapcaseSingleByte(DynamicObject string,
return nil();
}

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't swapcase strings larger than int range");
}

final int s = rope.begin();
final int end = s + rope.byteLength();
final int end = (int) (s + rope.byteLength());
final byte[] bytes = rope.getBytesCopy();

if (singleByteOptimizableProfile.profile(rope.isSingleByteOptimizable())) {
@@ -1799,6 +1845,7 @@ public SetByteNode(RubyContext context, SourceSection sourceSection) {
rightMakeSubstringNode = RopeNodesFactory.MakeSubstringNodeGen.create(context, sourceSection, null, null, null);
}

// TODO (nirvdrum 07-Apr-16) This needs to be updated for long-length strings.
@CreateCast("index") public RubyNode coerceIndexToInt(RubyNode index) {
return FixnumLowerNodeGen.create(getContext(), getSourceSection(),
ToIntNodeGen.create(getContext(), getSourceSection(), index));
@@ -1810,8 +1857,8 @@ public SetByteNode(RubyContext context, SourceSection sourceSection) {
}

@Specialization
public int setByte(DynamicObject string, int index, int value) {
final int normalizedIndex = StringNodesHelper.checkIndexForRef(string, index, this);
public int setByte(DynamicObject string, long index, int value) {
final long normalizedIndex = StringNodesHelper.checkIndexForRef(string, index, this);

final Rope rope = rope(string);

@@ -1835,7 +1882,7 @@ public SizeNode(RubyContext context, SourceSection sourceSection) {
}

@Specialization
public int size(DynamicObject string,
public long size(DynamicObject string,
@Cached("createBinaryProfile()") ConditionProfile mutableRopeProfile) {
final Rope rope = rope(string);

@@ -2012,9 +2059,15 @@ public Object sum(VirtualFrame frame, DynamicObject string, long bits) {
// Copied from JRuby

final Rope rope = rope(string);

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't sum ropes larger than int range");
}

final byte[] bytes = rope.getBytes();
int p = rope.begin();
final int len = rope.byteLength();
final int len = (int) rope.byteLength();
final int end = p + len;

if (bits >= 8 * 8) { // long size * bits in byte
@@ -2505,14 +2558,19 @@ public DynamicObject capitalizeBang(DynamicObject string) {
String.format("incompatible encoding with this operation: %s", enc), this));
}

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't capitalize ropes larger than int range");
}

if (rope.isEmpty()) {
return nil();
}

StringOperations.modifyAndKeepCodeRange(string);

int s = 0;
int end = s + rope.byteLength();
int end = (int) (s + rope.byteLength());
byte[] bytes = rope.getBytesCopy();
boolean modify = false;

@@ -2581,7 +2639,7 @@ public DynamicObject clear(DynamicObject string) {

public static class StringNodesHelper {

public static int checkIndex(int length, int index, RubyNode node) {
public static long checkIndex(long length, long index, RubyNode node) {
if (index > length) {
CompilerDirectives.transferToInterpreter();

@@ -2603,10 +2661,10 @@ public static int checkIndex(int length, int index, RubyNode node) {
return index;
}

public static int checkIndexForRef(DynamicObject string, int index, RubyNode node) {
public static long checkIndexForRef(DynamicObject string, long index, RubyNode node) {
assert RubyGuards.isRubyString(string);

final int length = rope(string).byteLength();
final long length = rope(string).byteLength();

if (index >= length) {
CompilerDirectives.transferToInterpreter();
Original file line number Diff line number Diff line change
@@ -170,15 +170,25 @@ public static void forceEncodingVerySlow(DynamicObject string, Encoding encoding
StringOperations.setRope(string, RopeOperations.withEncodingVerySlow(oldRope, encoding, CodeRange.CR_UNKNOWN));
}

public static int normalizeIndex(int length, int index) {
return ArrayOperations.normalizeIndex(length, index);
public static long normalizeIndex(long length, long index) {
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, index < 0)) {
return length + index;
} else {
return index;
}
}

public static int clampExclusiveIndex(DynamicObject string, int index) {
public static long clampExclusiveIndex(DynamicObject string, long index) {
assert RubyGuards.isRubyString(string);

// TODO (nirvdrum 21-Jan-16): Verify this is supposed to be the byteLength and not the characterLength.
return ArrayOperations.clampExclusiveIndex(StringOperations.rope(string).byteLength(), index);
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.UNLIKELY_PROBABILITY, index < 0)) {
return 0;
} else if (CompilerDirectives.injectBranchProbability(CompilerDirectives.UNLIKELY_PROBABILITY, index > rope(string).byteLength())) {
return rope(string).byteLength();
} else {
return index;
}
}

public static Encoding checkEncoding(RubyContext context, DynamicObject string, DynamicObject other, Node node) {
Original file line number Diff line number Diff line change
@@ -14,9 +14,11 @@
import com.oracle.truffle.api.object.DynamicObject;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.util.ByteList;
@@ -117,7 +119,13 @@ public DynamicObject getSymbol(Rope rope) {

final DynamicObject symbolClass = context.getCoreLibrary().getSymbolClass();
final Rope flattenedRope = RopeOperations.flatten(rope);
final String string = ByteList.decode(flattenedRope.getBytes(), flattenedRope.begin(), flattenedRope.byteLength(), "ISO-8859-1");

if (!CoreLibrary.fitsIntoInteger(flattenedRope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't convert ropes larger than int range to Java Strings");
}

final String string = ByteList.decode(flattenedRope.getBytes(), flattenedRope.begin(), (int) flattenedRope.byteLength(), "ISO-8859-1");

final DynamicObject newSymbol = Layouts.SYMBOL.createSymbol(
Layouts.CLASS.getInstanceFactory(symbolClass),
16 changes: 13 additions & 3 deletions truffle/src/main/java/org/jruby/truffle/stdlib/DigestNodes.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
*/
package org.jruby.truffle.stdlib;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
@@ -17,13 +18,15 @@
import org.jruby.ext.digest.BubbleBabble;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.BytesVisitor;
import org.jruby.truffle.core.rope.CodeRange;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rope.RopeTooLongException;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.util.ByteList;

@@ -155,8 +158,9 @@ public DynamicObject update(DynamicObject digestObject, DynamicObject message) {
RopeOperations.visitBytes(StringOperations.rope(message), new BytesVisitor() {

@Override
public void accept(byte[] bytes, int offset, int length) {
digest.update(bytes, offset, length);
public void accept(byte[] bytes, long offset, long length) {
// Since bytes is limited to int range, the offset and length must as well, so casting is safe.
digest.update(bytes, (int) offset, (int) length);
}

});
@@ -236,7 +240,13 @@ public BubbleBabbleNode(RubyContext context, SourceSection sourceSection) {
@Specialization(guards = "isRubyString(message)")
public DynamicObject bubblebabble(DynamicObject message) {
final Rope rope = StringOperations.rope(message);
return createString(BubbleBabble.bubblebabble(rope.getBytes(), rope.begin(), rope.byteLength()));

if (!CoreLibrary.fitsIntoInteger(rope.byteLength())) {
CompilerDirectives.transferToInterpreter();
throw new RopeTooLongException("Can't create a ByteList larger than int range");
}

return createString(BubbleBabble.bubblebabble(rope.getBytes(), rope.begin(), (int) rope.byteLength()));
}

}
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public int memsizeOfHash(DynamicObject object) {
}

@Specialization(guards = "isRubyString(object)")
public int memsizeOfString(DynamicObject object) {
public long memsizeOfString(DynamicObject object) {
return 1 + object.getShape().getPropertyListInternal(false).size() + StringOperations.rope(object).byteLength();
}

0 comments on commit 4710381

Please sign in to comment.