Skip to content

Commit 996966b

Browse files
committed
[GR-19220] Replace boundary on StringByteIndexPrimitiveNode with faster specializations for boundary checks and single-byte-optimizable strings (#2380)
PullRequest: truffleruby/2742
2 parents d497bae + 380b389 commit 996966b

File tree

2 files changed

+120
-24
lines changed

2 files changed

+120
-24
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
11+
package org.truffleruby.core.cast;
12+
13+
import com.oracle.truffle.api.CompilerDirectives;
14+
import com.oracle.truffle.api.dsl.Fallback;
15+
import com.oracle.truffle.api.dsl.NodeChild;
16+
import com.oracle.truffle.api.dsl.Specialization;
17+
import org.jcodings.Encoding;
18+
import org.truffleruby.core.rope.Rope;
19+
import org.truffleruby.core.string.ImmutableRubyString;
20+
import org.truffleruby.core.string.RubyString;
21+
import org.truffleruby.language.RubyContextSourceNode;
22+
import org.truffleruby.language.RubyNode;
23+
24+
@NodeChild(value = "child", type = RubyNode.class)
25+
public abstract class ToRopeNode extends RubyContextSourceNode {
26+
27+
public abstract Rope executeToRope(Object object);
28+
29+
public static ToRopeNode create() {
30+
return ToRopeNodeGen.create(null);
31+
}
32+
33+
@Specialization
34+
protected Rope coerceRubyString(RubyString string) {
35+
return string.rope;
36+
}
37+
38+
@Specialization
39+
protected Rope coerceImmutableRubyString(ImmutableRubyString string) {
40+
return string.rope;
41+
}
42+
43+
@Fallback
44+
protected Encoding failure(Object value) {
45+
throw CompilerDirectives.shouldNotReachHere();
46+
}
47+
}

src/main/java/org/truffleruby/core/string/StringNodes.java

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
import java.nio.charset.StandardCharsets;
7777

7878
import com.oracle.truffle.api.dsl.Bind;
79+
import com.oracle.truffle.api.nodes.LoopNode;
80+
import com.oracle.truffle.api.profiles.LoopConditionProfile;
7981
import org.jcodings.Config;
8082
import org.jcodings.Encoding;
8183
import org.jcodings.specific.ASCIIEncoding;
@@ -100,6 +102,7 @@
100102
import org.truffleruby.core.cast.BooleanCastNode;
101103
import org.truffleruby.core.cast.ToIntNode;
102104
import org.truffleruby.core.cast.ToLongNode;
105+
import org.truffleruby.core.cast.ToRopeNodeGen;
103106
import org.truffleruby.core.cast.ToStrNode;
104107
import org.truffleruby.core.cast.ToStrNodeGen;
105108
import org.truffleruby.core.encoding.EncodingLeftCharHeadNode;
@@ -4461,42 +4464,88 @@ protected Object stringCharacterIndex(Object string, Object pattern, int offset,
44614464
}
44624465

44634466
@Primitive(name = "string_byte_index", lowerFixnum = 2)
4464-
public abstract static class StringByteIndexPrimitiveNode extends PrimitiveArrayArgumentsNode {
4467+
@NodeChild(value = "string", type = RubyNode.class)
4468+
@NodeChild(value = "pattern", type = RubyNode.class)
4469+
@NodeChild(value = "offset", type = RubyNode.class)
4470+
public abstract static class StringByteIndexPrimitiveNode extends PrimitiveNode {
44654471

4466-
@TruffleBoundary
4467-
@Specialization
4468-
protected Object stringCharacterIndex(Object string, Object pattern, int offset,
4469-
@Cached RopeNodes.CalculateCharacterLengthNode calculateCharacterLengthNode,
4470-
@CachedLibrary(limit = "2") RubyStringLibrary libString,
4471-
@CachedLibrary(limit = "2") RubyStringLibrary libPattern) {
4472-
if (offset < 0) {
4473-
return nil;
4474-
}
4472+
@Child SingleByteOptimizableNode singleByteOptimizableNode = SingleByteOptimizableNode.create();
44754473

4476-
final Rope stringRope = libString.getRope(string);
4477-
final Rope patternRope = libPattern.getRope(pattern);
4474+
@CreateCast("string")
4475+
protected RubyNode coerceStringToRope(RubyNode string) {
4476+
return ToRopeNodeGen.create(string);
4477+
}
44784478

4479-
final int total = stringRope.byteLength();
4480-
int p = 0;
4481-
final int e = p + total;
4479+
@CreateCast("pattern")
4480+
protected RubyNode coercePatternToRope(RubyNode pattern) {
4481+
return ToRopeNodeGen.create(pattern);
4482+
}
4483+
4484+
@Specialization(guards = "offset < 0")
4485+
protected Object stringByteIndexNegativeOffset(Rope stringRope, Rope patternRope, int offset) {
4486+
return nil;
4487+
}
4488+
4489+
@Specialization(
4490+
guards = {
4491+
"offset >= 0",
4492+
"singleByteOptimizableNode.execute(stringRope)",
4493+
"patternRope.byteLength() > stringRope.byteLength()" })
4494+
protected Object stringByteIndexPatternTooLarge(Rope stringRope, Rope patternRope, int offset) {
4495+
return nil;
4496+
}
4497+
4498+
@Specialization(
4499+
guards = {
4500+
"offset >= 0",
4501+
"singleByteOptimizableNode.execute(stringRope)",
4502+
"patternRope.byteLength() <= stringRope.byteLength()" })
4503+
protected Object stringCharacterIndexSingleByteOptimizable(Rope stringRope, Rope patternRope, int offset,
4504+
@Cached RopeNodes.BytesNode stringBytesNode,
4505+
@Cached RopeNodes.BytesNode patternBytesNode,
4506+
@Cached LoopConditionProfile loopProfile,
4507+
@Cached("createCountingProfile()") ConditionProfile matchProfile) {
4508+
4509+
int p = offset;
4510+
final int e = stringRope.byteLength();
44824511
final int pe = patternRope.byteLength();
44834512
final int l = e - pe + 1;
44844513

4485-
final byte[] stringBytes = stringRope.getBytes();
4486-
final byte[] patternBytes = patternRope.getBytes();
4487-
4488-
p += offset;
4514+
final byte[] stringBytes = stringBytesNode.execute(stringRope);
4515+
final byte[] patternBytes = patternBytesNode.execute(patternRope);
44894516

4490-
if (stringRope.isSingleByteOptimizable()) {
4491-
for (; p < l; p++) {
4492-
if (ArrayUtils.memcmp(stringBytes, p, patternBytes, 0, pe) == 0) {
4517+
try {
4518+
for (; loopProfile.profile(p < l); p++) {
4519+
if (matchProfile.profile(ArrayUtils.memcmp(stringBytes, p, patternBytes, 0, pe) == 0)) {
44934520
return p;
44944521
}
44954522
}
4496-
4497-
return nil;
4523+
} finally {
4524+
LoopNode.reportLoopCount(this, p - offset);
44984525
}
44994526

4527+
return nil;
4528+
}
4529+
4530+
@TruffleBoundary
4531+
@Specialization(
4532+
guards = {
4533+
"offset >= 0",
4534+
"!singleByteOptimizableNode.execute(stringRope)",
4535+
"patternRope.byteLength() <= stringRope.byteLength()" })
4536+
protected Object stringCharacterIndex(Rope stringRope, Rope patternRope, int offset,
4537+
@Cached RopeNodes.CalculateCharacterLengthNode calculateCharacterLengthNode,
4538+
@Cached RopeNodes.BytesNode stringBytesNode,
4539+
@Cached RopeNodes.BytesNode patternBytesNode) {
4540+
4541+
int p = offset;
4542+
final int e = stringRope.byteLength();
4543+
final int pe = patternRope.byteLength();
4544+
final int l = e - pe + 1;
4545+
4546+
final byte[] stringBytes = stringBytesNode.execute(stringRope);
4547+
final byte[] patternBytes = patternBytesNode.execute(patternRope);
4548+
45004549
final Encoding enc = stringRope.getEncoding();
45014550
final CodeRange cr = stringRope.getCodeRange();
45024551
int c = 0;

0 commit comments

Comments
 (0)