Commit 439a8edc authored by cbruni's avatar cbruni Committed by Commit bot

[builtins] Add String.prototype.indexOf fast path in TF

Review-Url: https://codereview.chromium.org/2638393002
Cr-Commit-Position: refs/heads/master@{#42695}
parent 55aed782
......@@ -60,6 +60,10 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
UnicodeEncoding encoding);
void StringIndexOf(Node* receiver, Node* instance_type, Node* search_string,
Node* search_string_instance_type, Node* position,
std::function<void(Node*)> f_return);
};
void StringBuiltinsAssembler::GenerateStringEqual(ResultMode mode) {
......@@ -785,7 +789,93 @@ BUILTIN(StringPrototypeIncludes) {
return *isolate->factory()->ToBoolean(index_in_str != -1);
}
// ES6 #sec-string.prototype.indexof
void StringBuiltinsAssembler::StringIndexOf(
Node* receiver, Node* instance_type, Node* search_string,
Node* search_string_instance_type, Node* position,
std::function<void(Node*)> f_return) {
CSA_ASSERT(this, IsString(receiver));
CSA_ASSERT(this, IsString(search_string));
CSA_ASSERT(this, TaggedIsSmi(position));
Label zero_length_needle(this), call_runtime_unchecked(this),
return_minus_1(this), check_search_string(this), continue_fast_path(this);
Node* needle_length = SmiUntag(LoadStringLength(search_string));
// Use faster/complex runtime fallback for long search strings.
GotoIf(IntPtrLessThan(IntPtrConstant(1), needle_length),
&call_runtime_unchecked);
Node* string_length = SmiUntag(LoadStringLength(receiver));
Node* start_position = SmiUntag(position);
GotoIf(IntPtrEqual(IntPtrConstant(0), needle_length), &zero_length_needle);
// Check that the needle fits in the start position.
GotoUnless(IntPtrLessThanOrEqual(needle_length,
IntPtrSub(string_length, start_position)),
&return_minus_1);
// Only support one-byte strings on the fast path.
BranchIfSimpleOneByteStringInstanceType(instance_type, &check_search_string,
&call_runtime_unchecked);
Bind(&check_search_string);
BranchIfSimpleOneByteStringInstanceType(search_string_instance_type,
&continue_fast_path,
&call_runtime_unchecked);
Bind(&continue_fast_path);
{
Node* needle_byte =
ChangeInt32ToIntPtr(LoadOneByteChar(search_string, IntPtrConstant(0)));
Node* start_address = OneByteCharAddress(receiver, start_position);
Node* search_length = IntPtrSub(string_length, start_position);
// Call out to the highly optimized memchr to perform the actual byte
// search.
Node* memchr =
ExternalConstant(ExternalReference::libc_memchr_function(isolate()));
Node* result_address =
CallCFunction3(MachineType::Pointer(), MachineType::Pointer(),
MachineType::IntPtr(), MachineType::UintPtr(), memchr,
start_address, needle_byte, search_length);
GotoIf(WordEqual(result_address, IntPtrConstant(0)), &return_minus_1);
Node* result_index =
IntPtrAdd(IntPtrSub(result_address, start_address), start_position);
f_return(SmiTag(result_index));
}
Bind(&return_minus_1);
{ f_return(SmiConstant(-1)); }
Bind(&zero_length_needle);
{
Comment("0-length search_string");
f_return(SmiTag(IntPtrMin(string_length, start_position)));
}
Bind(&call_runtime_unchecked);
{
// Simplified version of the runtime call where the types of the arguments
// are already known due to type checks in this stub.
Comment("Call Runtime Unchecked");
Node* result = CallRuntime(Runtime::kStringIndexOfUnchecked, SmiConstant(0),
receiver, search_string, position);
f_return(result);
}
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
// Unchecked helper for builtins lowering.
TF_BUILTIN(StringIndexOf, StringBuiltinsAssembler) {
Node* receiver = Parameter(0);
Node* search_string = Parameter(1);
Node* position = Parameter(2);
Label call_runtime(this);
Node* instance_type = LoadInstanceType(receiver);
Node* search_string_instance_type = LoadInstanceType(search_string);
StringIndexOf(receiver, instance_type, search_string,
search_string_instance_type, position,
[this](Node* result) { this->Return(result); });
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
Variable search_string(this, MachineRepresentation::kTagged),
position(this, MachineRepresentation::kTagged);
......@@ -832,67 +922,21 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
Bind(&fast_path);
{
Comment("Fast Path");
Label zero_length_needle(this);
GotoIf(TaggedIsSmi(receiver), &call_runtime);
Node* needle = search_string.value();
GotoIf(TaggedIsSmi(needle), &call_runtime);
Node* instance_type = LoadInstanceType(receiver);
GotoUnless(IsStringInstanceType(instance_type), &call_runtime);
Node* needle_instance_type = LoadInstanceType(needle);
GotoUnless(IsStringInstanceType(needle_instance_type), &call_runtime);
// At this point we know that the receiver and the needle are Strings and
// that position is a Smi.
Node* needle_length = SmiUntag(LoadStringLength(needle));
// Use possibly faster runtime fallback for long search strings.
GotoIf(IntPtrLessThan(IntPtrConstant(1), needle_length),
&call_runtime_unchecked);
Node* string_length = SmiUntag(LoadStringLength(receiver));
Node* start_position = SmiUntag(position.value());
GotoIf(IntPtrEqual(IntPtrConstant(0), needle_length), &zero_length_needle);
// Check that the needle fits in the start position.
GotoUnless(IntPtrLessThanOrEqual(needle_length,
IntPtrSub(string_length, start_position)),
&return_minus_1);
// Only support one-byte strings on the fast path.
Label check_needle(this), continue_fast_path(this);
BranchIfSimpleOneByteStringInstanceType(instance_type, &check_needle,
&call_runtime_unchecked);
Bind(&check_needle);
BranchIfSimpleOneByteStringInstanceType(
needle_instance_type, &continue_fast_path, &call_runtime_unchecked);
Bind(&continue_fast_path);
{
Node* needle_byte =
ChangeInt32ToIntPtr(LoadOneByteChar(needle, IntPtrConstant(0)));
Node* start_address = OneByteCharAddress(receiver, start_position);
Node* search_length = IntPtrSub(string_length, start_position);
// Call out to the highly optimized memchr to perform the actual byte
// search.
Node* memchr =
ExternalConstant(ExternalReference::libc_memchr_function(isolate()));
Node* result_address =
CallCFunction3(MachineType::Pointer(), MachineType::Pointer(),
MachineType::IntPtr(), MachineType::UintPtr(), memchr,
start_address, needle_byte, search_length);
GotoIf(WordEqual(result_address, IntPtrConstant(0)), &return_minus_1);
Node* result_index =
IntPtrAdd(IntPtrSub(result_address, start_address), start_position);
arguments.PopAndReturn(SmiTag(result_index));
}
Bind(&zero_length_needle);
{
Comment("0-length needle");
arguments.PopAndReturn(SmiTag(IntPtrMin(string_length, start_position)));
}
StringIndexOf(
receiver, instance_type, needle, needle_instance_type, position.value(),
[&arguments](Node* result) { arguments.PopAndReturn(result); });
}
Bind(&return_minus_1);
{ arguments.PopAndReturn(SmiConstant(-1)); }
Bind(&call_runtime);
{
Comment("Call Runtime");
......@@ -900,17 +944,6 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
search_string.value(), position.value());
arguments.PopAndReturn(result);
}
Bind(&call_runtime_unchecked);
{
// Simplified version of the runtime call where the types of the arguments
// are already known due to type checks in this stub.
Comment("Call Runtime Unchecked");
Node* result =
CallRuntime(Runtime::kStringIndexOfUnchecked, context, receiver,
search_string.value(), position.value());
arguments.PopAndReturn(result);
}
}
// ES6 section 21.1.3.9
......
......@@ -129,14 +129,15 @@ namespace internal {
ASM(StackCheck) \
\
/* String helpers */ \
TFS(StringCharAt, BUILTIN, kNoExtraICState, StringCharAt) \
TFS(StringCharCodeAt, BUILTIN, kNoExtraICState, StringCharCodeAt) \
TFS(StringEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringNotEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringLessThan, BUILTIN, kNoExtraICState, Compare) \
TFS(StringLessThanOrEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringGreaterThan, BUILTIN, kNoExtraICState, Compare) \
TFS(StringGreaterThanOrEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringCharAt, BUILTIN, kNoExtraICState, StringCharAt) \
TFS(StringCharCodeAt, BUILTIN, kNoExtraICState, StringCharCodeAt) \
TFS(StringIndexOf, BUILTIN, kNoExtraICState, StringIndexOf) \
TFS(StringLessThan, BUILTIN, kNoExtraICState, Compare) \
TFS(StringLessThanOrEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringNotEqual, BUILTIN, kNoExtraICState, Compare) \
\
/* Interpreter */ \
ASM(InterpreterEntryTrampoline) \
......
......@@ -293,6 +293,12 @@ Callable CodeFactory::StringCompare(Isolate* isolate, Token::Value token) {
return StringEqual(isolate);
}
// static
Callable CodeFactory::StringIndexOf(Isolate* isolate) {
return Callable(isolate->builtins()->StringIndexOf(),
StringIndexOfDescriptor(isolate));
}
// static
Callable CodeFactory::SubString(Isolate* isolate) {
SubStringStub stub(isolate);
......
......@@ -127,6 +127,7 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable StringGreaterThan(Isolate* isolate);
static Callable StringGreaterThanOrEqual(Isolate* isolate);
static Callable SubString(Isolate* isolate);
static Callable StringIndexOf(Isolate* isolate);
static Callable ClassOf(Isolate* isolate);
static Callable Typeof(Isolate* isolate);
......
......@@ -5,9 +5,11 @@
#include "src/compiler/js-builtin-reducer.h"
#include "src/base/bits.h"
#include "src/code-factory.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
......@@ -1730,6 +1732,44 @@ Reduction JSBuiltinReducer::ReduceStringCharCodeAt(Node* node) {
return NoChange();
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
Reduction JSBuiltinReducer::ReduceStringIndexOf(Node* node) {
int arg_count = node->op()->ValueInputCount();
if (arg_count != 3 && arg_count != 4) return NoChange();
Node* receiver;
if (!(receiver = GetStringWitness(node))) return NoChange();
Node* search_string = NodeProperties::GetValueInput(node, 2);
if (!NodeProperties::GetType(search_string)->Is(Type::String())) {
return NoChange();
}
// Replace the current JSFunctionCall to String.prototype.indexOf with a
// simple Call to the unchecked StringIndexOf builtin.
Callable callable = CodeFactory::StringIndexOf(isolate());
const CallInterfaceDescriptor& descriptor = callable.descriptor();
CallDescriptor* desc =
Linkage::GetStubCallDescriptor(isolate(), graph()->zone(), descriptor,
descriptor.GetStackParameterCount(),
CallDescriptor::kNoFlags, Operator::kPure);
Node* stub_code = jsgraph()->HeapConstant(callable.code());
// The Call Operator doesn't require an effect nor a control input.
RelaxEffectsAndControls(node);
// Remove framestate since StringIndexOf cannot deopt.
node->RemoveInput(arg_count + 1);
// Remove the control input.
node->RemoveInput(arg_count + 2);
// Replace the JSFunction from the JSFunctionCall node with the CodeStub.
node->ReplaceInput(0, stub_code);
if (arg_count == 3) {
// Insert the missing position argument.
node->InsertInput(graph()->zone(), 3, jsgraph()->ZeroConstant());
}
const Operator* op = common()->Call(desc);
node->TrimInputCount(op->ValueInputCount());
NodeProperties::ChangeOp(node, op);
return Changed(node);
}
Reduction JSBuiltinReducer::ReduceStringIterator(Node* node) {
if (Node* receiver = GetStringWitness(node)) {
Node* effect = NodeProperties::GetEffectInput(node);
......@@ -2101,6 +2141,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
return ReduceStringCharAt(node);
case kStringCharCodeAt:
return ReduceStringCharCodeAt(node);
case kStringIndexOf:
return ReduceStringIndexOf(node);
case kStringIterator:
return ReduceStringIterator(node);
case kStringIteratorNext:
......
......@@ -103,6 +103,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
Reduction ReduceStringCharAt(Node* node);
Reduction ReduceStringCharCodeAt(Node* node);
Reduction ReduceStringFromCharCode(Node* node);
Reduction ReduceStringIndexOf(Node* node);
Reduction ReduceStringIterator(Node* node);
Reduction ReduceStringIteratorNext(Node* node);
Reduction ReduceArrayBufferViewAccessor(Node* node,
......
......@@ -78,6 +78,7 @@ class PlatformInterfaceDescriptor;
V(StringCharAt) \
V(StringCharCodeAt) \
V(StringCompare) \
V(StringIndexOf) \
V(SubString) \
V(Keyed) \
V(Named) \
......@@ -752,6 +753,13 @@ class SubStringDescriptor : public CallInterfaceDescriptor {
CallInterfaceDescriptor)
};
class StringIndexOfDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kReceiver, kSearchString, kPosition)
DECLARE_DEFAULT_DESCRIPTOR(StringIndexOfDescriptor, CallInterfaceDescriptor,
kParameterCount)
};
// TODO(ishell): not used, remove.
class KeyedDescriptor : public CallInterfaceDescriptor {
public:
......
......@@ -25,6 +25,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
var s = "test test test";
assertEquals(0, s.indexOf("t"));
......@@ -205,3 +207,79 @@ for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
assertEquals(2, "aba".indexOf("a", "2.00000"));
assertEquals(-1, "aba".indexOf("a", "3.00000"));
})();
(function optimize() {
function f() {
return 'abc'.indexOf('a');
}
assertEquals(0, f());
assertEquals(0, f());
assertEquals(0, f());
% OptimizeFunctionOnNextCall(f);
assertEquals(0, f());
function f2() {
return 'abc'.indexOf('a', 1);
}
assertEquals(-1, f2());
assertEquals(-1, f2());
assertEquals(-1, f2());
% OptimizeFunctionOnNextCall(f2);
assertEquals(-1, f2());
function f3() {
return 'abc'.indexOf('a');
}
assertEquals(0, f3());
assertEquals(0, f3());
assertEquals(0, f3());
% OptimizeFunctionOnNextCall(f3);
assertEquals(0, f3());
function f4() {
return 'abcbc'.indexOf('bc', 2);
}
assertEquals(3, f4());
assertEquals(3, f4());
assertEquals(3, f4());
% OptimizeFunctionOnNextCall(f4);
assertEquals(3, f4());
})();
(function optimizeOSR() {
function f() {
var result;
for (var i = 0; i < 100000; i++) {
result = 'abc'.indexOf('a');
}
return result;
}
assertEquals(0, f());
function f2() {
var result;
for (var i = 0; i < 100000; i++) {
result = 'abc'.indexOf('a', 1);
}
return result;
}
assertEquals(-1, f2());
function f3() {
var result;
for (var i = 0; i < 100000; i++) {
result = 'abc'.indexOf('a');
}
return result;
}
assertEquals(0, f3());
function f4() {
var result;
for (var i = 0; i < 100000; i++) {
result = 'abcbc'.indexOf('bc', 2);
}
return result;
}
assertEquals(3, f4());
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment