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 { ...@@ -60,6 +60,10 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index, Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
UnicodeEncoding encoding); 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) { void StringBuiltinsAssembler::GenerateStringEqual(ResultMode mode) {
...@@ -785,7 +789,93 @@ BUILTIN(StringPrototypeIncludes) { ...@@ -785,7 +789,93 @@ BUILTIN(StringPrototypeIncludes) {
return *isolate->factory()->ToBoolean(index_in_str != -1); 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) { TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
Variable search_string(this, MachineRepresentation::kTagged), Variable search_string(this, MachineRepresentation::kTagged),
position(this, MachineRepresentation::kTagged); position(this, MachineRepresentation::kTagged);
...@@ -832,67 +922,21 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) { ...@@ -832,67 +922,21 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
Bind(&fast_path); Bind(&fast_path);
{ {
Comment("Fast Path"); Comment("Fast Path");
Label zero_length_needle(this);
GotoIf(TaggedIsSmi(receiver), &call_runtime); GotoIf(TaggedIsSmi(receiver), &call_runtime);
Node* needle = search_string.value(); Node* needle = search_string.value();
GotoIf(TaggedIsSmi(needle), &call_runtime); GotoIf(TaggedIsSmi(needle), &call_runtime);
Node* instance_type = LoadInstanceType(receiver); Node* instance_type = LoadInstanceType(receiver);
GotoUnless(IsStringInstanceType(instance_type), &call_runtime); GotoUnless(IsStringInstanceType(instance_type), &call_runtime);
Node* needle_instance_type = LoadInstanceType(needle); Node* needle_instance_type = LoadInstanceType(needle);
GotoUnless(IsStringInstanceType(needle_instance_type), &call_runtime); GotoUnless(IsStringInstanceType(needle_instance_type), &call_runtime);
// At this point we know that the receiver and the needle are Strings and StringIndexOf(
// that position is a Smi. receiver, instance_type, needle, needle_instance_type, position.value(),
[&arguments](Node* result) { arguments.PopAndReturn(result); });
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)));
}
} }
Bind(&return_minus_1);
{ arguments.PopAndReturn(SmiConstant(-1)); }
Bind(&call_runtime); Bind(&call_runtime);
{ {
Comment("Call Runtime"); Comment("Call Runtime");
...@@ -900,17 +944,6 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) { ...@@ -900,17 +944,6 @@ TF_BUILTIN(StringPrototypeIndexOf, StringBuiltinsAssembler) {
search_string.value(), position.value()); search_string.value(), position.value());
arguments.PopAndReturn(result); 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 // ES6 section 21.1.3.9
......
...@@ -129,14 +129,15 @@ namespace internal { ...@@ -129,14 +129,15 @@ namespace internal {
ASM(StackCheck) \ ASM(StackCheck) \
\ \
/* String helpers */ \ /* String helpers */ \
TFS(StringCharAt, BUILTIN, kNoExtraICState, StringCharAt) \
TFS(StringCharCodeAt, BUILTIN, kNoExtraICState, StringCharCodeAt) \
TFS(StringEqual, BUILTIN, kNoExtraICState, Compare) \ 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(StringGreaterThan, BUILTIN, kNoExtraICState, Compare) \
TFS(StringGreaterThanOrEqual, BUILTIN, kNoExtraICState, Compare) \ TFS(StringGreaterThanOrEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringCharAt, BUILTIN, kNoExtraICState, StringCharAt) \ TFS(StringIndexOf, BUILTIN, kNoExtraICState, StringIndexOf) \
TFS(StringCharCodeAt, BUILTIN, kNoExtraICState, StringCharCodeAt) \ TFS(StringLessThan, BUILTIN, kNoExtraICState, Compare) \
TFS(StringLessThanOrEqual, BUILTIN, kNoExtraICState, Compare) \
TFS(StringNotEqual, BUILTIN, kNoExtraICState, Compare) \
\ \
/* Interpreter */ \ /* Interpreter */ \
ASM(InterpreterEntryTrampoline) \ ASM(InterpreterEntryTrampoline) \
......
...@@ -293,6 +293,12 @@ Callable CodeFactory::StringCompare(Isolate* isolate, Token::Value token) { ...@@ -293,6 +293,12 @@ Callable CodeFactory::StringCompare(Isolate* isolate, Token::Value token) {
return StringEqual(isolate); return StringEqual(isolate);
} }
// static
Callable CodeFactory::StringIndexOf(Isolate* isolate) {
return Callable(isolate->builtins()->StringIndexOf(),
StringIndexOfDescriptor(isolate));
}
// static // static
Callable CodeFactory::SubString(Isolate* isolate) { Callable CodeFactory::SubString(Isolate* isolate) {
SubStringStub stub(isolate); SubStringStub stub(isolate);
......
...@@ -127,6 +127,7 @@ class V8_EXPORT_PRIVATE CodeFactory final { ...@@ -127,6 +127,7 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable StringGreaterThan(Isolate* isolate); static Callable StringGreaterThan(Isolate* isolate);
static Callable StringGreaterThanOrEqual(Isolate* isolate); static Callable StringGreaterThanOrEqual(Isolate* isolate);
static Callable SubString(Isolate* isolate); static Callable SubString(Isolate* isolate);
static Callable StringIndexOf(Isolate* isolate);
static Callable ClassOf(Isolate* isolate); static Callable ClassOf(Isolate* isolate);
static Callable Typeof(Isolate* isolate); static Callable Typeof(Isolate* isolate);
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
#include "src/compiler/js-builtin-reducer.h" #include "src/compiler/js-builtin-reducer.h"
#include "src/base/bits.h" #include "src/base/bits.h"
#include "src/code-factory.h"
#include "src/compilation-dependencies.h" #include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h" #include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h" #include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h" #include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h" #include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h" #include "src/compiler/simplified-operator.h"
...@@ -1730,6 +1732,44 @@ Reduction JSBuiltinReducer::ReduceStringCharCodeAt(Node* node) { ...@@ -1730,6 +1732,44 @@ Reduction JSBuiltinReducer::ReduceStringCharCodeAt(Node* node) {
return NoChange(); 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) { Reduction JSBuiltinReducer::ReduceStringIterator(Node* node) {
if (Node* receiver = GetStringWitness(node)) { if (Node* receiver = GetStringWitness(node)) {
Node* effect = NodeProperties::GetEffectInput(node); Node* effect = NodeProperties::GetEffectInput(node);
...@@ -2101,6 +2141,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) { ...@@ -2101,6 +2141,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
return ReduceStringCharAt(node); return ReduceStringCharAt(node);
case kStringCharCodeAt: case kStringCharCodeAt:
return ReduceStringCharCodeAt(node); return ReduceStringCharCodeAt(node);
case kStringIndexOf:
return ReduceStringIndexOf(node);
case kStringIterator: case kStringIterator:
return ReduceStringIterator(node); return ReduceStringIterator(node);
case kStringIteratorNext: case kStringIteratorNext:
......
...@@ -103,6 +103,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final ...@@ -103,6 +103,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
Reduction ReduceStringCharAt(Node* node); Reduction ReduceStringCharAt(Node* node);
Reduction ReduceStringCharCodeAt(Node* node); Reduction ReduceStringCharCodeAt(Node* node);
Reduction ReduceStringFromCharCode(Node* node); Reduction ReduceStringFromCharCode(Node* node);
Reduction ReduceStringIndexOf(Node* node);
Reduction ReduceStringIterator(Node* node); Reduction ReduceStringIterator(Node* node);
Reduction ReduceStringIteratorNext(Node* node); Reduction ReduceStringIteratorNext(Node* node);
Reduction ReduceArrayBufferViewAccessor(Node* node, Reduction ReduceArrayBufferViewAccessor(Node* node,
......
...@@ -78,6 +78,7 @@ class PlatformInterfaceDescriptor; ...@@ -78,6 +78,7 @@ class PlatformInterfaceDescriptor;
V(StringCharAt) \ V(StringCharAt) \
V(StringCharCodeAt) \ V(StringCharCodeAt) \
V(StringCompare) \ V(StringCompare) \
V(StringIndexOf) \
V(SubString) \ V(SubString) \
V(Keyed) \ V(Keyed) \
V(Named) \ V(Named) \
...@@ -752,6 +753,13 @@ class SubStringDescriptor : public CallInterfaceDescriptor { ...@@ -752,6 +753,13 @@ class SubStringDescriptor : public CallInterfaceDescriptor {
CallInterfaceDescriptor) CallInterfaceDescriptor)
}; };
class StringIndexOfDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kReceiver, kSearchString, kPosition)
DECLARE_DEFAULT_DESCRIPTOR(StringIndexOfDescriptor, CallInterfaceDescriptor,
kParameterCount)
};
// TODO(ishell): not used, remove. // TODO(ishell): not used, remove.
class KeyedDescriptor : public CallInterfaceDescriptor { class KeyedDescriptor : public CallInterfaceDescriptor {
public: public:
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
var s = "test test test"; var s = "test test test";
assertEquals(0, s.indexOf("t")); assertEquals(0, s.indexOf("t"));
...@@ -205,3 +207,79 @@ for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { ...@@ -205,3 +207,79 @@ for (var lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {
assertEquals(2, "aba".indexOf("a", "2.00000")); assertEquals(2, "aba".indexOf("a", "2.00000"));
assertEquals(-1, "aba".indexOf("a", "3.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