Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'truffle-ropes-2' into truffle-ropes-on-head
Browse files Browse the repository at this point in the history
nirvdrum committed Jan 27, 2016
2 parents 358331b + 0b72e28 commit 6ad0311
Showing 21 changed files with 471 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -19,6 +19,8 @@
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.StringOperations;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.truffle.runtime.rope.RopeOperations;
import org.jruby.truffle.translator.BodyTranslator;
import org.jruby.util.RegexpOptions;

@@ -48,13 +50,15 @@ public Object execute(VirtualFrame frame) {

final org.jruby.RubyString preprocessed = org.jruby.RubyRegexp.preprocessDRegexp(getContext().getRuntime(), strings, options);

final DynamicObject regexp = RegexpNodes.createRubyRegexp(getContext(), this, getContext().getCoreLibrary().getRegexpClass(), preprocessed.getByteList(), options);
final DynamicObject regexp = RegexpNodes.createRubyRegexp(getContext(), this, getContext().getCoreLibrary().getRegexpClass(), StringOperations.ropeFromByteList(preprocessed.getByteList()), options);

if (options.isEncodingNone()) {
final Rope source = Layouts.REGEXP.getSource(regexp);

if (!BodyTranslator.all7Bit(preprocessed.getByteList().bytes())) {
Layouts.REGEXP.getSource(regexp).setEncoding(getContext().getRuntime().getEncodingService().getAscii8bitEncoding());
Layouts.REGEXP.setSource(regexp, RopeOperations.withEncoding(source, getContext().getRuntime().getEncodingService().getAscii8bitEncoding()));
} else {
Layouts.REGEXP.getSource(regexp).setEncoding(getContext().getRuntime().getEncodingService().getUSAsciiEncoding());
Layouts.REGEXP.setSource(regexp, RopeOperations.withEncoding(source, getContext().getRuntime().getEncodingService().getUSAsciiEncoding()));
}
}

72 changes: 42 additions & 30 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/RegexpNodes.java
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@
import org.jruby.truffle.runtime.core.StringOperations;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.truffle.runtime.rope.RopeOperations;
import org.jruby.util.*;

import java.nio.charset.StandardCharsets;
@@ -55,17 +56,17 @@ public static Object matchCommon(RubyContext context, DynamicObject regexp, Dyna
assert RubyGuards.isRubyRegexp(regexp);
assert RubyGuards.isRubyString(source);

final ByteList sourceByteList = StringOperations.getByteListReadOnly(source);
final Rope sourceRope = StringOperations.rope(source);

final ByteList bl = Layouts.REGEXP.getSource(regexp);
final Encoding enc = checkEncoding(regexp, StringOperations.getCodeRangeableReadOnly(source), true);
final ByteList preprocessed = RegexpSupport.preprocess(context.getRuntime(), bl, enc, new Encoding[] { null }, RegexpSupport.ErrorMode.RAISE);
final Rope regexpSourceRope = Layouts.REGEXP.getSource(regexp);
final Encoding enc = checkEncoding(regexp, sourceRope, true);
final ByteList preprocessed = RegexpSupport.preprocess(context.getRuntime(), regexpSourceRope.getUnsafeByteList(), 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, StringOperations.getCodeRangeableReadOnly(source), true));
final Matcher matcher = r.matcher(sourceByteList.unsafeBytes(), sourceByteList.begin(), sourceByteList.begin() + sourceByteList.realSize());
int range = sourceByteList.begin() + sourceByteList.realSize();
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.realSize());
int range = sourceRope.begin() + sourceRope.realSize();

return matchCommon(context, regexp, source, operator, setNamedCaptures, matcher, sourceByteList.begin() + startPos, range);
return matchCommon(context, regexp, source, operator, setNamedCaptures, matcher, sourceRope.begin() + startPos, range);
}

@TruffleBoundary
@@ -197,7 +198,7 @@ private static void setLocalVariable(Frame frame, String name, Object value) {
}
}

public static ByteList shimModifiers(ByteList bytes) {
public static Rope shimModifiers(Rope bytes) {
// Joni doesn't support (?u) etc but we can shim some common cases

String bytesString = bytes.toString();
@@ -223,14 +224,15 @@ public static ByteList shimModifiers(ByteList bytes) {
throw new UnsupportedOperationException();
}

bytes = ByteList.create(bytesString);
// TODO (nirvdrum 25-Jan-16): We probably just want a way to create a Rope from a java.lang.String.
bytes = StringOperations.ropeFromByteList(ByteList.create(bytesString));
}

return bytes;
}

@TruffleBoundary
public static Regex compile(Node currentNode, RubyContext context, ByteList bytes, RegexpOptions options) {
public static Regex compile(Node currentNode, RubyContext context, Rope bytes, RegexpOptions options) {
bytes = shimModifiers(bytes);

try {
@@ -249,13 +251,14 @@ public static Regex compile(Node currentNode, RubyContext context, ByteList byte
}
*/

final ByteList byteList = bytes.getUnsafeByteList();
Encoding enc = bytes.getEncoding();
Encoding[] fixedEnc = new Encoding[]{null};
ByteList unescaped = RegexpSupport.preprocess(context.getRuntime(), bytes, enc, fixedEnc, RegexpSupport.ErrorMode.RAISE);
ByteList unescaped = RegexpSupport.preprocess(context.getRuntime(), byteList, enc, fixedEnc, RegexpSupport.ErrorMode.RAISE);
if (fixedEnc[0] != null) {
if ((fixedEnc[0] != enc && options.isFixed()) ||
(fixedEnc[0] != ASCIIEncoding.INSTANCE && options.isEncodingNone())) {
RegexpSupport.raiseRegexpError19(context.getRuntime(), bytes, enc, options, "incompatible character encoding");
RegexpSupport.raiseRegexpError19(context.getRuntime(), byteList, enc, options, "incompatible character encoding");
}
if (fixedEnc[0] != ASCIIEncoding.INSTANCE) {
options.setFixed(true);
@@ -268,10 +271,8 @@ public static Regex compile(Node currentNode, RubyContext context, ByteList byte
if (fixedEnc[0] != null) options.setFixed(true);
//if (regexpOptions.isEncodingNone()) setEncodingNone();

bytes.setEncoding(enc);

Regex ret = new Regex(unescaped.getUnsafeBytes(), unescaped.getBegin(), unescaped.getBegin() + unescaped.getRealSize(), options.toJoniOptions(), enc, Syntax.RUBY);
ret.setUserObject(bytes);
ret.setUserObject(RopeOperations.withEncoding(bytes, enc));

return ret;
} catch (ValueException e) {
@@ -293,7 +294,7 @@ public static void setRegex(DynamicObject regexp, Regex regex) {
Layouts.REGEXP.setRegex(regexp, regex);
}

public static void setSource(DynamicObject regexp, ByteList source) {
public static void setSource(DynamicObject regexp, Rope source) {
Layouts.REGEXP.setSource(regexp, source);
}

@@ -302,7 +303,7 @@ public static void setOptions(DynamicObject regexp, RegexpOptions options) {
}

// TODO (nirvdrum 03-June-15) Unify with JRuby in RegexpSupport.
public static Encoding checkEncoding(DynamicObject regexp, CodeRangeable str, boolean warn) {
public static Encoding checkEncoding(DynamicObject regexp, Rope str, boolean warn) {
assert RubyGuards.isRubyRegexp(regexp);

final Regex pattern = Layouts.REGEXP.getRegex(regexp);
@@ -313,7 +314,7 @@ public static Encoding checkEncoding(DynamicObject regexp, CodeRangeable str, bo
}
*/
//check();
Encoding enc = str.getByteList().getEncoding();
Encoding enc = str.getEncoding();
if (!enc.isAsciiCompatible()) {
if (enc != pattern.getEncoding()) {
//encodingMatchError(getRuntime(), pattern, enc);
@@ -336,31 +337,42 @@ public static Encoding checkEncoding(DynamicObject regexp, CodeRangeable str, bo
return enc;
}

public static void initialize(RubyContext context, DynamicObject regexp, Node currentNode, ByteList setSource, int options) {
public static void initialize(RubyContext context, DynamicObject regexp, Node currentNode, Rope setSource, int options) {
assert RubyGuards.isRubyRegexp(regexp);
setSource(regexp, setSource);
setOptions(regexp, RegexpOptions.fromEmbeddedOptions(options));
setRegex(regexp, compile(currentNode, context, setSource, Layouts.REGEXP.getOptions(regexp)));
final RegexpOptions regexpOptions = RegexpOptions.fromEmbeddedOptions(options);
final Regex regex = compile(currentNode, context, setSource, regexpOptions);

// The RegexpNodes.compile operation may modify the encoding of the source rope. This modified copy is stored
// in the Regex object as the "user object". Since ropes are immutable, we need to take this updated copy when
// constructing the final regexp.
setSource(regexp, (Rope) regex.getUserObject());
setOptions(regexp, regexpOptions);
setRegex(regexp, regex);
}

public static void initialize(DynamicObject regexp, Regex setRegex, ByteList setSource) {
public static void initialize(DynamicObject regexp, Regex setRegex, Rope setSource) {
assert RubyGuards.isRubyRegexp(regexp);
setRegex(regexp, setRegex);
setSource(regexp, setSource);
}

public static DynamicObject createRubyRegexp(RubyContext context, Node currentNode, DynamicObject regexpClass, ByteList regex, RegexpOptions options) {
return Layouts.REGEXP.createRegexp(Layouts.CLASS.getInstanceFactory(regexpClass), RegexpNodes.compile(currentNode, context, regex, options), regex, options, null);
public static DynamicObject createRubyRegexp(RubyContext context, Node currentNode, DynamicObject regexpClass, Rope source, RegexpOptions options) {
final Regex regexp = RegexpNodes.compile(currentNode, context, source, options);

// The RegexpNodes.compile operation may modify the encoding of the source rope. This modified copy is stored
// in the Regex object as the "user object". Since ropes are immutable, we need to take this updated copy when
// constructing the final regexp.
return Layouts.REGEXP.createRegexp(Layouts.CLASS.getInstanceFactory(regexpClass), regexp, (Rope) regexp.getUserObject(), options, null);
}

public static DynamicObject createRubyRegexp(DynamicObject regexpClass, Regex regex, ByteList source, RegexpOptions options) {
public static DynamicObject createRubyRegexp(DynamicObject regexpClass, Regex regex, Rope source, RegexpOptions options) {
final DynamicObject regexp = Layouts.REGEXP.createRegexp(Layouts.CLASS.getInstanceFactory(regexpClass), null, null, RegexpOptions.NULL_OPTIONS, null);
RegexpNodes.setOptions(regexp, options);
RegexpNodes.initialize(regexp, regex, source);
return regexp;
}

public static DynamicObject createRubyRegexp(DynamicObject regexpClass, Regex regex, ByteList source) {
public static DynamicObject createRubyRegexp(DynamicObject regexpClass, Regex regex, Rope source) {
final DynamicObject regexp = Layouts.REGEXP.createRegexp(Layouts.CLASS.getInstanceFactory(regexpClass), null, null, RegexpOptions.NULL_OPTIONS, null);
RegexpNodes.initialize(regexp, regex, source);
return regexp;
@@ -502,7 +514,7 @@ public SourceNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public DynamicObject source(DynamicObject regexp) {
return createString(Layouts.REGEXP.getSource(regexp).dup());
return createString(Layouts.REGEXP.getSource(regexp));
}

}
@@ -517,7 +529,7 @@ public ToSNode(RubyContext context, SourceSection sourceSection) {
@TruffleBoundary
@Specialization
public DynamicObject toS(DynamicObject regexp) {
return createString(((org.jruby.RubyString) org.jruby.RubyRegexp.newRegexp(getContext().getRuntime(), Layouts.REGEXP.getSource(regexp), Layouts.REGEXP.getRegex(regexp).getOptions()).to_s()).getByteList());
return createString(((org.jruby.RubyString) org.jruby.RubyRegexp.newRegexp(getContext().getRuntime(), Layouts.REGEXP.getSource(regexp).getUnsafeByteList(), Layouts.REGEXP.getRegex(regexp).getOptions()).to_s()).getByteList());
}

}
97 changes: 94 additions & 3 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/RopeNodes.java
Original file line number Diff line number Diff line change
@@ -10,13 +10,13 @@

package org.jruby.truffle.nodes.core;


import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node.Child;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.ConditionProfile;
import org.jcodings.Encoding;
@@ -111,7 +111,7 @@ private Rope makeSubstring(Rope base, int offset, int byteLength, ConditionProfi
return makeSubstringNon7Bit(base, offset, byteLength);
}

@CompilerDirectives.TruffleBoundary
@TruffleBoundary
private Rope makeSubstringNon7Bit(Rope base, int offset, int byteLength) {
final long packedLengthAndCodeRange = RopeOperations.calculateCodeRangeAndLength(base.getEncoding(), base.getBytes(), offset, offset + byteLength);
final int codeRange = StringSupport.unpackArg(packedLengthAndCodeRange);
@@ -344,4 +344,95 @@ protected static boolean isUnknown(int codeRange) {
return codeRange == StringSupport.CR_UNKNOWN;
}
}

@NodeChildren({
@NodeChild(type = RubyNode.class, value = "rope"),
@NodeChild(type = RubyNode.class, value = "currentLevel"),
@NodeChild(type = RubyNode.class, value = "printString")
})
public abstract static class DebugPrintRopeNode extends RubyNode {

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

public abstract DynamicObject executeDebugPrint(Rope rope, int currentLevel, boolean printString);

@TruffleBoundary
@Specialization
public DynamicObject debugPrintLeafRope(LeafRope rope, int currentLevel, boolean printString) {
printPreamble(currentLevel);

// Converting a rope to a java.lang.String may populate the byte[], so we need to query for the array status beforehand.
final boolean bytesAreNull = rope.getRawBytes() == null;

System.err.println(String.format("%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; D: %d)",
printString ? rope.toString() : "<skipped>",
rope.getClass().getSimpleName(),
bytesAreNull,
rope.byteLength(),
rope.characterLength(),
StringSupport.codeRangeAsString(rope.getCodeRange()),
rope.depth()));

return nil();
}

@TruffleBoundary
@Specialization
public DynamicObject debugPrintSubstringRope(SubstringRope rope, int currentLevel, boolean printString) {
printPreamble(currentLevel);

// Converting a rope to a java.lang.String may populate the byte[], so we need to query for the array status beforehand.
final boolean bytesAreNull = rope.getRawBytes() == null;

System.err.println(String.format("%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; O: %d; D: %d)",
printString ? rope.toString() : "<skipped>",
rope.getClass().getSimpleName(),
bytesAreNull,
rope.byteLength(),
rope.characterLength(),
StringSupport.codeRangeAsString(rope.getCodeRange()),
rope.getOffset(),
rope.depth()));

executeDebugPrint(rope.getChild(), currentLevel + 1, printString);

return nil();
}

@TruffleBoundary
@Specialization
public DynamicObject debugPrintConcatRope(ConcatRope rope, int currentLevel, boolean printString) {
printPreamble(currentLevel);

// Converting a rope to a java.lang.String may populate the byte[], so we need to query for the array status beforehand.
final boolean bytesAreNull = rope.getRawBytes() == null;

System.err.println(String.format("%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; D: %d; LD: %d; RD: %d)",
printString ? rope.toString() : "<skipped>",
rope.getClass().getSimpleName(),
bytesAreNull,
rope.byteLength(),
rope.characterLength(),
StringSupport.codeRangeAsString(rope.getCodeRange()),
rope.depth(),
rope.getLeft().depth(),
rope.getRight().depth()));

executeDebugPrint(rope.getLeft(), currentLevel + 1, printString);
executeDebugPrint(rope.getRight(), currentLevel + 1, printString);

return nil();
}

private void printPreamble(int level) {
if (level > 0) {
for (int i = 0; i < level; i++) {
System.err.print("| ");
}
}
}

}
}
Original file line number Diff line number Diff line change
@@ -68,4 +68,9 @@ public static boolean isEmpty(DynamicObject string) {
assert RubyGuards.isRubyString(string);
return Layouts.STRING.getRope(string).isEmpty();
}

public static boolean isBrokenCodeRange(DynamicObject string) {
assert RubyGuards.isRubyString(string);
return StringOperations.codeRange(string) == StringSupport.CR_BROKEN;
}
}
86 changes: 50 additions & 36 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/StringNodes.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
import org.jruby.truffle.runtime.hash.BucketsStrategy;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.methods.InternalMethod;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.truffle.runtime.rope.RopeOperations;
import org.jruby.truffle.runtime.subsystems.SimpleShell;
import org.jruby.util.ByteList;
@@ -456,6 +457,57 @@ public DynamicObject debugPrint(DynamicObject string) {

}

@CoreMethod(names = "debug_print_rope", onSingleton = true, required = 1, optional = 1)
public abstract static class DebugPrintRopeNode extends CoreMethodArrayArgumentsNode {

@Child private RopeNodes.DebugPrintRopeNode debugPrintRopeNode;

public DebugPrintRopeNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
debugPrintRopeNode = RopeNodesFactory.DebugPrintRopeNodeGen.create(context, sourceSection, null, null, null);
}

@TruffleBoundary
@Specialization(guards = "isRubyString(string)")
public DynamicObject debugPrintDefault(DynamicObject string, NotProvided printString) {
return debugPrint(string, true);
}

@TruffleBoundary
@Specialization(guards = "isRubyString(string)")
public DynamicObject debugPrint(DynamicObject string, boolean printString) {
System.err.println("Legend: ");
System.err.println("BN = Bytes Null? (byte[] not yet populated)");
System.err.println("BL = Byte Length");
System.err.println("CL = Character Length");
System.err.println("CR = Code Range");
System.err.println("O = Offset (SubstringRope only)");
System.err.println("D = Depth");
System.err.println("LD = Left Depth (ConcatRope only)");
System.err.println("RD = Right Depth (ConcatRope only)");

return debugPrintRopeNode.executeDebugPrint(StringOperations.rope(string), 0, printString);
}

}

@CoreMethod(names = "flatten_rope", onSingleton = true, required = 1)
public abstract static class FlattenRopeNode extends CoreMethodArrayArgumentsNode {

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

@TruffleBoundary
@Specialization(guards = "isRubyString(string)")
public DynamicObject debugPrint(DynamicObject string) {
final Rope flattened = RopeOperations.flatten(StringOperations.rope(string));

return createString(flattened);
}

}

@CoreMethod(names = "jruby_home_directory", onSingleton = true)
public abstract static class JRubyHomeDirectoryNode extends CoreMethodNode {

Original file line number Diff line number Diff line change
@@ -11,11 +11,13 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectFactory;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.ConditionProfile;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.core.*;
import org.jruby.truffle.runtime.RubyContext;
@@ -40,8 +42,21 @@ public GetByteNode(RubyContext context, SourceSection sourceSection) {
}

@Specialization
public int getByte(DynamicObject bytes, int index) {
return Layouts.BYTE_ARRAY.getBytes(bytes).get(index) & 0xff;
public int getByte(DynamicObject bytes, int index,
@Cached("createBinaryProfile()") ConditionProfile nullByteIndexProfile) {
final ByteList byteList = Layouts.BYTE_ARRAY.getBytes(bytes);

// Handling out-of-bounds issues like this is non-standard. In Rubinius, it would raise an exception instead.
// We're modifying the semantics to address a primary use case for this class: Rubinius's @data array
// in the String class. Rubinius Strings are NULL-terminated and their code working with Strings takes
// advantage of that fact. So, where they expect to receive a NULL byte, we'd be out-of-bounds and raise
// an exception. Simply appending a NULL byte may trigger a full copy of the original byte[], which we
// want to avoid. The compromise is bending on the semantics here.
if (nullByteIndexProfile.profile(index == byteList.realSize())) {
return 0;
}

return byteList.get(index) & 0xff;
}

}
Original file line number Diff line number Diff line change
@@ -147,7 +147,7 @@ private Object primitiveConvertHelper(DynamicObject encodingConverter, DynamicOb

if (nonNullSource) {
sourceRope = makeSubstringNode.executeMake(sourceRope, inPtr.p, sourceRope.byteLength() - inPtr.p);
Layouts.STRING.setRope(source, sourceRope);
StringOperations.setRope(source, sourceRope);
}

if (growOutputBuffer && res == EConvResult.DestinationBufferFull) {
@@ -164,7 +164,7 @@ private Object primitiveConvertHelper(DynamicObject encodingConverter, DynamicOb
outBytes.setEncoding(ec.destinationEncoding);
}

Layouts.STRING.setRope(target, StringOperations.ropeFromByteList(outBytes));
StringOperations.setRope(target, StringOperations.ropeFromByteList(outBytes));

return getSymbol(res.symbolicName());
}
Original file line number Diff line number Diff line change
@@ -713,7 +713,7 @@ public DynamicObject getcwd(DynamicObject resultPath, int maxSize) {
// We just ignore maxSize - I think this is ok

final String path = getContext().getRuntime().getCurrentDirectory();
Layouts.STRING.setRope(resultPath, makeLeafRopeNode.executeMake(path.getBytes(StandardCharsets.UTF_8), Layouts.STRING.getRope(resultPath).getEncoding(), StringSupport.CR_UNKNOWN));
StringOperations.setRope(resultPath, makeLeafRopeNode.executeMake(path.getBytes(StandardCharsets.UTF_8), Layouts.STRING.getRope(resultPath).getEncoding(), StringSupport.CR_UNKNOWN));
return resultPath;
}

Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.StringOperations;
import org.jruby.truffle.runtime.layouts.Layouts;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.util.ByteList;
import org.jruby.util.RegexpSupport;

@@ -69,7 +70,7 @@ public DynamicObject initializeAlreadyInitialized(DynamicObject regexp, DynamicO

@Specialization(guards = {"!isRegexpLiteral(regexp)", "!isInitialized(regexp)", "isRubyString(pattern)"})
public DynamicObject initialize(DynamicObject regexp, DynamicObject pattern, int options) {
RegexpNodes.initialize(getContext(), regexp, this, StringOperations.getByteListReadOnly(pattern), options);
RegexpNodes.initialize(getContext(), regexp, this, StringOperations.rope(pattern), options);
return regexp;
}

@@ -135,20 +136,20 @@ public Object searchRegionInvalidEncoding(DynamicObject regexp, DynamicObject st
@TruffleBoundary
@Specialization(guards = {"isInitialized(regexp)", "isRubyString(string)", "isValidEncoding(string)"})
public Object searchRegion(DynamicObject regexp, DynamicObject string, int start, int end, boolean forward) {
final ByteList stringBl = StringOperations.getByteListReadOnly(string);
final ByteList bl = Layouts.REGEXP.getSource(regexp);
final Encoding enc = RegexpNodes.checkEncoding(regexp, StringOperations.getCodeRangeableReadOnly(string), true);
ByteList preprocessed = RegexpSupport.preprocess(getContext().getRuntime(), bl, enc, new Encoding[]{null}, RegexpSupport.ErrorMode.RAISE);
preprocessed = RegexpNodes.shimModifiers(preprocessed);
final Regex r = new Regex(preprocessed.getUnsafeBytes(), preprocessed.getBegin(), preprocessed.getBegin() + preprocessed.getRealSize(), Layouts.REGEXP.getRegex(regexp).getOptions(), RegexpNodes.checkEncoding(regexp, StringOperations.getCodeRangeableReadOnly(string), true));
final Matcher matcher = r.matcher(stringBl.getUnsafeBytes(), stringBl.begin(), stringBl.begin() + stringBl.realSize());
final Rope stringRope = StringOperations.rope(string);
final Rope regexpSourceRope = Layouts.REGEXP.getSource(regexp);
final Encoding enc = RegexpNodes.checkEncoding(regexp, stringRope, true);
ByteList preprocessed = RegexpSupport.preprocess(getContext().getRuntime(), regexpSourceRope.getUnsafeByteList(), enc, new Encoding[]{null}, RegexpSupport.ErrorMode.RAISE);
Rope preprocessedRope = RegexpNodes.shimModifiers(StringOperations.ropeFromByteList(preprocessed));
final Regex r = new Regex(preprocessedRope.getBytes(), preprocessedRope.getBegin(), preprocessedRope.getBegin() + preprocessedRope.getRealSize(), Layouts.REGEXP.getRegex(regexp).getOptions(), RegexpNodes.checkEncoding(regexp, stringRope, true));
final Matcher matcher = r.matcher(stringRope.getBytes(), stringRope.begin(), stringRope.begin() + stringRope.realSize());

if (forward) {
// Search forward through the string.
return RegexpNodes.matchCommon(getContext(), regexp, string, false, false, matcher, start + stringBl.begin(), end + stringBl.begin());
return RegexpNodes.matchCommon(getContext(), regexp, string, false, false, matcher, start + stringRope.begin(), end + stringRope.begin());
} else {
// Search backward through the string.
return RegexpNodes.matchCommon(getContext(), regexp, string, false, false, matcher, end + stringBl.begin(), start + stringBl.begin());
return RegexpNodes.matchCommon(getContext(), regexp, string, false, false, matcher, end + stringRope.begin(), start + stringRope.begin());
}
}

Original file line number Diff line number Diff line change
@@ -171,7 +171,7 @@ public DynamicObject stringAppend(VirtualFrame frame, DynamicObject string, Dyna
String.format("incompatible encodings: %s and %s", left.getEncoding(), right.getEncoding()), this));
}

Layouts.STRING.setRope(string, makeConcatNode.executeMake(left, right, compatibleEncoding));
StringOperations.setRope(string, makeConcatNode.executeMake(left, right, compatibleEncoding));

return string;
}
@@ -1169,7 +1169,7 @@ public DynamicObject stringCopyFrom(DynamicObject string, DynamicObject other, i

System.arraycopy(otherRope.getBytes(), otherRope.begin() + src, stringBytes.getUnsafeBytes(), stringBytes.begin() + dest, cnt);

Layouts.STRING.setRope(string, StringOperations.ropeFromByteList(stringBytes));
StringOperations.setRope(string, StringOperations.ropeFromByteList(stringBytes));

return string;
}
@@ -1357,7 +1357,7 @@ public Object splicePrepend(DynamicObject string, DynamicObject other, int splic
final Rope left = rope(other);
final Rope right = prependMakeSubstringNode.executeMake(original, byteCountToReplace, original.byteLength() - byteCountToReplace);

Layouts.STRING.setRope(string, prependMakeConcatNode.executeMake(left, right, right.getEncoding()));
StringOperations.setRope(string, prependMakeConcatNode.executeMake(left, right, right.getEncoding()));

return string;
}
@@ -1372,7 +1372,7 @@ public Object spliceAppend(DynamicObject string, DynamicObject other, int splice
appendMakeConcatNode = insert(RopeNodesFactory.MakeConcatNodeGen.create(getContext(), getSourceSection(), null, null, null));
}

Layouts.STRING.setRope(string, appendMakeConcatNode.executeMake(left, right, left.getEncoding()));
StringOperations.setRope(string, appendMakeConcatNode.executeMake(left, right, left.getEncoding()));

return string;
}
@@ -1408,7 +1408,7 @@ public DynamicObject splice(DynamicObject string, DynamicObject other, int splic
final Rope joinedLeft = leftMakeConcatNode.executeMake(splitLeft, insert, source.getEncoding());
final Rope joinedRight = rightMakeConcatNode.executeMake(joinedLeft, splitRight, source.getEncoding());

Layouts.STRING.setRope(string, joinedRight);
StringOperations.setRope(string, joinedRight);

return string;
}
@@ -1482,7 +1482,7 @@ public DynamicObject stringByteAppend(DynamicObject string, DynamicObject other)

final Rope rightConverted = makeLeafRopeNode.executeMake(right.getBytes(), left.getEncoding(), left.getCodeRange());

Layouts.STRING.setRope(string, makeConcatNode.executeMake(left, rightConverted, left.getEncoding()));
StringOperations.setRope(string, makeConcatNode.executeMake(left, rightConverted, left.getEncoding()));

return string;
}
Original file line number Diff line number Diff line change
@@ -64,17 +64,6 @@ public static String getString(RubyContext context, DynamicObject string) {
return RopeOperations.decodeRope(context.getRuntime(), StringOperations.rope(string));
}

public static StringCodeRangeableWrapper getCodeRangeable(DynamicObject string) {
StringCodeRangeableWrapper wrapper = Layouts.STRING.getCodeRangeableWrapper(string);

if (wrapper == null) {
wrapper = new StringCodeRangeableWrapper(string);
Layouts.STRING.setCodeRangeableWrapper(string, wrapper);
}

return wrapper;
}

public static StringCodeRangeableWrapper getCodeRangeableReadWrite(final DynamicObject string) {
return new StringCodeRangeableWrapper(string) {
private final ByteList byteList = StringOperations.rope(string).toByteListCopy();
@@ -168,7 +157,7 @@ public static Encoding checkEncoding(DynamicObject string, CodeRangeable other)
public static void forceEncoding(DynamicObject string, Encoding encoding) {
modify(string);
final Rope oldRope = Layouts.STRING.getRope(string);
Layouts.STRING.setRope(string, RopeOperations.withEncoding(oldRope, encoding, StringSupport.CR_UNKNOWN));
StringOperations.setRope(string, RopeOperations.withEncoding(oldRope, encoding, StringSupport.CR_UNKNOWN));
}

public static int normalizeIndex(int length, int index) {
@@ -228,6 +217,13 @@ public static Rope rope(DynamicObject string) {
return Layouts.STRING.getRope(string);
}

public static void setRope(DynamicObject string, Rope rope) {
assert RubyGuards.isRubyString(string);

Layouts.STRING.setRope(string, rope);
Layouts.STRING.setRubiniusDataArray(string, null);
}

public static Encoding encoding(DynamicObject string) {
assert RubyGuards.isRubyString(string);

Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
import org.joni.Regex;
import org.jruby.truffle.om.dsl.api.Layout;
import org.jruby.truffle.om.dsl.api.Nullable;
import org.jruby.util.ByteList;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.util.RegexpOptions;

@Layout
@@ -25,7 +25,7 @@ DynamicObjectFactory createRegexpShape(DynamicObject logicalClass,

DynamicObject createRegexp(DynamicObjectFactory factory,
@Nullable Regex regex,
@Nullable ByteList source,
@Nullable Rope source,
RegexpOptions options,
@Nullable Object cachedNames);

@@ -35,8 +35,8 @@ DynamicObject createRegexp(DynamicObjectFactory factory,
Regex getRegex(DynamicObject object);
void setRegex(DynamicObject object, Regex value);

ByteList getSource(DynamicObject object);
void setSource(DynamicObject object, ByteList value);
Rope getSource(DynamicObject object);
void setSource(DynamicObject object, Rope value);

RegexpOptions getOptions(DynamicObject object);
void setOptions(DynamicObject object, RegexpOptions value);
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ DynamicObjectFactory createStringShape(DynamicObject logicalClass,

DynamicObject createString(DynamicObjectFactory factory,
Rope rope,
@Nullable StringCodeRangeableWrapper codeRangeableWrapper);
@Nullable DynamicObject rubiniusDataArray);

boolean isString(ObjectType objectType);
boolean isString(DynamicObject dynamicObject);
@@ -34,7 +34,7 @@ DynamicObject createString(DynamicObjectFactory factory,
Rope getRope(DynamicObject object);
void setRope(DynamicObject object, Rope rope);

StringCodeRangeableWrapper getCodeRangeableWrapper(DynamicObject object);
void setCodeRangeableWrapper(DynamicObject object, StringCodeRangeableWrapper codeRangeableWrapper);
DynamicObject getRubiniusDataArray(DynamicObject object);
void setRubiniusDataArray(DynamicObject object, DynamicObject rubiniusDataArray);

}
Original file line number Diff line number Diff line change
@@ -40,19 +40,6 @@ public int get(int index) {
return right.get(index - left.byteLength());
}

@Override
@TruffleBoundary
public byte[] calculateBytes() {
final byte[] leftBytes = left.getBytes();
final byte[] rightBytes = right.getBytes();

final byte[] bytes = new byte[leftBytes.length + rightBytes.length];
System.arraycopy(leftBytes, 0, bytes, 0, leftBytes.length);
System.arraycopy(rightBytes, 0, bytes, leftBytes.length, rightBytes.length);

return bytes;
}

@Override
@TruffleBoundary
public byte[] extractRange(int offset, int length) {
@@ -96,31 +83,7 @@ public Rope getRight() {
@Override
public String toString() {
// This should be used for debugging only.
return RopeOperations.decodeUTF8(left) + RopeOperations.decodeUTF8(right);
return left.toString() + right.toString();
}

@Override
protected void fillBytes(byte[] buffer, int bufferPosition, int offset, int byteLength) {
if (getRawBytes() != null) {
System.arraycopy(getRawBytes(), offset, buffer, bufferPosition, byteLength);
} else {
final int leftLength = left.byteLength();

if (offset < leftLength) {
// The left branch might not be large enough to extract the full hash code we want. In that case,
// we'll extract what we can and extract the difference from the right side.
if (offset + byteLength > leftLength) {
final int coveredByLeft = leftLength - offset;

left.fillBytes(buffer, bufferPosition, offset, coveredByLeft);
right.fillBytes(buffer, bufferPosition + coveredByLeft, 0, byteLength - coveredByLeft);

} else {
left.fillBytes(buffer, bufferPosition, offset, byteLength);
}
} else {
right.fillBytes(buffer, bufferPosition, offset - leftLength, byteLength);
}
}
}
}
11 changes: 0 additions & 11 deletions truffle/src/main/java/org/jruby/truffle/runtime/rope/LeafRope.java
Original file line number Diff line number Diff line change
@@ -24,11 +24,6 @@ public int get(int index) {
return getRawBytes()[index];
}

@Override
public byte[] calculateBytes() {
throw new UnsupportedOperationException("LeafRope's bytes are always known. There is no need to calculate them.");
}

@Override
public byte[] extractRange(int offset, int length) {
assert offset + length <= byteLength();
@@ -47,10 +42,4 @@ public String toString() {
return RopeOperations.decodeUTF8(this);
}

@Override
protected void fillBytes(byte[] buffer, int bufferPosition, int offset, int byteLength) {
assert offset + byteLength <= byteLength();

System.arraycopy(getRawBytes(), offset, buffer, bufferPosition, byteLength);
}
}
Original file line number Diff line number Diff line change
@@ -63,7 +63,7 @@ public final byte[] getRawBytes() {
public final byte[] getBytes() {
if (bytes == null) {
CompilerDirectives.transferToInterpreter();
bytes = calculateBytes();
bytes = RopeOperations.flattenBytes(this);
}

return bytes;
@@ -75,8 +75,6 @@ public byte[] getBytesCopy() {

public abstract byte[] extractRange(int offset, int length);

public abstract byte[] calculateBytes();

public final Encoding getEncoding() {
return encoding;
}
@@ -138,6 +136,4 @@ public boolean equals(Object o) {
return false;
}

protected abstract void fillBytes(byte[] buffer, int bufferPosition, int offset, int byteLength);

}
Original file line number Diff line number Diff line change
@@ -25,6 +25,8 @@

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.Deque;

public class RopeOperations {

@@ -134,7 +136,162 @@ public static LeafRope flatten(Rope rope) {
return (LeafRope) rope;
}

return create(rope.getBytes(), rope.getEncoding(), rope.getCodeRange());
return create(flattenBytes(rope), rope.getEncoding(), rope.getCodeRange());
}

@TruffleBoundary
/**
* Performs an iterative depth first search of the Rope tree to calculate its byte[] without needing to populate
* the byte[] for each level beneath. Every LeafRope has its byte[] populated by definition. The goal is to determine
* which descendant LeafRopes contribute bytes to the top-most Rope's logical byte[] and how many bytes they should
* contribute. Then each such LeafRope copies the appropriate range of bytes to a shared byte[].
*
* Rope trees can be very deep. An iterative algorithm is preferable to recursion because it removes the overhead
* of stack frame management. Additionally, a recursive algorithm will eventually overflow the stack if the Rope
* tree is too deep.
*/
public static byte[] flattenBytes(Rope rope) {
if (rope instanceof LeafRope) {
return rope.getRawBytes();
}

int bufferPosition = 0;
int offset = 0;

final byte[] buffer = new byte[rope.byteLength()];

// As we traverse the rope tree, we need to keep track of any bounded lengths of SubstringRopes. LeafRopes always
// provide their full byte[]. ConcatRope always provides the full byte[] of each of its children. SubstringRopes,
// in contrast, may bound the length of their children. Since we may have SubstringRopes of SubstringRopes, we
// need to track each SubstringRope's bounded length and how much that bounded length contributes to the total
// byte[] for any ancestor (e.g., a SubstringRope of a ConcatRope with SubstringRopes for each of its children).
// Because we need to track multiple levels, we can't use a single updated int.
final Deque<Integer> substringLengths = new ArrayDeque<>();

final Deque<Rope> workStack = new ArrayDeque<>();
workStack.push(rope);

while (!workStack.isEmpty()) {
final Rope current = workStack.pop();

// An empty rope trivially cannot contribute to filling the output buffer.
if (current.isEmpty()) {
continue;
}

if (current instanceof ConcatRope) {
final ConcatRope concatRope = (ConcatRope) current;

// In the absence of any SubstringRopes, we always take the full contents of the ConcatRope.
if (substringLengths.isEmpty()) {
workStack.push(concatRope.getRight());
workStack.push(concatRope.getLeft());
} else {
final int leftLength = concatRope.getLeft().byteLength();

// If we reach here, this ConcatRope is a descendant of a SubstringRope at some level. Based on
// the currently calculated byte[] offset and the number of bytes to extract, determine which of
// the ConcatRope's children we need to visit.
if (offset < leftLength) {
if ((offset + substringLengths.peek()) > leftLength) {
workStack.push(concatRope.getRight());
workStack.push(concatRope.getLeft());
} else {
workStack.push(concatRope.getLeft());
}
} else {
// If we can skip the left child entirely, we need to update the offset so it's accurate for
// the right child as each child's starting point is 0.
offset -= leftLength;
workStack.push(concatRope.getRight());
}
}
} else if (current instanceof SubstringRope) {
final SubstringRope substringRope = (SubstringRope) current;

// If this SubstringRope is a descendant of another SubstringRope, we need to increment the offset
// so that when we finally reach a LeafRope, we're extracting bytes from the correct location.
offset += substringRope.getOffset();

workStack.push(substringRope.getChild());

// Either we haven't seen another SubstringRope or it's been cleared off the work queue. In either case,
// we can start fresh.
if (substringLengths.isEmpty()) {
substringLengths.push(substringRope.byteLength());
} else {
// Since we may be taking a substring of a substring, we need to note that we're not extracting the
// entirety of the current SubstringRope.
final int adjustedByteLength = substringRope.byteLength() - (offset - substringRope.getOffset());

// We have to do some bookkeeping once we encounter multiple SubstringRopes along the same ancestry
// chain. The top of the stack always indicates the number of bytes to extract from any descendants.
// Any bytes extracted from this SubstringRope must contribute to the total of the parent SubstringRope
// and are thus deducted. We can't simply update a total byte count, however, because we need distinct
// counts for each level.
//
// For example: SubstringRope (byteLength = 6)
// |
// ConcatRope (byteLength = 20)
// / \
// SubstringRope (byteLength = 4) LeafRope (byteLength = 16)
// |
// LeafRope (byteLength = 50)
//
// In this case we need to know that we're only extracting 4 bytes from descendants of the second
// SubstringRope. And those 4 bytes contribute to the total 6 bytes from the ancestor SubstringRope.
// The top of stack manipulation performed here maintains that invariant.

if (substringLengths.peek() > adjustedByteLength) {
final int bytesToCopy = substringLengths.pop();
substringLengths.push(bytesToCopy - adjustedByteLength);
substringLengths.push(adjustedByteLength);
}
}
} else if (current instanceof LeafRope) {
// In the absence of any SubstringRopes, we always take the full contents of the LeafRope.
if (substringLengths.isEmpty()) {
System.arraycopy(current.getRawBytes(), offset, buffer, bufferPosition, current.byteLength());
bufferPosition += current.byteLength();
} else {
int bytesToCopy = substringLengths.pop();
final int currentBytesToCopy;

// If we reach here, this LeafRope is a descendant of a SubstringRope at some level. Based on
// the currently calculated byte[] offset and the number of bytes to extract, determine how many
// bytes we can copy to the buffer.
if (bytesToCopy > (current.byteLength() - offset)) {
currentBytesToCopy = current.byteLength() - offset;
} else {
currentBytesToCopy = bytesToCopy;
}

System.arraycopy(current.getRawBytes(), offset, buffer, bufferPosition, currentBytesToCopy);
bufferPosition += currentBytesToCopy;
bytesToCopy -= currentBytesToCopy;

// If this LeafRope wasn't able to satisfy the remaining byte count from the ancestor SubstringRope,
// update the byte count for the next item in the work queue.
if (bytesToCopy > 0) {
substringLengths.push(bytesToCopy);
}
}

// By definition, offsets only affect the start of the rope. Once we've copied bytes out of a LeafRope,
// we need to reset the offset or subsequent items in the work queue will copy from the wrong location.
//
// NB: In contrast to the number of bytes to extract, the offset can be shared and updated by multiple
// levels of SubstringRopes. Thus, we do not need to maintain offsets in a stack and it is appropriate
// to clear the offset after the first time we use it, since it will have been updated accordingly at
// each SubstringRope encountered for this SubstringRope ancestry chain.
offset = 0;
} else {
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException("Don't know how to flatten rope of type: " + rope.getClass().getName());
}
}

return buffer;
}

public static int hashCodeForLeafRope(byte[] bytes, int startingHashCode, int offset, int length) {
Original file line number Diff line number Diff line change
@@ -10,8 +10,6 @@

package org.jruby.truffle.runtime.rope;

import org.jruby.RubyEncoding;

public class SubstringRope extends Rope {

private final Rope child;
@@ -29,11 +27,6 @@ public int get(int index) {
return child.get(index + offset);
}

@Override
public byte[] calculateBytes() {
return child.extractRange(offset, byteLength());
}

@Override
public byte[] extractRange(int offset, int length) {
assert length <= this.byteLength();
@@ -52,15 +45,7 @@ public int getOffset() {
@Override
public String toString() {
// This should be used for debugging only.
return RubyEncoding.decodeUTF8(child.getBytes(), offset, byteLength());
return child.toString().substring(offset, offset + byteLength());
}

@Override
protected void fillBytes(byte[] buffer, int bufferPosition, int offset, int byteLength) {
if (getRawBytes() != null) {
System.arraycopy(getRawBytes(), offset, buffer, bufferPosition, byteLength);
} else {
child.fillBytes(buffer, bufferPosition, offset + this.offset, byteLength);
}
}
}
Original file line number Diff line number Diff line change
@@ -70,6 +70,7 @@
import org.jruby.truffle.runtime.methods.Arity;
import org.jruby.truffle.runtime.methods.SharedMethodInfo;
import org.jruby.truffle.runtime.BreakID;
import org.jruby.truffle.runtime.rope.Rope;
import org.jruby.util.ByteList;
import org.jruby.util.KeyValuePair;

@@ -1761,10 +1762,7 @@ public RubyNode visitInstVarNode(org.jruby.ast.InstVarNode node) {
ret = StringNodesFactory.ByteSizeNodeFactory.create(context, sourceSection, new RubyNode[]{ self });
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@data")) {
final RubyNode bytes = StringNodesFactory.BytesNodeFactory.create(context, sourceSection, new RubyNode[]{ self });
// Wrap in a StringData instance, see shims.
LiteralNode stringDataClass = new LiteralNode(context, sourceSection, context.getCoreLibrary().getStringDataClass());
ret = new RubyCallNode(context, sourceSection, "new", stringDataClass, null, false, bytes);
ret = StringNodesFactory.DataNodeFactory.create(context, sourceSection, new RubyNode[]{ self });
return addNewlineIfNeeded(node, ret);
}
} else if (path.equals(corePath + "rubinius/common/time.rb")) {
@@ -2592,9 +2590,13 @@ public RubyNode visitRedoNode(org.jruby.ast.RedoNode node) {

@Override
public RubyNode visitRegexpNode(org.jruby.ast.RegexpNode node) {
Regex regex = RegexpNodes.compile(currentNode, context, node.getValue(), node.getOptions());
final Rope rope = StringOperations.ropeFromByteList(node.getValue());
Regex regex = RegexpNodes.compile(currentNode, context, rope, node.getOptions());

final DynamicObject regexp = RegexpNodes.createRubyRegexp(context.getCoreLibrary().getRegexpClass(), regex, node.getValue(), node.getOptions());
// The RegexpNodes.compile operation may modify the encoding of the source rope. This modified copy is stored
// in the Regex object as the "user object". Since ropes are immutable, we need to take this updated copy when
// constructing the final regexp.
final DynamicObject regexp = RegexpNodes.createRubyRegexp(context.getCoreLibrary().getRegexpClass(), regex, (Rope) regex.getUserObject(), node.getOptions());
Layouts.REGEXP.getOptions(regexp).setLiteral(true);

final LiteralNode literalNode = new LiteralNode(context, translate(node.getPosition()), regexp);
27 changes: 0 additions & 27 deletions truffle/src/main/ruby/core/shims.rb
Original file line number Diff line number Diff line change
@@ -69,34 +69,7 @@ class Rational
alias :__slash__ :/
end

# Wrapper class for Rubinius's exposure of @data within String.
#
# We can't use Array directly because we don't currently guarantee that we'll always return the same
# exact underlying byte array. Rubinius calls #equal? rather than #== throughout its code, making a tighter
# assumption than we provide. This wrapper provides the semantics we need in the interim.
module Rubinius
class StringData
attr_accessor :array

def initialize(array)
@array = array
end

def equal?(other)
@array == other.array
end

alias_method :==, :equal?

def size
@array.size
end

def [](index)
@array[index]
end
end

class Mirror
module Process
def self.set_status_global(status)

0 comments on commit 6ad0311

Please sign in to comment.