[builtins] Tune Array.indexOf performance
BUG=v8:6371 Change-Id: Iacb4ad572ea83ade6262272ed30d4cb684f9d8ed Reviewed-on: https://chromium-review.googlesource.com/505107 Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#45332}
This commit is contained in:
parent
64fb9441fd
commit
1345de0b1e
1
BUILD.gn
1
BUILD.gn
@ -918,6 +918,7 @@ v8_source_set("v8_builtins_generators") {
|
||||
"src/builtins/builtins-regexp-gen.h",
|
||||
"src/builtins/builtins-sharedarraybuffer-gen.cc",
|
||||
"src/builtins/builtins-string-gen.cc",
|
||||
"src/builtins/builtins-string-gen.h",
|
||||
"src/builtins/builtins-symbol-gen.cc",
|
||||
"src/builtins/builtins-typedarray-gen.cc",
|
||||
"src/builtins/builtins-utils-gen.h",
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/builtins/builtins-string-gen.h"
|
||||
#include "src/builtins/builtins-utils-gen.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/code-stub-assembler.h"
|
||||
@ -1703,32 +1704,37 @@ TF_BUILTIN(ArrayIndexOf, CodeStubAssembler) {
|
||||
Node* array_length = SmiUntag(LoadJSArrayLength(array));
|
||||
|
||||
{
|
||||
// For now only deal with undefined and Smis here; we must be really careful
|
||||
// with side-effects from the ToInteger conversion as the side-effects might
|
||||
// render our assumptions about the receiver being a fast JSArray and the
|
||||
// length invalid.
|
||||
Label done(this);
|
||||
// Initialize fromIndex.
|
||||
Label is_smi(this), is_nonsmi(this), done(this);
|
||||
|
||||
// If no fromIndex was passed, default to 0.
|
||||
GotoIf(IntPtrLessThanOrEqual(argc, IntPtrConstant(kFromIndexArg)), &done);
|
||||
|
||||
// Handle Smis here and everything else in runtime.
|
||||
Node* start_from = args.AtIndex(kFromIndexArg);
|
||||
GotoIfNot(TaggedIsSmi(start_from), &call_runtime);
|
||||
// Handle Smis and undefined here and everything else in runtime.
|
||||
// We must be very careful with side effects from the ToInteger conversion,
|
||||
// as the side effects might render previously checked assumptions about
|
||||
// the receiver being a fast JSArray and its length invalid.
|
||||
Branch(TaggedIsSmi(start_from), &is_smi, &is_nonsmi);
|
||||
|
||||
Node* intptr_start_from = SmiUntag(start_from);
|
||||
index_var.Bind(intptr_start_from);
|
||||
|
||||
Label if_negative(this);
|
||||
Branch(IntPtrLessThan(intptr_start_from, intptr_zero), &if_negative, &done);
|
||||
|
||||
BIND(&if_negative);
|
||||
BIND(&is_nonsmi);
|
||||
{
|
||||
Node* len_minus_start_from = IntPtrAdd(array_length, intptr_start_from);
|
||||
index_var.Bind(IntPtrMax(len_minus_start_from, intptr_zero));
|
||||
GotoIfNot(IsUndefined(start_from), &call_runtime);
|
||||
Goto(&done);
|
||||
}
|
||||
BIND(&is_smi);
|
||||
{
|
||||
Node* intptr_start_from = SmiUntag(start_from);
|
||||
index_var.Bind(intptr_start_from);
|
||||
|
||||
GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done);
|
||||
// The fromIndex is negative: add it to the array's length.
|
||||
index_var.Bind(IntPtrAdd(array_length, index_var.value()));
|
||||
// Clamp negative results at zero.
|
||||
GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done);
|
||||
index_var.Bind(intptr_zero);
|
||||
Goto(&done);
|
||||
}
|
||||
BIND(&done);
|
||||
}
|
||||
|
||||
@ -1736,27 +1742,29 @@ TF_BUILTIN(ArrayIndexOf, CodeStubAssembler) {
|
||||
GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), array_length),
|
||||
&return_not_found);
|
||||
|
||||
static int32_t kElementsKind[] = {
|
||||
FAST_SMI_ELEMENTS, FAST_HOLEY_SMI_ELEMENTS, FAST_ELEMENTS,
|
||||
FAST_HOLEY_ELEMENTS, FAST_DOUBLE_ELEMENTS, FAST_HOLEY_DOUBLE_ELEMENTS,
|
||||
};
|
||||
|
||||
Label if_smiorobjects(this), if_packed_doubles(this), if_holey_doubles(this);
|
||||
Label* element_kind_handlers[] = {&if_smiorobjects, &if_smiorobjects,
|
||||
&if_smiorobjects, &if_smiorobjects,
|
||||
&if_packed_doubles, &if_holey_doubles};
|
||||
|
||||
Node* map = LoadMap(array);
|
||||
Node* elements_kind = LoadMapElementsKind(map);
|
||||
Node* elements = LoadElements(array);
|
||||
Switch(elements_kind, &return_not_found, kElementsKind, element_kind_handlers,
|
||||
arraysize(kElementsKind));
|
||||
STATIC_ASSERT(FAST_SMI_ELEMENTS == 0);
|
||||
STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1);
|
||||
STATIC_ASSERT(FAST_ELEMENTS == 2);
|
||||
STATIC_ASSERT(FAST_HOLEY_ELEMENTS == 3);
|
||||
GotoIf(
|
||||
Uint32LessThanOrEqual(elements_kind, Int32Constant(FAST_HOLEY_ELEMENTS)),
|
||||
&if_smiorobjects);
|
||||
GotoIf(Word32Equal(elements_kind, Int32Constant(FAST_DOUBLE_ELEMENTS)),
|
||||
&if_packed_doubles);
|
||||
GotoIf(Word32Equal(elements_kind, Int32Constant(FAST_HOLEY_DOUBLE_ELEMENTS)),
|
||||
&if_holey_doubles);
|
||||
Goto(&return_not_found);
|
||||
|
||||
BIND(&if_smiorobjects);
|
||||
{
|
||||
VARIABLE(search_num, MachineRepresentation::kFloat64);
|
||||
Label ident_loop(this, &index_var), heap_num_loop(this, &search_num),
|
||||
string_loop(this, &index_var), not_smi(this), not_heap_num(this);
|
||||
string_loop(this), not_smi(this), not_heap_num(this);
|
||||
|
||||
GotoIfNot(TaggedIsSmi(search_element), ¬_smi);
|
||||
search_num.Bind(SmiToFloat64(search_element));
|
||||
@ -1814,22 +1822,35 @@ TF_BUILTIN(ArrayIndexOf, CodeStubAssembler) {
|
||||
BIND(&string_loop);
|
||||
{
|
||||
CSA_ASSERT(this, IsString(search_element));
|
||||
Label continue_loop(this);
|
||||
Label continue_loop(this), next_iteration(this, &index_var),
|
||||
slow_compare(this), runtime(this, Label::kDeferred);
|
||||
Node* search_length = LoadStringLength(search_element);
|
||||
Goto(&next_iteration);
|
||||
BIND(&next_iteration);
|
||||
GotoIfNot(UintPtrLessThan(index_var.value(), array_length),
|
||||
&return_not_found);
|
||||
Node* element_k = LoadFixedArrayElement(elements, index_var.value());
|
||||
GotoIf(TaggedIsSmi(element_k), &continue_loop);
|
||||
GotoIfNot(IsString(element_k), &continue_loop);
|
||||
GotoIf(WordEqual(search_element, element_k), &return_found);
|
||||
Node* element_k_type = LoadInstanceType(element_k);
|
||||
GotoIfNot(IsStringInstanceType(element_k_type), &continue_loop);
|
||||
Branch(WordEqual(search_length, LoadStringLength(element_k)),
|
||||
&slow_compare, &continue_loop);
|
||||
|
||||
// TODO(bmeurer): Consider inlining the StringEqual logic here.
|
||||
Callable callable = CodeFactory::StringEqual(isolate());
|
||||
Node* result = CallStub(callable, context, search_element, element_k);
|
||||
BIND(&slow_compare);
|
||||
StringBuiltinsAssembler string_asm(state());
|
||||
string_asm.StringEqual_Core(context, search_element, search_type,
|
||||
search_length, element_k, element_k_type,
|
||||
&return_found, &continue_loop, &runtime);
|
||||
BIND(&runtime);
|
||||
Node* result = CallRuntime(Runtime::kStringEqual, context, search_element,
|
||||
element_k);
|
||||
Branch(WordEqual(BooleanConstant(true), result), &return_found,
|
||||
&continue_loop);
|
||||
|
||||
BIND(&continue_loop);
|
||||
Increment(index_var);
|
||||
Goto(&string_loop);
|
||||
Goto(&next_iteration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,219 +2,167 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/builtins/builtins-string-gen.h"
|
||||
|
||||
#include "src/builtins/builtins-regexp-gen.h"
|
||||
#include "src/builtins/builtins-utils-gen.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/code-factory.h"
|
||||
#include "src/code-stub-assembler.h"
|
||||
#include "src/objects.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
typedef CodeStubAssembler::RelationalComparisonMode RelationalComparisonMode;
|
||||
typedef compiler::Node Node;
|
||||
|
||||
class StringBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit StringBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
Node* StringBuiltinsAssembler::DirectStringData(Node* string,
|
||||
Node* string_instance_type) {
|
||||
// Compute the effective offset of the first character.
|
||||
VARIABLE(var_data, MachineType::PointerRepresentation());
|
||||
Label if_sequential(this), if_external(this), if_join(this);
|
||||
Branch(Word32Equal(Word32And(string_instance_type,
|
||||
Int32Constant(kStringRepresentationMask)),
|
||||
Int32Constant(kSeqStringTag)),
|
||||
&if_sequential, &if_external);
|
||||
|
||||
// ES#sec-getsubstitution
|
||||
Node* GetSubstitution(Node* context, Node* subject_string,
|
||||
Node* match_start_index, Node* match_end_index,
|
||||
Node* replace_string);
|
||||
|
||||
protected:
|
||||
Node* DirectStringData(Node* string, Node* string_instance_type) {
|
||||
// Compute the effective offset of the first character.
|
||||
VARIABLE(var_data, MachineType::PointerRepresentation());
|
||||
Label if_sequential(this), if_external(this), if_join(this);
|
||||
Branch(Word32Equal(Word32And(string_instance_type,
|
||||
Int32Constant(kStringRepresentationMask)),
|
||||
Int32Constant(kSeqStringTag)),
|
||||
&if_sequential, &if_external);
|
||||
|
||||
BIND(&if_sequential);
|
||||
{
|
||||
var_data.Bind(IntPtrAdd(
|
||||
IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag),
|
||||
BitcastTaggedToWord(string)));
|
||||
Goto(&if_join);
|
||||
}
|
||||
|
||||
BIND(&if_external);
|
||||
{
|
||||
// This is only valid for ExternalStrings where the resource data
|
||||
// pointer is cached (i.e. no short external strings).
|
||||
CSA_ASSERT(this, Word32NotEqual(
|
||||
Word32And(string_instance_type,
|
||||
Int32Constant(kShortExternalStringMask)),
|
||||
Int32Constant(kShortExternalStringTag)));
|
||||
var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset,
|
||||
MachineType::Pointer()));
|
||||
Goto(&if_join);
|
||||
}
|
||||
|
||||
BIND(&if_join);
|
||||
return var_data.value();
|
||||
BIND(&if_sequential);
|
||||
{
|
||||
var_data.Bind(IntPtrAdd(
|
||||
IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag),
|
||||
BitcastTaggedToWord(string)));
|
||||
Goto(&if_join);
|
||||
}
|
||||
|
||||
void DispatchOnStringEncodings(Node* const lhs_instance_type,
|
||||
Node* const rhs_instance_type,
|
||||
Label* if_one_one, Label* if_one_two,
|
||||
Label* if_two_one, Label* if_two_two) {
|
||||
STATIC_ASSERT(kStringEncodingMask == 0x8);
|
||||
STATIC_ASSERT(kTwoByteStringTag == 0x0);
|
||||
STATIC_ASSERT(kOneByteStringTag == 0x8);
|
||||
|
||||
// First combine the encodings.
|
||||
|
||||
Node* const encoding_mask = Int32Constant(kStringEncodingMask);
|
||||
Node* const lhs_encoding = Word32And(lhs_instance_type, encoding_mask);
|
||||
Node* const rhs_encoding = Word32And(rhs_instance_type, encoding_mask);
|
||||
|
||||
Node* const combined_encodings =
|
||||
Word32Or(lhs_encoding, Word32Shr(rhs_encoding, 1));
|
||||
|
||||
// Then dispatch on the combined encoding.
|
||||
|
||||
Label unreachable(this, Label::kDeferred);
|
||||
|
||||
int32_t values[] = {
|
||||
kOneByteStringTag | (kOneByteStringTag >> 1),
|
||||
kOneByteStringTag | (kTwoByteStringTag >> 1),
|
||||
kTwoByteStringTag | (kOneByteStringTag >> 1),
|
||||
kTwoByteStringTag | (kTwoByteStringTag >> 1),
|
||||
};
|
||||
Label* labels[] = {
|
||||
if_one_one, if_one_two, if_two_one, if_two_two,
|
||||
};
|
||||
|
||||
STATIC_ASSERT(arraysize(values) == arraysize(labels));
|
||||
Switch(combined_encodings, &unreachable, values, labels, arraysize(values));
|
||||
|
||||
BIND(&unreachable);
|
||||
Unreachable();
|
||||
BIND(&if_external);
|
||||
{
|
||||
// This is only valid for ExternalStrings where the resource data
|
||||
// pointer is cached (i.e. no short external strings).
|
||||
CSA_ASSERT(
|
||||
this, Word32NotEqual(Word32And(string_instance_type,
|
||||
Int32Constant(kShortExternalStringMask)),
|
||||
Int32Constant(kShortExternalStringTag)));
|
||||
var_data.Bind(LoadObjectField(string, ExternalString::kResourceDataOffset,
|
||||
MachineType::Pointer()));
|
||||
Goto(&if_join);
|
||||
}
|
||||
|
||||
template <typename SubjectChar, typename PatternChar>
|
||||
Node* CallSearchStringRaw(Node* const subject_ptr, Node* const subject_length,
|
||||
Node* const search_ptr, Node* const search_length,
|
||||
Node* const start_position) {
|
||||
Node* const function_addr = ExternalConstant(
|
||||
ExternalReference::search_string_raw<SubjectChar, PatternChar>(
|
||||
isolate()));
|
||||
Node* const isolate_ptr =
|
||||
ExternalConstant(ExternalReference::isolate_address(isolate()));
|
||||
BIND(&if_join);
|
||||
return var_data.value();
|
||||
}
|
||||
|
||||
MachineType type_ptr = MachineType::Pointer();
|
||||
MachineType type_intptr = MachineType::IntPtr();
|
||||
void StringBuiltinsAssembler::DispatchOnStringEncodings(
|
||||
Node* const lhs_instance_type, Node* const rhs_instance_type,
|
||||
Label* if_one_one, Label* if_one_two, Label* if_two_one,
|
||||
Label* if_two_two) {
|
||||
STATIC_ASSERT(kStringEncodingMask == 0x8);
|
||||
STATIC_ASSERT(kTwoByteStringTag == 0x0);
|
||||
STATIC_ASSERT(kOneByteStringTag == 0x8);
|
||||
|
||||
Node* const result = CallCFunction6(
|
||||
type_intptr, type_ptr, type_ptr, type_intptr, type_ptr, type_intptr,
|
||||
type_intptr, function_addr, isolate_ptr, subject_ptr, subject_length,
|
||||
search_ptr, search_length, start_position);
|
||||
// First combine the encodings.
|
||||
|
||||
return result;
|
||||
Node* const encoding_mask = Int32Constant(kStringEncodingMask);
|
||||
Node* const lhs_encoding = Word32And(lhs_instance_type, encoding_mask);
|
||||
Node* const rhs_encoding = Word32And(rhs_instance_type, encoding_mask);
|
||||
|
||||
Node* const combined_encodings =
|
||||
Word32Or(lhs_encoding, Word32Shr(rhs_encoding, 1));
|
||||
|
||||
// Then dispatch on the combined encoding.
|
||||
|
||||
Label unreachable(this, Label::kDeferred);
|
||||
|
||||
int32_t values[] = {
|
||||
kOneByteStringTag | (kOneByteStringTag >> 1),
|
||||
kOneByteStringTag | (kTwoByteStringTag >> 1),
|
||||
kTwoByteStringTag | (kOneByteStringTag >> 1),
|
||||
kTwoByteStringTag | (kTwoByteStringTag >> 1),
|
||||
};
|
||||
Label* labels[] = {
|
||||
if_one_one, if_one_two, if_two_one, if_two_two,
|
||||
};
|
||||
|
||||
STATIC_ASSERT(arraysize(values) == arraysize(labels));
|
||||
Switch(combined_encodings, &unreachable, values, labels, arraysize(values));
|
||||
|
||||
BIND(&unreachable);
|
||||
Unreachable();
|
||||
}
|
||||
|
||||
template <typename SubjectChar, typename PatternChar>
|
||||
Node* StringBuiltinsAssembler::CallSearchStringRaw(Node* const subject_ptr,
|
||||
Node* const subject_length,
|
||||
Node* const search_ptr,
|
||||
Node* const search_length,
|
||||
Node* const start_position) {
|
||||
Node* const function_addr = ExternalConstant(
|
||||
ExternalReference::search_string_raw<SubjectChar, PatternChar>(
|
||||
isolate()));
|
||||
Node* const isolate_ptr =
|
||||
ExternalConstant(ExternalReference::isolate_address(isolate()));
|
||||
|
||||
MachineType type_ptr = MachineType::Pointer();
|
||||
MachineType type_intptr = MachineType::IntPtr();
|
||||
|
||||
Node* const result = CallCFunction6(
|
||||
type_intptr, type_ptr, type_ptr, type_intptr, type_ptr, type_intptr,
|
||||
type_intptr, function_addr, isolate_ptr, subject_ptr, subject_length,
|
||||
search_ptr, search_length, start_position);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Node* StringBuiltinsAssembler::PointerToStringDataAtIndex(
|
||||
Node* const string_data, Node* const index, String::Encoding encoding) {
|
||||
const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING)
|
||||
? UINT8_ELEMENTS
|
||||
: UINT16_ELEMENTS;
|
||||
Node* const offset_in_bytes =
|
||||
ElementOffsetFromIndex(index, kind, INTPTR_PARAMETERS);
|
||||
return IntPtrAdd(string_data, offset_in_bytes);
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::ConvertAndBoundsCheckStartArgument(
|
||||
Node* context, Variable* var_start, Node* start, Node* string_length) {
|
||||
Node* const start_int =
|
||||
ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
|
||||
Node* const zero = SmiConstant(Smi::kZero);
|
||||
|
||||
Label done(this);
|
||||
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
|
||||
Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
|
||||
|
||||
BIND(&if_issmi);
|
||||
{
|
||||
var_start->Bind(
|
||||
Select(SmiLessThan(start_int, zero),
|
||||
[&] { return SmiMax(SmiAdd(string_length, start_int), zero); },
|
||||
[&] { return start_int; }, MachineRepresentation::kTagged));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
Node* PointerToStringDataAtIndex(Node* const string_data, Node* const index,
|
||||
String::Encoding encoding) {
|
||||
const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING)
|
||||
? UINT8_ELEMENTS
|
||||
: UINT16_ELEMENTS;
|
||||
|
||||
Node* const offset_in_bytes =
|
||||
ElementOffsetFromIndex(index, kind, INTPTR_PARAMETERS);
|
||||
return IntPtrAdd(string_data, offset_in_bytes);
|
||||
BIND(&if_isheapnumber);
|
||||
{
|
||||
// If {start} is a heap number, it is definitely out of bounds. If it is
|
||||
// negative, {start} = max({string_length} + {start}),0) = 0'. If it is
|
||||
// positive, set {start} to {string_length} which ultimately results in
|
||||
// returning an empty string.
|
||||
Node* const float_zero = Float64Constant(0.);
|
||||
Node* const start_float = LoadHeapNumberValue(start_int);
|
||||
var_start->Bind(SelectTaggedConstant(
|
||||
Float64LessThan(start_float, float_zero), zero, string_length));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
void GenerateStringEqual(Node* context, Node* left, Node* right);
|
||||
void GenerateStringRelationalComparison(Node* context, Node* left,
|
||||
Node* right,
|
||||
RelationalComparisonMode mode);
|
||||
|
||||
Node* ToSmiBetweenZeroAnd(Node* context, Node* value, Node* limit);
|
||||
|
||||
Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
|
||||
UnicodeEncoding encoding);
|
||||
|
||||
void StringIndexOf(Node* const subject_string,
|
||||
Node* const subject_instance_type,
|
||||
Node* const search_string,
|
||||
Node* const search_instance_type, Node* const position,
|
||||
std::function<void(Node*)> f_return);
|
||||
|
||||
Node* IndexOfDollarChar(Node* const context, Node* const string);
|
||||
|
||||
Node* IsNullOrUndefined(Node* const value);
|
||||
void RequireObjectCoercible(Node* const context, Node* const value,
|
||||
const char* method_name);
|
||||
|
||||
Node* SmiIsNegative(Node* const value) {
|
||||
return SmiLessThan(value, SmiConstant(0));
|
||||
}
|
||||
|
||||
// substr and slice have a common way of handling the {start} argument.
|
||||
void ConvertAndBoundsCheckStartArgument(Node* context, Variable* var_start,
|
||||
Node* start, Node* string_length) {
|
||||
Node* const start_int =
|
||||
ToInteger(context, start, CodeStubAssembler::kTruncateMinusZero);
|
||||
Node* const zero = SmiConstant(Smi::kZero);
|
||||
|
||||
Label done(this);
|
||||
Label if_issmi(this), if_isheapnumber(this, Label::kDeferred);
|
||||
Branch(TaggedIsSmi(start_int), &if_issmi, &if_isheapnumber);
|
||||
|
||||
BIND(&if_issmi);
|
||||
{
|
||||
var_start->Bind(
|
||||
Select(SmiLessThan(start_int, zero),
|
||||
[&] { return SmiMax(SmiAdd(string_length, start_int), zero); },
|
||||
[&] { return start_int; }, MachineRepresentation::kTagged));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&if_isheapnumber);
|
||||
{
|
||||
// If {start} is a heap number, it is definitely out of bounds. If it is
|
||||
// negative, {start} = max({string_length} + {start}),0) = 0'. If it is
|
||||
// positive, set {start} to {string_length} which ultimately results in
|
||||
// returning an empty string.
|
||||
Node* const float_zero = Float64Constant(0.);
|
||||
Node* const start_float = LoadHeapNumberValue(start_int);
|
||||
var_start->Bind(SelectTaggedConstant(
|
||||
Float64LessThan(start_float, float_zero), zero, string_length));
|
||||
Goto(&done);
|
||||
}
|
||||
BIND(&done);
|
||||
}
|
||||
|
||||
// Implements boilerplate logic for {match, split, replace, search} of the
|
||||
// form:
|
||||
//
|
||||
// if (!IS_NULL_OR_UNDEFINED(object)) {
|
||||
// var maybe_function = object[symbol];
|
||||
// if (!IS_UNDEFINED(maybe_function)) {
|
||||
// return %_Call(maybe_function, ...);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Contains fast paths for Smi and RegExp objects.
|
||||
typedef std::function<Node*()> NodeFunction0;
|
||||
typedef std::function<Node*(Node* fn)> NodeFunction1;
|
||||
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
|
||||
Handle<Symbol> symbol,
|
||||
const NodeFunction0& regexp_call,
|
||||
const NodeFunction1& generic_call);
|
||||
};
|
||||
BIND(&done);
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::GenerateStringEqual(Node* context, Node* left,
|
||||
Node* right) {
|
||||
// Here's pseudo-code for the algorithm below:
|
||||
//
|
||||
// if (lhs == rhs) return true;
|
||||
// if (lhs->length() != rhs->length()) return false;
|
||||
// restart:
|
||||
// if (lhs == rhs) return true;
|
||||
// if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) {
|
||||
// return false;
|
||||
// }
|
||||
@ -225,97 +173,33 @@ void StringBuiltinsAssembler::GenerateStringEqual(Node* context, Node* left,
|
||||
// return true;
|
||||
// }
|
||||
// if (lhs and/or rhs are indirect strings) {
|
||||
// unwrap them and restart from the beginning;
|
||||
// unwrap them and restart from the "restart:" label;
|
||||
// }
|
||||
// return %StringEqual(lhs, rhs);
|
||||
|
||||
VARIABLE(var_left, MachineRepresentation::kTagged, left);
|
||||
VARIABLE(var_right, MachineRepresentation::kTagged, right);
|
||||
|
||||
Variable* input_vars[2] = {&var_left, &var_right};
|
||||
Label if_equal(this), if_notequal(this), restart(this, 2, input_vars);
|
||||
Label if_equal(this), if_notequal(this), if_notbothdirectonebytestrings(this),
|
||||
restart(this, 2, input_vars);
|
||||
|
||||
Node* lhs_length = LoadStringLength(left);
|
||||
Node* rhs_length = LoadStringLength(right);
|
||||
|
||||
// Strings with different lengths cannot be equal.
|
||||
GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal);
|
||||
|
||||
Goto(&restart);
|
||||
BIND(&restart);
|
||||
Node* lhs = var_left.value();
|
||||
Node* rhs = var_right.value();
|
||||
|
||||
// Fast check to see if {lhs} and {rhs} refer to the same String object.
|
||||
GotoIf(WordEqual(lhs, rhs), &if_equal);
|
||||
|
||||
// Load the length of {lhs} and {rhs}.
|
||||
Node* lhs_length = LoadStringLength(lhs);
|
||||
Node* rhs_length = LoadStringLength(rhs);
|
||||
|
||||
// Strings with different lengths cannot be equal.
|
||||
GotoIf(WordNotEqual(lhs_length, rhs_length), &if_notequal);
|
||||
|
||||
// Load instance types of {lhs} and {rhs}.
|
||||
Node* lhs_instance_type = LoadInstanceType(lhs);
|
||||
Node* rhs_instance_type = LoadInstanceType(rhs);
|
||||
|
||||
// Combine the instance types into a single 16-bit value, so we can check
|
||||
// both of them at once.
|
||||
Node* both_instance_types = Word32Or(
|
||||
lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8)));
|
||||
|
||||
// Check if both {lhs} and {rhs} are internalized. Since we already know
|
||||
// that they're not the same object, they're not equal in that case.
|
||||
int const kBothInternalizedMask =
|
||||
kIsNotInternalizedMask | (kIsNotInternalizedMask << 8);
|
||||
int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8);
|
||||
GotoIf(Word32Equal(Word32And(both_instance_types,
|
||||
Int32Constant(kBothInternalizedMask)),
|
||||
Int32Constant(kBothInternalizedTag)),
|
||||
&if_notequal);
|
||||
|
||||
// Check that both {lhs} and {rhs} are flat one-byte strings, and that
|
||||
// in case of ExternalStrings the data pointer is cached..
|
||||
STATIC_ASSERT(kShortExternalStringTag != 0);
|
||||
int const kBothDirectOneByteStringMask =
|
||||
kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask |
|
||||
((kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask)
|
||||
<< 8);
|
||||
int const kBothDirectOneByteStringTag =
|
||||
kOneByteStringTag | (kOneByteStringTag << 8);
|
||||
Label if_bothdirectonebytestrings(this), if_notbothdirectonebytestrings(this);
|
||||
Branch(Word32Equal(Word32And(both_instance_types,
|
||||
Int32Constant(kBothDirectOneByteStringMask)),
|
||||
Int32Constant(kBothDirectOneByteStringTag)),
|
||||
&if_bothdirectonebytestrings, &if_notbothdirectonebytestrings);
|
||||
|
||||
BIND(&if_bothdirectonebytestrings);
|
||||
{
|
||||
// Compute the effective offset of the first character.
|
||||
Node* lhs_data = DirectStringData(lhs, lhs_instance_type);
|
||||
Node* rhs_data = DirectStringData(rhs, rhs_instance_type);
|
||||
|
||||
// Compute the first offset after the string from the length.
|
||||
Node* length = SmiUntag(lhs_length);
|
||||
|
||||
// Loop over the {lhs} and {rhs} strings to see if they are equal.
|
||||
VARIABLE(var_offset, MachineType::PointerRepresentation());
|
||||
Label loop(this, &var_offset);
|
||||
var_offset.Bind(IntPtrConstant(0));
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
// If {offset} equals {end}, no difference was found, so the
|
||||
// strings are equal.
|
||||
Node* offset = var_offset.value();
|
||||
GotoIf(WordEqual(offset, length), &if_equal);
|
||||
|
||||
// Load the next characters from {lhs} and {rhs}.
|
||||
Node* lhs_value = Load(MachineType::Uint8(), lhs_data, offset);
|
||||
Node* rhs_value = Load(MachineType::Uint8(), rhs_data, offset);
|
||||
|
||||
// Check if the characters match.
|
||||
GotoIf(Word32NotEqual(lhs_value, rhs_value), &if_notequal);
|
||||
|
||||
// Advance to next character.
|
||||
var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1)));
|
||||
Goto(&loop);
|
||||
}
|
||||
}
|
||||
StringEqual_Core(context, lhs, lhs_instance_type, lhs_length, rhs,
|
||||
rhs_instance_type, &if_equal, &if_notequal,
|
||||
&if_notbothdirectonebytestrings);
|
||||
|
||||
BIND(&if_notbothdirectonebytestrings);
|
||||
{
|
||||
@ -334,6 +218,80 @@ void StringBuiltinsAssembler::GenerateStringEqual(Node* context, Node* left,
|
||||
Return(FalseConstant());
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::StringEqual_Core(
|
||||
Node* context, Node* lhs, Node* lhs_instance_type, Node* lhs_length,
|
||||
Node* rhs, Node* rhs_instance_type, Label* if_equal, Label* if_not_equal,
|
||||
Label* if_notbothdirectonebyte) {
|
||||
CSA_ASSERT(this, IsString(lhs));
|
||||
CSA_ASSERT(this, IsString(rhs));
|
||||
CSA_ASSERT(this, WordEqual(LoadStringLength(lhs), lhs_length));
|
||||
CSA_ASSERT(this, WordEqual(LoadStringLength(rhs), lhs_length));
|
||||
// Fast check to see if {lhs} and {rhs} refer to the same String object.
|
||||
GotoIf(WordEqual(lhs, rhs), if_equal);
|
||||
|
||||
// Combine the instance types into a single 16-bit value, so we can check
|
||||
// both of them at once.
|
||||
Node* both_instance_types = Word32Or(
|
||||
lhs_instance_type, Word32Shl(rhs_instance_type, Int32Constant(8)));
|
||||
|
||||
// Check if both {lhs} and {rhs} are internalized. Since we already know
|
||||
// that they're not the same object, they're not equal in that case.
|
||||
int const kBothInternalizedMask =
|
||||
kIsNotInternalizedMask | (kIsNotInternalizedMask << 8);
|
||||
int const kBothInternalizedTag = kInternalizedTag | (kInternalizedTag << 8);
|
||||
GotoIf(Word32Equal(Word32And(both_instance_types,
|
||||
Int32Constant(kBothInternalizedMask)),
|
||||
Int32Constant(kBothInternalizedTag)),
|
||||
if_not_equal);
|
||||
|
||||
// Check that both {lhs} and {rhs} are flat one-byte strings, and that
|
||||
// in case of ExternalStrings the data pointer is cached..
|
||||
STATIC_ASSERT(kShortExternalStringTag != 0);
|
||||
int const kBothDirectOneByteStringMask =
|
||||
kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask |
|
||||
((kStringEncodingMask | kIsIndirectStringMask | kShortExternalStringMask)
|
||||
<< 8);
|
||||
int const kBothDirectOneByteStringTag =
|
||||
kOneByteStringTag | (kOneByteStringTag << 8);
|
||||
GotoIfNot(Word32Equal(Word32And(both_instance_types,
|
||||
Int32Constant(kBothDirectOneByteStringMask)),
|
||||
Int32Constant(kBothDirectOneByteStringTag)),
|
||||
if_notbothdirectonebyte);
|
||||
|
||||
// At this point we know that we have two direct one-byte strings.
|
||||
|
||||
// Compute the effective offset of the first character.
|
||||
Node* lhs_data = DirectStringData(lhs, lhs_instance_type);
|
||||
Node* rhs_data = DirectStringData(rhs, rhs_instance_type);
|
||||
|
||||
// Compute the first offset after the string from the length.
|
||||
Node* length = SmiUntag(lhs_length);
|
||||
|
||||
// Loop over the {lhs} and {rhs} strings to see if they are equal.
|
||||
VARIABLE(var_offset, MachineType::PointerRepresentation());
|
||||
Label loop(this, &var_offset);
|
||||
var_offset.Bind(IntPtrConstant(0));
|
||||
Goto(&loop);
|
||||
BIND(&loop);
|
||||
{
|
||||
// If {offset} equals {end}, no difference was found, so the
|
||||
// strings are equal.
|
||||
Node* offset = var_offset.value();
|
||||
GotoIf(WordEqual(offset, length), if_equal);
|
||||
|
||||
// Load the next characters from {lhs} and {rhs}.
|
||||
Node* lhs_value = Load(MachineType::Uint8(), lhs_data, offset);
|
||||
Node* rhs_value = Load(MachineType::Uint8(), rhs_data, offset);
|
||||
|
||||
// Check if the characters match.
|
||||
GotoIf(Word32NotEqual(lhs_value, rhs_value), if_not_equal);
|
||||
|
||||
// Advance to next character.
|
||||
var_offset.Bind(IntPtrAdd(offset, IntPtrConstant(1)));
|
||||
Goto(&loop);
|
||||
}
|
||||
}
|
||||
|
||||
void StringBuiltinsAssembler::GenerateStringRelationalComparison(
|
||||
Node* context, Node* left, Node* right, RelationalComparisonMode mode) {
|
||||
VARIABLE(var_left, MachineRepresentation::kTagged, left);
|
||||
|
95
src/builtins/builtins-string-gen.h
Normal file
95
src/builtins/builtins-string-gen.h
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2017 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_BUILTINS_BUILTINS_STRING_GEN_H_
|
||||
#define V8_BUILTINS_BUILTINS_STRING_GEN_H_
|
||||
|
||||
#include "src/code-stub-assembler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class StringBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
explicit StringBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||
: CodeStubAssembler(state) {}
|
||||
|
||||
// ES#sec-getsubstitution
|
||||
Node* GetSubstitution(Node* context, Node* subject_string,
|
||||
Node* match_start_index, Node* match_end_index,
|
||||
Node* replace_string);
|
||||
void StringEqual_Core(Node* context, Node* lhs, Node* lhs_instance_type,
|
||||
Node* lhs_length, Node* rhs, Node* rhs_instance_type,
|
||||
Label* if_equal, Label* if_not_equal,
|
||||
Label* if_notbothdirectonebyte);
|
||||
|
||||
protected:
|
||||
Node* DirectStringData(Node* string, Node* string_instance_type);
|
||||
|
||||
void DispatchOnStringEncodings(Node* const lhs_instance_type,
|
||||
Node* const rhs_instance_type,
|
||||
Label* if_one_one, Label* if_one_two,
|
||||
Label* if_two_one, Label* if_two_two);
|
||||
|
||||
template <typename SubjectChar, typename PatternChar>
|
||||
Node* CallSearchStringRaw(Node* const subject_ptr, Node* const subject_length,
|
||||
Node* const search_ptr, Node* const search_length,
|
||||
Node* const start_position);
|
||||
|
||||
Node* PointerToStringDataAtIndex(Node* const string_data, Node* const index,
|
||||
String::Encoding encoding);
|
||||
|
||||
// substr and slice have a common way of handling the {start} argument.
|
||||
void ConvertAndBoundsCheckStartArgument(Node* context, Variable* var_start,
|
||||
Node* start, Node* string_length);
|
||||
|
||||
void GenerateStringEqual(Node* context, Node* left, Node* right);
|
||||
void GenerateStringRelationalComparison(Node* context, Node* left,
|
||||
Node* right,
|
||||
RelationalComparisonMode mode);
|
||||
|
||||
Node* ToSmiBetweenZeroAnd(Node* context, Node* value, Node* limit);
|
||||
|
||||
Node* LoadSurrogatePairAt(Node* string, Node* length, Node* index,
|
||||
UnicodeEncoding encoding);
|
||||
|
||||
void StringIndexOf(Node* const subject_string,
|
||||
Node* const subject_instance_type,
|
||||
Node* const search_string,
|
||||
Node* const search_instance_type, Node* const position,
|
||||
std::function<void(Node*)> f_return);
|
||||
|
||||
Node* IndexOfDollarChar(Node* const context, Node* const string);
|
||||
|
||||
Node* IsNullOrUndefined(Node* const value);
|
||||
void RequireObjectCoercible(Node* const context, Node* const value,
|
||||
const char* method_name);
|
||||
|
||||
Node* SmiIsNegative(Node* const value) {
|
||||
return SmiLessThan(value, SmiConstant(0));
|
||||
}
|
||||
|
||||
// Implements boilerplate logic for {match, split, replace, search} of the
|
||||
// form:
|
||||
//
|
||||
// if (!IS_NULL_OR_UNDEFINED(object)) {
|
||||
// var maybe_function = object[symbol];
|
||||
// if (!IS_UNDEFINED(maybe_function)) {
|
||||
// return %_Call(maybe_function, ...);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Contains fast paths for Smi and RegExp objects.
|
||||
typedef std::function<Node*()> NodeFunction0;
|
||||
typedef std::function<Node*(Node* fn)> NodeFunction1;
|
||||
void MaybeCallFunctionAtSymbol(Node* const context, Node* const object,
|
||||
Handle<Symbol> symbol,
|
||||
const NodeFunction0& regexp_call,
|
||||
const NodeFunction1& generic_call);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_BUILTINS_BUILTINS_STRING_GEN_H_
|
@ -319,6 +319,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
|
||||
V(StringParseInt) \
|
||||
V(StringCharCodeAtRT) \
|
||||
V(StringIndexOfUnchecked) \
|
||||
V(StringEqual) \
|
||||
V(SymbolDescriptiveString) \
|
||||
V(GenerateRandomNumbers) \
|
||||
V(ExternalStringGetChar) \
|
||||
@ -679,6 +680,7 @@ bool DebugEvaluate::FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info) {
|
||||
if (builtin_index >= 0 && builtin_index < Builtins::builtin_count &&
|
||||
BuiltinHasNoSideEffect(static_cast<Builtins::Name>(builtin_index))) {
|
||||
#ifdef DEBUG
|
||||
// TODO(yangguo): Check builtin-to-builtin calls too.
|
||||
int mode = RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE);
|
||||
bool failed = false;
|
||||
for (RelocIterator it(info->code(), mode); !it.done(); it.next()) {
|
||||
|
@ -527,6 +527,7 @@
|
||||
'builtins/builtins-sharedarraybuffer-gen.cc',
|
||||
'builtins/builtins-string.cc',
|
||||
'builtins/builtins-string-gen.cc',
|
||||
'builtins/builtins-string-gen.h',
|
||||
'builtins/builtins-intl.cc',
|
||||
'builtins/builtins-intl-gen.cc',
|
||||
'builtins/builtins-symbol.cc',
|
||||
|
Loading…
Reference in New Issue
Block a user