From 56e55bac1e8c4cb91de0a860745695be5ad1b9db Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Wed, 16 Jun 2021 15:12:24 -0400 Subject: [PATCH] Replace boundary on `StringByteIndexPrimitiveNode` with faster specializations for boundary checks and single-byte-optimizable strings. --- .../org/truffleruby/core/cast/ToRopeNode.java | 45 +++++++++ .../truffleruby/core/string/StringNodes.java | 98 ++++++++++++++----- 2 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/truffleruby/core/cast/ToRopeNode.java diff --git a/src/main/java/org/truffleruby/core/cast/ToRopeNode.java b/src/main/java/org/truffleruby/core/cast/ToRopeNode.java new file mode 100644 index 000000000000..54a34a1869fe --- /dev/null +++ b/src/main/java/org/truffleruby/core/cast/ToRopeNode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. This + * code is released under a tri EPL/GPL/LGPL license. You can use it, + * redistribute it and/or modify it under the terms of the: + * + * Eclipse Public License version 2.0, or + * GNU General Public License version 2, or + * GNU Lesser General Public License version 2.1. + */ + +package org.truffleruby.core.cast; + +import com.oracle.truffle.api.dsl.NodeChild; +import com.oracle.truffle.api.dsl.Specialization; +import org.truffleruby.core.rope.Rope; +import org.truffleruby.core.string.ImmutableRubyString; +import org.truffleruby.core.string.RubyString; +import org.truffleruby.core.symbol.RubySymbol; +import org.truffleruby.language.RubyContextSourceNode; +import org.truffleruby.language.RubyNode; + +@NodeChild(value = "child", type = RubyNode.class) +public abstract class ToRopeNode extends RubyContextSourceNode { + + public abstract Rope executeToRope(Object object); + + public static ToRopeNode create() { + return ToRopeNodeGen.create(null); + } + + @Specialization + protected Rope coerceRubyString(RubyString string) { + return string.rope; + } + + @Specialization + protected Rope coerceImmutableRubyString(ImmutableRubyString string) { + return string.rope; + } + + @Specialization + protected Rope coerceSymbol(RubySymbol symbol) { + return symbol.getRope(); + } +} diff --git a/src/main/java/org/truffleruby/core/string/StringNodes.java b/src/main/java/org/truffleruby/core/string/StringNodes.java index 719a6a9ae032..160d8ea5b79e 100644 --- a/src/main/java/org/truffleruby/core/string/StringNodes.java +++ b/src/main/java/org/truffleruby/core/string/StringNodes.java @@ -100,6 +100,7 @@ import org.truffleruby.core.cast.BooleanCastNode; import org.truffleruby.core.cast.ToIntNode; import org.truffleruby.core.cast.ToLongNode; +import org.truffleruby.core.cast.ToRopeNodeGen; import org.truffleruby.core.cast.ToStrNode; import org.truffleruby.core.cast.ToStrNodeGen; import org.truffleruby.core.encoding.EncodingLeftCharHeadNode; @@ -4398,7 +4399,6 @@ protected int notValidUtf8(Object string, int byteIndex, @Primitive(name = "string_character_index", lowerFixnum = 2) public abstract static class StringCharacterIndexPrimitiveNode extends PrimitiveArrayArgumentsNode { - @TruffleBoundary @Specialization protected Object stringCharacterIndex(Object string, Object pattern, int offset, @CachedLibrary(limit = "2") RubyStringLibrary stringLibrary, @@ -4461,42 +4461,86 @@ protected Object stringCharacterIndex(Object string, Object pattern, int offset, } @Primitive(name = "string_byte_index", lowerFixnum = 2) - public abstract static class StringByteIndexPrimitiveNode extends PrimitiveArrayArgumentsNode { + @NodeChild(value = "string", type = RubyNode.class) + @NodeChild(value = "pattern", type = RubyNode.class) + @NodeChild(value = "offset", type = RubyNode.class) + public abstract static class StringByteIndexPrimitiveNode extends PrimitiveNode { - @TruffleBoundary - @Specialization - protected Object stringCharacterIndex(Object string, Object pattern, int offset, - @Cached RopeNodes.CalculateCharacterLengthNode calculateCharacterLengthNode, - @CachedLibrary(limit = "2") RubyStringLibrary libString, - @CachedLibrary(limit = "2") RubyStringLibrary libPattern) { - if (offset < 0) { - return nil; - } + @Child SingleByteOptimizableNode singleByteOptimizableNode = SingleByteOptimizableNode.create(); - final Rope stringRope = libString.getRope(string); - final Rope patternRope = libPattern.getRope(pattern); + @CreateCast("string") + protected RubyNode coerceStringToRope(RubyNode string) { + return ToRopeNodeGen.create(string); + } - final int total = stringRope.byteLength(); - int p = 0; - final int e = p + total; + @CreateCast("pattern") + protected RubyNode coercePatternToRope(RubyNode pattern) { + return ToRopeNodeGen.create(pattern); + } + + @Specialization(guards = "offset < 0") + protected Object stringByteIndexNegativeOffset(Rope stringRope, Rope patternRope, int offset) { + return nil; + } + + @Specialization( + guards = { + "offset >= 0", + "singleByteOptimizableNode.execute(stringRope)", + "patternRope.byteLength() > stringRope.byteLength()" }) + protected Object stringByteIndexPatternTooLarge(Rope stringRope, Rope patternRope, int offset) { + return nil; + } + + @Specialization( + guards = { + "offset >= 0", + "singleByteOptimizableNode.execute(stringRope)", + "patternRope.byteLength() <= stringRope.byteLength()" }) + protected Object stringCharacterIndexSingleByteOptimizable(Rope stringRope, Rope patternRope, int offset, + @Cached BranchProfile matchProfile, + @Cached BranchProfile noMatchProfile, + @Cached RopeNodes.BytesNode stringBytesNode, + @Cached RopeNodes.BytesNode patternBytesNode) { + + int p = offset; + final int e = stringRope.byteLength(); final int pe = patternRope.byteLength(); final int l = e - pe + 1; - final byte[] stringBytes = stringRope.getBytes(); - final byte[] patternBytes = patternRope.getBytes(); - - p += offset; + final byte[] stringBytes = stringBytesNode.execute(stringRope); + final byte[] patternBytes = patternBytesNode.execute(patternRope); - if (stringRope.isSingleByteOptimizable()) { - for (; p < l; p++) { - if (ArrayUtils.memcmp(stringBytes, p, patternBytes, 0, pe) == 0) { - return p; - } + for (; p < l; p++) { + if (ArrayUtils.memcmp(stringBytes, p, patternBytes, 0, pe) == 0) { + matchProfile.enter(); + return p; } - - return nil; } + noMatchProfile.enter(); + return nil; + } + + @TruffleBoundary + @Specialization( + guards = { + "offset >= 0", + "!singleByteOptimizableNode.execute(stringRope)", + "patternRope.byteLength() <= stringRope.byteLength()" }) + protected Object stringCharacterIndex(Rope stringRope, Rope patternRope, int offset, + @Cached RopeNodes.CalculateCharacterLengthNode calculateCharacterLengthNode, + @Cached RopeNodes.BytesNode stringBytesNode, + @Cached RopeNodes.BytesNode patternBytesNode) { + + int p = offset; + final int e = stringRope.byteLength(); + final int pe = patternRope.byteLength(); + final int l = e - pe + 1; + + final byte[] stringBytes = stringBytesNode.execute(stringRope); + final byte[] patternBytes = patternBytesNode.execute(patternRope); + final Encoding enc = stringRope.getEncoding(); final CodeRange cr = stringRope.getCodeRange(); int c = 0;