Skip to content

Commit be112ed

Browse files
author
Nicolas Laurent
committed
release memory for children ropes when a concat rope becomes flattened
1 parent 9be9a1f commit be112ed

File tree

5 files changed

+190
-86
lines changed

5 files changed

+190
-86
lines changed

src/main/java/org/truffleruby/core/rope/ConcatRope.java

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,68 +9,116 @@
99
*/
1010
package org.truffleruby.core.rope;
1111

12+
import com.oracle.truffle.api.CompilerDirectives;
13+
import com.oracle.truffle.api.CompilerDirectives.ValueType;
1214
import org.jcodings.Encoding;
1315
import org.jcodings.specific.ASCIIEncoding;
1416

1517
public class ConcatRope extends ManagedRope {
1618

17-
private final ManagedRope left;
18-
private final ManagedRope right;
19+
@ValueType
20+
public static class ConcatChildren {
21+
public final ManagedRope left, right;
22+
23+
public ConcatChildren(ManagedRope left, ManagedRope right) {
24+
this.left = left;
25+
this.right = right;
26+
}
27+
}
28+
29+
private ManagedRope left;
30+
private ManagedRope right;
1931

2032
public ConcatRope(
2133
ManagedRope left,
2234
ManagedRope right,
2335
Encoding encoding,
2436
CodeRange codeRange) {
25-
this(left, right, encoding, codeRange, left.characterLength() + right.characterLength(), null);
37+
this(
38+
left,
39+
right,
40+
encoding,
41+
codeRange,
42+
left.byteLength() + right.byteLength(),
43+
left.characterLength() + right.characterLength(),
44+
null);
2645
}
2746

2847
private ConcatRope(
2948
ManagedRope left,
3049
ManagedRope right,
3150
Encoding encoding,
3251
CodeRange codeRange,
52+
int byteLength,
3353
int characterLength,
3454
byte[] bytes) {
35-
super(
36-
encoding,
37-
codeRange,
38-
left.byteLength() + right.byteLength(),
39-
characterLength,
40-
bytes);
55+
super(encoding, codeRange, byteLength, characterLength, bytes);
4156
this.left = left;
4257
this.right = right;
4358
}
4459

4560
@Override
4661
Rope withEncoding7bit(Encoding newEncoding) {
4762
assert getCodeRange() == CodeRange.CR_7BIT;
48-
return new ConcatRope(
49-
getLeft(),
50-
getRight(),
51-
newEncoding,
52-
CodeRange.CR_7BIT,
53-
characterLength(),
54-
getRawBytes());
63+
return withEncoding(newEncoding, CodeRange.CR_7BIT, characterLength());
5564
}
5665

5766
@Override
5867
Rope withBinaryEncoding() {
5968
assert getCodeRange() == CodeRange.CR_VALID;
60-
return new ConcatRope(
61-
getLeft(),
62-
getRight(),
63-
ASCIIEncoding.INSTANCE,
64-
CodeRange.CR_VALID,
65-
byteLength(),
66-
getRawBytes());
69+
return withEncoding(ASCIIEncoding.INSTANCE, CodeRange.CR_VALID, byteLength());
70+
}
71+
72+
private ConcatRope withEncoding(Encoding encoding, CodeRange codeRange, int characterLength) {
73+
final ConcatChildren children = getChildrenOrNull();
74+
final ManagedRope left, right;
75+
final byte[] bytes;
76+
if (children != null) {
77+
left = children.left;
78+
right = children.right;
79+
bytes = null;
80+
} else {
81+
left = null;
82+
right = null;
83+
bytes = getRawBytes();
84+
}
85+
return new ConcatRope(left, right, encoding, codeRange, byteLength(), characterLength, bytes);
86+
}
87+
88+
@Override
89+
protected byte[] getBytesSlow() {
90+
final byte[] out = RopeOperations.flattenBytes(this);
91+
this.left = null;
92+
this.right = null;
93+
return out;
6794
}
6895

69-
public ManagedRope getLeft() {
70-
return left;
96+
public ConcatChildren getChildrenOrNull() {
97+
final ManagedRope left = this.left;
98+
final ManagedRope right = this.right;
99+
100+
if (left != null && right != null) {
101+
return new ConcatChildren(left, right);
102+
} else if (this.bytes != null) {
103+
return null;
104+
} else {
105+
CompilerDirectives.transferToInterpreterAndInvalidate();
106+
assert this.bytes != null;
107+
return null;
108+
}
71109
}
72110

73-
public ManagedRope getRight() {
74-
return right;
111+
public byte[] getBytesOrNull() {
112+
final byte[] bytes = this.bytes;
113+
114+
if (bytes != null) {
115+
return bytes;
116+
} else if (this.left != null && this.right != null) {
117+
return null;
118+
} else {
119+
CompilerDirectives.transferToInterpreterAndInvalidate();
120+
assert this.bytes != null;
121+
return this.bytes;
122+
}
75123
}
76124
}

src/main/java/org/truffleruby/core/rope/RopeNodes.java

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121

2222
import java.util.Arrays;
2323

24+
import com.oracle.truffle.api.dsl.Bind;
2425
import org.jcodings.Encoding;
2526
import org.jcodings.specific.ASCIIEncoding;
2627
import org.jcodings.specific.USASCIIEncoding;
2728
import org.jcodings.specific.UTF8Encoding;
2829
import org.truffleruby.SuppressFBWarnings;
2930
import org.truffleruby.core.array.ArrayUtils;
3031
import org.truffleruby.core.encoding.EncodingNodes;
32+
import org.truffleruby.core.rope.ConcatRope.ConcatChildren;
3133
import org.truffleruby.core.rope.RopeNodesFactory.AreComparableRopesNodeGen;
3234
import org.truffleruby.core.rope.RopeNodesFactory.CompareRopesNodeGen;
3335
import org.truffleruby.core.rope.RopeNodesFactory.SetByteNodeGen;
@@ -804,26 +806,46 @@ protected Object debugPrintSubstringRope(SubstringRope rope, int currentLevel, b
804806
}
805807

806808
@TruffleBoundary
807-
@Specialization
808-
protected Object debugPrintConcatRope(ConcatRope rope, int currentLevel, boolean printString) {
809+
@Specialization(guards = "bytes != null")
810+
protected Object debugPrintConcatRope(ConcatRope rope, int currentLevel, boolean printString,
811+
@Bind("rope.getBytesOrNull()") byte[] bytes) {
812+
printPreamble(currentLevel);
813+
814+
System.err
815+
.println(StringUtils.format(
816+
"%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; E: %s)",
817+
printString ? rope.toString() : "<skipped>",
818+
rope.getClass().getSimpleName(),
819+
false,
820+
rope.byteLength(),
821+
rope.characterLength(),
822+
rope.getCodeRange(),
823+
rope.getEncoding()));
824+
825+
return nil;
826+
}
827+
828+
@TruffleBoundary
829+
@Specialization(guards = "children != null")
830+
protected Object debugPrintConcatRope(ConcatRope rope, int currentLevel, boolean printString,
831+
@Bind("rope.getChildrenOrNull()") ConcatChildren children) {
809832
printPreamble(currentLevel);
810833

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

814835
System.err
815836
.println(StringUtils.format(
816837
"%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; E: %s)",
817838
printString ? rope.toString() : "<skipped>",
818839
rope.getClass().getSimpleName(),
819-
bytesAreNull,
840+
// Note: converting a rope to a java.lang.String may populate the byte[].
841+
true, // are bytes null?
820842
rope.byteLength(),
821843
rope.characterLength(),
822844
rope.getCodeRange(),
823845
rope.getEncoding()));
824846

825-
executeDebugPrint(rope.getLeft(), currentLevel + 1, printString);
826-
executeDebugPrint(rope.getRight(), currentLevel + 1, printString);
847+
executeDebugPrint(children.left, currentLevel + 1, printString);
848+
executeDebugPrint(children.right, currentLevel + 1, printString);
827849

828850
return nil;
829851
}
@@ -996,28 +1018,36 @@ protected int getByteRepeatingRope(RepeatingRope rope, int index,
9961018
return rope.getChild().getRawBytes()[index % rope.getChild().byteLength()] & 0xff;
9971019
}
9981020

999-
@Specialization(guards = "rope.getRawBytes() == null")
1021+
@Specialization(guards = "children != null")
10001022
protected int getByteConcatRope(ConcatRope rope, int index,
1023+
@Bind("rope.getChildrenOrNull()") ConcatChildren children,
10011024
@Cached ConditionProfile chooseLeftChildProfile,
10021025
@Cached ConditionProfile leftChildRawBytesNullProfile,
10031026
@Cached ConditionProfile rightChildRawBytesNullProfile,
10041027
@Cached ByteSlowNode byteSlowLeft,
10051028
@Cached ByteSlowNode byteSlowRight) {
1006-
if (chooseLeftChildProfile.profile(index < rope.getLeft().byteLength())) {
1007-
if (leftChildRawBytesNullProfile.profile(rope.getLeft().getRawBytes() == null)) {
1008-
return byteSlowLeft.execute(rope.getLeft(), index) & 0xff;
1029+
if (chooseLeftChildProfile.profile(index < children.left.byteLength())) {
1030+
if (leftChildRawBytesNullProfile.profile(children.left.getRawBytes() == null)) {
1031+
return byteSlowLeft.execute(children.left, index) & 0xff;
10091032
}
10101033

1011-
return rope.getLeft().getRawBytes()[index] & 0xff;
1034+
return children.left.getRawBytes()[index] & 0xff;
10121035
}
10131036

1014-
if (rightChildRawBytesNullProfile.profile(rope.getRight().getRawBytes() == null)) {
1015-
return byteSlowRight.execute(rope.getRight(), index - rope.getLeft().byteLength()) & 0xff;
1037+
if (rightChildRawBytesNullProfile.profile(children.right.getRawBytes() == null)) {
1038+
return byteSlowRight.execute(children.right, index - children.left.byteLength()) & 0xff;
10161039
}
10171040

1018-
return rope.getRight().getRawBytes()[index - rope.getLeft().byteLength()] & 0xff;
1041+
return children.right.getRawBytes()[index - children.left.byteLength()] & 0xff;
10191042
}
10201043

1044+
// Necessary because getRawBytes() might return null, but then be populated and the children nulled
1045+
// before we get to run the other getByteConcatRope.
1046+
@Specialization(guards = "bytes != null")
1047+
protected int getByteConcatRope(ConcatRope rope, int index,
1048+
@Bind("rope.getBytesOrNull()") byte[] bytes) {
1049+
return bytes[index] & 0xff;
1050+
}
10211051
}
10221052

10231053
public abstract static class SetByteNode extends RubyContextNode {

src/main/java/org/truffleruby/core/rope/RopeOperations.java

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.truffleruby.collections.IntStack;
4040
import org.truffleruby.core.Hashing;
4141
import org.truffleruby.core.encoding.EncodingManager;
42+
import org.truffleruby.core.rope.ConcatRope.ConcatChildren;
4243
import org.truffleruby.core.rope.RopeNodesFactory.WithEncodingNodeGen;
4344
import org.truffleruby.core.string.StringAttributes;
4445
import org.truffleruby.core.string.StringOperations;
@@ -384,28 +385,36 @@ public static byte[] flattenBytes(Rope rope) {
384385
if (current instanceof ConcatRope) {
385386
final ConcatRope concatRope = (ConcatRope) current;
386387

388+
final ConcatChildren children = concatRope.getChildrenOrNull();
389+
if (children == null) {
390+
// The rope got concurrently flattened between entering the iteration and reaching here,
391+
// restart the iteration from the top.
392+
workStack.push(concatRope);
393+
continue;
394+
}
395+
387396
// In the absence of any SubstringRopes, we always take the full contents of the ConcatRope.
388397
if (substringLengths.isEmpty()) {
389-
workStack.push(concatRope.getRight());
390-
workStack.push(concatRope.getLeft());
398+
workStack.push(children.right);
399+
workStack.push(children.left);
391400
} else {
392-
final int leftLength = concatRope.getLeft().byteLength();
401+
final int leftLength = children.left.byteLength();
393402

394403
// If we reach here, this ConcatRope is a descendant of a SubstringRope at some level. Based on
395404
// the currently calculated byte[] offset and the number of bytes to extract, determine which of
396405
// the ConcatRope's children we need to visit.
397406
if (byteOffset < leftLength) {
398407
if ((byteOffset + substringLengths.peek()) > leftLength) {
399-
workStack.push(concatRope.getRight());
400-
workStack.push(concatRope.getLeft());
408+
workStack.push(children.right);
409+
workStack.push(children.left);
401410
} else {
402-
workStack.push(concatRope.getLeft());
411+
workStack.push(children.left);
403412
}
404413
} else {
405414
// If we can skip the left child entirely, we need to update the offset so it's accurate for
406415
// the right child as each child's starting point is 0.
407416
byteOffset -= leftLength;
408-
workStack.push(concatRope.getRight());
417+
workStack.push(children.right);
409418
}
410419
}
411420
} else if (current instanceof SubstringRope) {
@@ -505,12 +514,16 @@ static byte getByteSlow(Rope rope, int index) {
505514
return rawBytes[index];
506515
} else if (rope instanceof ConcatRope) {
507516
final ConcatRope concatRope = (ConcatRope) rope;
508-
final Rope left = concatRope.getLeft();
509-
if (index < left.byteLength()) {
510-
rope = left;
517+
final ConcatChildren children = concatRope.getChildrenOrNull();
518+
if (children == null) {
519+
// Rope got concurrently flattened.
520+
return concatRope.getRawBytes()[index];
521+
}
522+
if (index < children.left.byteLength()) {
523+
rope = children.left;
511524
} else {
512-
rope = concatRope.getRight();
513-
index -= left.byteLength();
525+
rope = children.right;
526+
index -= children.left.byteLength();
514527
}
515528
} else if (rope instanceof SubstringRope) {
516529
final SubstringRope substringRope = (SubstringRope) rope;
@@ -573,21 +586,27 @@ class Params {
573586
workStack.push(new Params(child, startingHashCode, newOffset, length, false));
574587
} else if (rope instanceof ConcatRope) {
575588
final ConcatRope concatRope = (ConcatRope) rope;
576-
final Rope left = concatRope.getLeft();
577-
final Rope right = concatRope.getRight();
578-
final int leftLength = left.byteLength();
579-
580-
if (offset >= leftLength) {
581-
// range fully contained in right child
582-
workStack.push(new Params(right, startingHashCode, offset - leftLength, length, false));
583-
} else if (offset + length <= leftLength) {
584-
// range fully contained in left child
585-
workStack.push(new Params(left, startingHashCode, offset, length, false));
589+
final ConcatChildren children = concatRope.getChildrenOrNull();
590+
if (children == null) {
591+
// Rope got concurrently flattened.
592+
resultHash = Hashing.stringHash(bytes, startingHashCode, offset, length);
586593
} else {
587-
final int coveredByLeft = leftLength - offset;
588-
// push right node first, starting hash is the result from the left node
589-
workStack.push(new Params(right, 0, 0, length - coveredByLeft, true));
590-
workStack.push(new Params(left, startingHashCode, offset, coveredByLeft, false));
594+
final Rope left = children.left;
595+
final Rope right = children.right;
596+
final int leftLength = left.byteLength();
597+
598+
if (offset >= leftLength) {
599+
// range fully contained in right child
600+
workStack.push(new Params(right, startingHashCode, offset - leftLength, length, false));
601+
} else if (offset + length <= leftLength) {
602+
// range fully contained in left child
603+
workStack.push(new Params(left, startingHashCode, offset, length, false));
604+
} else {
605+
final int coveredByLeft = leftLength - offset;
606+
// push right node first, starting hash is the result from the left node
607+
workStack.push(new Params(right, 0, 0, length - coveredByLeft, true));
608+
workStack.push(new Params(left, startingHashCode, offset, coveredByLeft, false));
609+
}
591610
}
592611
} else if (rope instanceof RepeatingRope) {
593612
final RepeatingRope repeatingRope = (RepeatingRope) rope;
@@ -668,14 +687,11 @@ public static boolean anyChildContains(Rope rope, String value) {
668687
if (rope instanceof SubstringRope) {
669688
return anyChildContains(((SubstringRope) rope).getChild(), value);
670689
} else if (rope instanceof ConcatRope) {
671-
return anyChildContains(((ConcatRope) rope).getLeft(), value) ||
672-
anyChildContains(((ConcatRope) rope).getRight(), value);
673-
} else {
674-
if (rope.byteLength() < value.length()) {
675-
return false;
690+
ConcatChildren children = ((ConcatRope) rope).getChildrenOrNull();
691+
if (children != null) {
692+
return anyChildContains(children.left, value) || anyChildContains(children.right, value);
676693
}
677-
return RopeOperations.decodeRope(rope).contains(value);
678694
}
695+
return rope.byteLength() < value.length() && RopeOperations.decodeRope(rope).contains(value);
679696
}
680-
681697
}

0 commit comments

Comments
 (0)