[torque] Port String.prototype.includes/indexOf and StringIndexOf

- Removed no longer used StringBuiltinAssembler methods (DispatchOnStringEncodings, PointerToStringDataAtIndex)
- Removed no longer used Runtime functions (StringIncludes, StringIndexOf, StringIndexOfUnchecked).
- Overall builtin code size is reduced (652 bytes on Mac x64.release build), builtin size breakdown:

BEFORE
======
TFS Builtin, StringIndexOf, 1092
TFJ Builtin, StringPrototypeIncludes, 1784
TFJ Builtin, StringPrototypeIndexOf, 1536
Total = 4412

AFTER
=====
TFC Builtin, StringIndexOf, 2036 (+944)
TFJ Builtin, StringPrototypeIncludes, 1072 (-712)
TFJ Builtin, StringPrototypeIndexOf, 652 (-884)
Total = 3760 (-652)


Bug: v8:8996
Change-Id: I9a88c095e2097f7d570e58e744d6692dc524ddf4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2660995
Commit-Queue: Peter Wong <peter.wm.wong@gmail.com>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72456}
This commit is contained in:
Peter Wong 2021-02-01 01:34:16 -06:00 committed by Commit Bot
parent 0461065ca9
commit 5a2c53f948
14 changed files with 273 additions and 492 deletions

View File

@ -1315,6 +1315,8 @@ torque_files = [
"src/builtins/string-at.tq", "src/builtins/string-at.tq",
"src/builtins/string-endswith.tq", "src/builtins/string-endswith.tq",
"src/builtins/string-html.tq", "src/builtins/string-html.tq",
"src/builtins/string-includes.tq",
"src/builtins/string-indexof.tq",
"src/builtins/string-iterator.tq", "src/builtins/string-iterator.tq",
"src/builtins/string-pad.tq", "src/builtins/string-pad.tq",
"src/builtins/string-repeat.tq", "src/builtins/string-repeat.tq",

View File

@ -110,7 +110,6 @@ namespace internal {
TFC(StringEqual, Compare) \ TFC(StringEqual, Compare) \
TFC(StringGreaterThan, Compare) \ TFC(StringGreaterThan, Compare) \
TFC(StringGreaterThanOrEqual, Compare) \ TFC(StringGreaterThanOrEqual, Compare) \
TFS(StringIndexOf, kReceiver, kSearchString, kPosition) \
TFC(StringLessThan, Compare) \ TFC(StringLessThan, Compare) \
TFC(StringLessThanOrEqual, Compare) \ TFC(StringLessThanOrEqual, Compare) \
TFC(StringSubstring, StringSubstring) \ TFC(StringSubstring, StringSubstring) \
@ -750,10 +749,6 @@ namespace internal {
CPP(StringFromCodePoint) \ CPP(StringFromCodePoint) \
/* ES6 #sec-string.fromcharcode */ \ /* ES6 #sec-string.fromcharcode */ \
TFJ(StringFromCharCode, kDontAdaptArgumentsSentinel) \ TFJ(StringFromCharCode, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.includes */ \
TFJ(StringPrototypeIncludes, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.indexof */ \
TFJ(StringPrototypeIndexOf, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.lastindexof */ \ /* ES6 #sec-string.prototype.lastindexof */ \
CPP(StringPrototypeLastIndexOf) \ CPP(StringPrototypeLastIndexOf) \
/* ES6 #sec-string.prototype.match */ \ /* ES6 #sec-string.prototype.match */ \

View File

@ -48,46 +48,6 @@ TNode<RawPtrT> StringBuiltinsAssembler::DirectStringData(
return var_data.value(); return var_data.value();
} }
void StringBuiltinsAssembler::DispatchOnStringEncodings(
TNode<Word32T> const lhs_instance_type,
TNode<Word32T> 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.
const TNode<Int32T> encoding_mask = Int32Constant(kStringEncodingMask);
const TNode<Word32T> lhs_encoding =
Word32And(lhs_instance_type, encoding_mask);
const TNode<Word32T> rhs_encoding =
Word32And(rhs_instance_type, encoding_mask);
const TNode<Word32T> 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> template <typename SubjectChar, typename PatternChar>
TNode<IntPtrT> StringBuiltinsAssembler::CallSearchStringRaw( TNode<IntPtrT> StringBuiltinsAssembler::CallSearchStringRaw(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length, const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
@ -111,15 +71,57 @@ TNode<IntPtrT> StringBuiltinsAssembler::CallSearchStringRaw(
return result; return result;
} }
TNode<IntPtrT> StringBuiltinsAssembler::SearchOneByteStringInTwoByteString(
TNode<RawPtrT> StringBuiltinsAssembler::PointerToStringDataAtIndex( const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
TNode<RawPtrT> string_data, TNode<IntPtrT> index, const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
String::Encoding encoding) { const TNode<IntPtrT> start_position) {
const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING) return CallSearchStringRaw<const uc16, const uint8_t>(
? UINT8_ELEMENTS subject_ptr, subject_length, search_ptr, search_length, start_position);
: UINT16_ELEMENTS; }
TNode<IntPtrT> offset_in_bytes = ElementOffsetFromIndex(index, kind); TNode<IntPtrT> StringBuiltinsAssembler::SearchOneByteStringInOneByteString(
return RawPtrAdd(string_data, offset_in_bytes); const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position) {
return CallSearchStringRaw<const uint8_t, const uint8_t>(
subject_ptr, subject_length, search_ptr, search_length, start_position);
}
TNode<IntPtrT> StringBuiltinsAssembler::SearchTwoByteStringInTwoByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position) {
return CallSearchStringRaw<const uc16, const uc16>(
subject_ptr, subject_length, search_ptr, search_length, start_position);
}
TNode<IntPtrT> StringBuiltinsAssembler::SearchTwoByteStringInOneByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position) {
return CallSearchStringRaw<const uint8_t, const uc16>(
subject_ptr, subject_length, search_ptr, search_length, start_position);
}
TNode<IntPtrT> StringBuiltinsAssembler::SearchOneByteInOneByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> start_position) {
const TNode<RawPtrT> subject_start_ptr =
RawPtrAdd(subject_ptr, start_position);
const TNode<IntPtrT> search_byte =
ChangeInt32ToIntPtr(Load<Uint8T>(search_ptr));
const TNode<UintPtrT> search_length =
Unsigned(IntPtrSub(subject_length, start_position));
const TNode<ExternalReference> memchr =
ExternalConstant(ExternalReference::libc_memchr_function());
const TNode<RawPtrT> result_address = UncheckedCast<RawPtrT>(
CallCFunction(memchr, MachineType::Pointer(),
std::make_pair(MachineType::Pointer(), subject_start_ptr),
std::make_pair(MachineType::IntPtr(), search_byte),
std::make_pair(MachineType::UintPtr(), search_length)));
return Select<IntPtrT>(
WordEqual(result_address, IntPtrConstant(0)),
[=] { return IntPtrConstant(-1); },
[=] {
return IntPtrAdd(RawPtrSub(result_address, subject_start_ptr),
start_position);
});
} }
void StringBuiltinsAssembler::GenerateStringEqual(TNode<String> left, void StringBuiltinsAssembler::GenerateStringEqual(TNode<String> left,
@ -887,273 +889,6 @@ TF_BUILTIN(StringFromCharCode, StringBuiltinsAssembler) {
} }
} }
void StringBuiltinsAssembler::StringIndexOf(
const TNode<String> subject_string, const TNode<String> search_string,
const TNode<Smi> position,
const std::function<void(TNode<Smi>)>& f_return) {
const TNode<IntPtrT> int_zero = IntPtrConstant(0);
const TNode<IntPtrT> search_length = LoadStringLengthAsWord(search_string);
const TNode<IntPtrT> subject_length = LoadStringLengthAsWord(subject_string);
const TNode<IntPtrT> start_position = IntPtrMax(SmiUntag(position), int_zero);
Label zero_length_needle(this), return_minus_1(this);
{
GotoIf(IntPtrEqual(int_zero, search_length), &zero_length_needle);
// Check that the needle fits in the start position.
GotoIfNot(IntPtrLessThanOrEqual(search_length,
IntPtrSub(subject_length, start_position)),
&return_minus_1);
}
// If the string pointers are identical, we can just return 0. Note that this
// implies {start_position} == 0 since we've passed the check above.
Label return_zero(this);
GotoIf(TaggedEqual(subject_string, search_string), &return_zero);
// Try to unpack subject and search strings. Bail to runtime if either needs
// to be flattened.
ToDirectStringAssembler subject_to_direct(state(), subject_string);
ToDirectStringAssembler search_to_direct(state(), search_string);
Label call_runtime_unchecked(this, Label::kDeferred);
subject_to_direct.TryToDirect(&call_runtime_unchecked);
search_to_direct.TryToDirect(&call_runtime_unchecked);
// Load pointers to string data.
const TNode<RawPtrT> subject_ptr =
subject_to_direct.PointerToData(&call_runtime_unchecked);
const TNode<RawPtrT> search_ptr =
search_to_direct.PointerToData(&call_runtime_unchecked);
const TNode<IntPtrT> subject_offset = subject_to_direct.offset();
const TNode<IntPtrT> search_offset = search_to_direct.offset();
// Like String::IndexOf, the actual matching is done by the optimized
// SearchString method in string-search.h. Dispatch based on string instance
// types, then call straight into C++ for matching.
CSA_ASSERT(this, IntPtrGreaterThan(search_length, int_zero));
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(start_position, int_zero));
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(subject_length, start_position));
CSA_ASSERT(this,
IntPtrLessThanOrEqual(search_length,
IntPtrSub(subject_length, start_position)));
Label one_one(this), one_two(this), two_one(this), two_two(this);
DispatchOnStringEncodings(subject_to_direct.instance_type(),
search_to_direct.instance_type(), &one_one,
&one_two, &two_one, &two_two);
using onebyte_t = const uint8_t;
using twobyte_t = const uc16;
BIND(&one_one);
{
const TNode<RawPtrT> adjusted_subject_ptr = PointerToStringDataAtIndex(
subject_ptr, subject_offset, String::ONE_BYTE_ENCODING);
const TNode<RawPtrT> adjusted_search_ptr = PointerToStringDataAtIndex(
search_ptr, search_offset, String::ONE_BYTE_ENCODING);
Label direct_memchr_call(this), generic_fast_path(this);
Branch(IntPtrEqual(search_length, IntPtrConstant(1)), &direct_memchr_call,
&generic_fast_path);
// An additional fast path that calls directly into memchr for 1-length
// search strings.
BIND(&direct_memchr_call);
{
const TNode<RawPtrT> string_addr =
RawPtrAdd(adjusted_subject_ptr, start_position);
const TNode<IntPtrT> search_length =
IntPtrSub(subject_length, start_position);
const TNode<IntPtrT> search_byte =
ChangeInt32ToIntPtr(Load<Uint8T>(adjusted_search_ptr));
const TNode<ExternalReference> memchr =
ExternalConstant(ExternalReference::libc_memchr_function());
const TNode<RawPtrT> result_address = UncheckedCast<RawPtrT>(
CallCFunction(memchr, MachineType::Pointer(),
std::make_pair(MachineType::Pointer(), string_addr),
std::make_pair(MachineType::IntPtr(), search_byte),
std::make_pair(MachineType::UintPtr(), search_length)));
GotoIf(WordEqual(result_address, int_zero), &return_minus_1);
const TNode<IntPtrT> result_index =
IntPtrAdd(RawPtrSub(result_address, string_addr), start_position);
f_return(SmiTag(result_index));
}
BIND(&generic_fast_path);
{
const TNode<IntPtrT> result = CallSearchStringRaw<onebyte_t, onebyte_t>(
adjusted_subject_ptr, subject_length, adjusted_search_ptr,
search_length, start_position);
f_return(SmiTag(result));
}
}
BIND(&one_two);
{
const TNode<RawPtrT> adjusted_subject_ptr = PointerToStringDataAtIndex(
subject_ptr, subject_offset, String::ONE_BYTE_ENCODING);
const TNode<RawPtrT> adjusted_search_ptr = PointerToStringDataAtIndex(
search_ptr, search_offset, String::TWO_BYTE_ENCODING);
const TNode<IntPtrT> result = CallSearchStringRaw<onebyte_t, twobyte_t>(
adjusted_subject_ptr, subject_length, adjusted_search_ptr,
search_length, start_position);
f_return(SmiTag(result));
}
BIND(&two_one);
{
const TNode<RawPtrT> adjusted_subject_ptr = PointerToStringDataAtIndex(
subject_ptr, subject_offset, String::TWO_BYTE_ENCODING);
const TNode<RawPtrT> adjusted_search_ptr = PointerToStringDataAtIndex(
search_ptr, search_offset, String::ONE_BYTE_ENCODING);
const TNode<IntPtrT> result = CallSearchStringRaw<twobyte_t, onebyte_t>(
adjusted_subject_ptr, subject_length, adjusted_search_ptr,
search_length, start_position);
f_return(SmiTag(result));
}
BIND(&two_two);
{
const TNode<RawPtrT> adjusted_subject_ptr = PointerToStringDataAtIndex(
subject_ptr, subject_offset, String::TWO_BYTE_ENCODING);
const TNode<RawPtrT> adjusted_search_ptr = PointerToStringDataAtIndex(
search_ptr, search_offset, String::TWO_BYTE_ENCODING);
const TNode<IntPtrT> result = CallSearchStringRaw<twobyte_t, twobyte_t>(
adjusted_subject_ptr, subject_length, adjusted_search_ptr,
search_length, start_position);
f_return(SmiTag(result));
}
BIND(&return_minus_1);
f_return(SmiConstant(-1));
BIND(&return_zero);
f_return(SmiConstant(0));
BIND(&zero_length_needle);
{
Comment("0-length search_string");
f_return(SmiTag(IntPtrMin(subject_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");
TNode<Smi> result =
CAST(CallRuntime(Runtime::kStringIndexOfUnchecked, NoContextConstant(),
subject_string, 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) {
auto receiver = Parameter<String>(Descriptor::kReceiver);
auto search_string = Parameter<String>(Descriptor::kSearchString);
auto position = Parameter<Smi>(Descriptor::kPosition);
StringIndexOf(receiver, search_string, position,
[this](TNode<Smi> result) { this->Return(result); });
}
// ES6 String.prototype.includes(searchString [, position])
// #sec-string.prototype.includes
TF_BUILTIN(StringPrototypeIncludes, StringIncludesIndexOfAssembler) {
TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
auto context = Parameter<Context>(Descriptor::kContext);
Generate(kIncludes, argc, context);
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
TF_BUILTIN(StringPrototypeIndexOf, StringIncludesIndexOfAssembler) {
TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
auto context = Parameter<Context>(Descriptor::kContext);
Generate(kIndexOf, argc, context);
}
void StringIncludesIndexOfAssembler::Generate(SearchVariant variant,
TNode<IntPtrT> argc,
TNode<Context> context) {
CodeStubArguments arguments(this, argc);
const TNode<Object> receiver = arguments.GetReceiver();
TVARIABLE(Object, var_search_string);
TVARIABLE(Object, var_position);
Label argc_1(this), argc_2(this), call_runtime(this, Label::kDeferred),
fast_path(this);
GotoIf(IntPtrEqual(arguments.GetLength(), IntPtrConstant(1)), &argc_1);
GotoIf(IntPtrGreaterThan(arguments.GetLength(), IntPtrConstant(1)), &argc_2);
{
Comment("0 Argument case");
CSA_ASSERT(this, IntPtrEqual(arguments.GetLength(), IntPtrConstant(0)));
TNode<Oddball> undefined = UndefinedConstant();
var_search_string = undefined;
var_position = undefined;
Goto(&call_runtime);
}
BIND(&argc_1);
{
Comment("1 Argument case");
var_search_string = arguments.AtIndex(0);
var_position = SmiConstant(0);
Goto(&fast_path);
}
BIND(&argc_2);
{
Comment("2 Argument case");
var_search_string = arguments.AtIndex(0);
var_position = arguments.AtIndex(1);
GotoIfNot(TaggedIsSmi(var_position.value()), &call_runtime);
Goto(&fast_path);
}
BIND(&fast_path);
{
Comment("Fast Path");
const TNode<Object> search = var_search_string.value();
const TNode<Smi> position = CAST(var_position.value());
GotoIf(TaggedIsSmi(receiver), &call_runtime);
GotoIf(TaggedIsSmi(search), &call_runtime);
GotoIfNot(IsString(CAST(receiver)), &call_runtime);
GotoIfNot(IsString(CAST(search)), &call_runtime);
StringIndexOf(CAST(receiver), CAST(search), position,
[&](TNode<Smi> result) {
if (variant == kIndexOf) {
arguments.PopAndReturn(result);
} else {
arguments.PopAndReturn(SelectBooleanConstant(
SmiGreaterThanOrEqual(result, SmiConstant(0))));
}
});
}
BIND(&call_runtime);
{
Comment("Call Runtime");
Runtime::FunctionId runtime = variant == kIndexOf
? Runtime::kStringIndexOf
: Runtime::kStringIncludes;
const TNode<Object> result =
CallRuntime(runtime, context, receiver, var_search_string.value(),
var_position.value());
arguments.PopAndReturn(result);
}
}
void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol( void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
const TNode<Context> context, const TNode<Object> object, const TNode<Context> context, const TNode<Object> object,
const TNode<Object> maybe_string, Handle<Symbol> symbol, const TNode<Object> maybe_string, Handle<Symbol> symbol,

View File

@ -61,6 +61,29 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
String::Encoding from_encoding, String::Encoding from_encoding,
String::Encoding to_encoding); String::Encoding to_encoding);
// Torque wrapper methods for CallSearchStringRaw for each combination of
// search and subject character widths (char8/char16). This is a workaround
// for Torque's current lack of support for extern macros with generics.
TNode<IntPtrT> SearchOneByteStringInTwoByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position);
TNode<IntPtrT> SearchOneByteStringInOneByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position);
TNode<IntPtrT> SearchTwoByteStringInTwoByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position);
TNode<IntPtrT> SearchTwoByteStringInOneByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position);
TNode<IntPtrT> SearchOneByteInOneByteString(
const TNode<RawPtrT> subject_ptr, const TNode<IntPtrT> subject_length,
const TNode<RawPtrT> search_ptr, const TNode<IntPtrT> start_position);
protected: protected:
void StringEqual_Loop(TNode<String> lhs, TNode<Word32T> lhs_instance_type, void StringEqual_Loop(TNode<String> lhs, TNode<Word32T> lhs_instance_type,
MachineType lhs_type, TNode<String> rhs, MachineType lhs_type, TNode<String> rhs,
@ -70,11 +93,6 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
TNode<RawPtrT> DirectStringData(TNode<String> string, TNode<RawPtrT> DirectStringData(TNode<String> string,
TNode<Word32T> string_instance_type); TNode<Word32T> string_instance_type);
void DispatchOnStringEncodings(const TNode<Word32T> lhs_instance_type,
const TNode<Word32T> rhs_instance_type,
Label* if_one_one, Label* if_one_two,
Label* if_two_one, Label* if_two_two);
template <typename SubjectChar, typename PatternChar> template <typename SubjectChar, typename PatternChar>
TNode<IntPtrT> CallSearchStringRaw(const TNode<RawPtrT> subject_ptr, TNode<IntPtrT> CallSearchStringRaw(const TNode<RawPtrT> subject_ptr,
const TNode<IntPtrT> subject_length, const TNode<IntPtrT> subject_length,
@ -82,10 +100,6 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
const TNode<IntPtrT> search_length, const TNode<IntPtrT> search_length,
const TNode<IntPtrT> start_position); const TNode<IntPtrT> start_position);
TNode<RawPtrT> PointerToStringDataAtIndex(TNode<RawPtrT> string_data,
TNode<IntPtrT> index,
String::Encoding encoding);
void GenerateStringEqual(TNode<String> left, TNode<String> right); void GenerateStringEqual(TNode<String> left, TNode<String> right);
void GenerateStringRelationalComparison(TNode<String> left, void GenerateStringRelationalComparison(TNode<String> left,
TNode<String> right, Operation op); TNode<String> right, Operation op);
@ -93,11 +107,6 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
using StringAtAccessor = std::function<TNode<Object>( using StringAtAccessor = std::function<TNode<Object>(
TNode<String> receiver, TNode<IntPtrT> length, TNode<IntPtrT> index)>; TNode<String> receiver, TNode<IntPtrT> length, TNode<IntPtrT> index)>;
void StringIndexOf(const TNode<String> subject_string,
const TNode<String> search_string,
const TNode<Smi> position,
const std::function<void(TNode<Smi>)>& f_return);
const TNode<Smi> IndexOfDollarChar(const TNode<Context> context, const TNode<Smi> IndexOfDollarChar(const TNode<Context> context,
const TNode<String> string); const TNode<String> string);
@ -172,18 +181,6 @@ class StringBuiltinsAssembler : public CodeStubAssembler {
TNode<IntPtrT> character_count); TNode<IntPtrT> character_count);
}; };
class StringIncludesIndexOfAssembler : public StringBuiltinsAssembler {
public:
explicit StringIncludesIndexOfAssembler(compiler::CodeAssemblerState* state)
: StringBuiltinsAssembler(state) {}
protected:
enum SearchVariant { kIncludes, kIndexOf };
void Generate(SearchVariant variant, TNode<IntPtrT> argc,
TNode<Context> context);
};
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -116,9 +116,7 @@ IfInBounds(String, uintptr, uintptr), IfOutOfBounds {
const index: uintptr = Unsigned(Convert<intptr>(indexSmi)); const index: uintptr = Unsigned(Convert<intptr>(indexSmi));
// Max string length fits Smi range, so we can do an unsigned bounds // Max string length fits Smi range, so we can do an unsigned bounds
// check. // check.
const kMaxStringLengthFitsSmi: constexpr bool = StaticAssertStringLengthFitsSmi();
kStringMaxLengthUintptr < kSmiMaxValue;
static_assert(kMaxStringLengthFitsSmi);
if (index >= length) goto IfOutOfBounds; if (index >= length) goto IfOutOfBounds;
goto IfInBounds(string, index, length); goto IfInBounds(string, index, length);
} }

View File

@ -108,9 +108,7 @@ transitioning macro RegExpPrototypeMatchBody(implicit context: Context)(
// On the fast path, we can be certain that lastIndex can never be // On the fast path, we can be certain that lastIndex can never be
// incremented to overflow the Smi range since the maximal string // incremented to overflow the Smi range since the maximal string
// length is less than the maximal Smi value. // length is less than the maximal Smi value.
const kMaxStringLengthFitsSmi: constexpr bool = StaticAssertStringLengthFitsSmi();
kStringMaxLengthUintptr < kSmiMaxValue;
static_assert(kMaxStringLengthFitsSmi);
assert(TaggedIsPositiveSmi(newLastIndex)); assert(TaggedIsPositiveSmi(newLastIndex));
} }

View File

@ -0,0 +1,49 @@
// Copyright 2021 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.
#include 'src/builtins/builtins-string-gen.h'
namespace string {
// https://tc39.es/ecma262/#sec-string.prototype.includes
transitioning javascript builtin
StringPrototypeIncludes(js-implicit context: NativeContext, receiver: JSAny)(
...arguments): Boolean {
const methodName: constexpr string = 'String.prototype.includes';
const searchString: JSAny = arguments[0];
const position: JSAny = arguments[1];
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
const s = ToThisString(receiver, methodName);
// 3. Let isRegExp be ? IsRegExp(searchString).
// 4. If isRegExp is true, throw a TypeError exception.
if (regexp::IsRegExp(searchString)) {
ThrowTypeError(MessageTemplate::kFirstArgumentNotRegExp, methodName);
}
// 5. Let searchStr be ? ToString(searchString).
const searchStr = ToString_Inline(searchString);
// 6. Let pos be ? ToIntegerOrInfinity(position).
// 7. Assert: If position is undefined, then pos is 0.
let start: Smi = 0;
if (position != Undefined) {
// 8. Let len be the length of S.
const len = s.length_uintptr;
// 9. Let start be the result of clamping pos between 0 and len.
StaticAssertStringLengthFitsSmi();
start = Convert<Smi>(Signed(ClampToIndexRange(position, len)));
}
// 10. Let index be ! StringIndexOf(S, searchStr, start).
const index = StringIndexOf(s, searchStr, start);
// 11. If index is not -1, return true.
// 12. Return false.
return index != -1 ? True : False;
}
}

View File

@ -0,0 +1,39 @@
// Copyright 2021 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.
#include 'src/builtins/builtins-string-gen.h'
namespace string {
// https://tc39.es/ecma262/#sec-string.prototype.indexof
transitioning javascript builtin
StringPrototypeIndexOf(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): Smi {
const methodName: constexpr string = 'String.prototype.indexOf';
const searchString: JSAny = arguments[0];
const position: JSAny = arguments[1];
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
const s = ToThisString(receiver, methodName);
// 3. Let searchStr be ? ToString(searchString).
const searchStr = ToString_Inline(searchString);
// 4. Let pos be ? ToIntegerOrInfinity(position).
// 5. Assert: If position is undefined, then pos is 0.
let start: Smi = 0;
if (position != Undefined) {
// 6. Let len be the length of S.
const len = s.length_uintptr;
// 7. Let start be the result of clamping pos between 0 and len.
StaticAssertStringLengthFitsSmi();
start = Convert<Smi>(Signed(ClampToIndexRange(position, len)));
}
// 8. Let index be ! StringIndexOf(S, searchStr, start).
return StringIndexOf(s, searchStr, start);
}
}

View File

@ -9,52 +9,6 @@ extern macro ReplaceSymbolConstant(): Symbol;
extern macro StringBuiltinsAssembler::GetSubstitution( extern macro StringBuiltinsAssembler::GetSubstitution(
implicit context: Context)(String, Smi, Smi, String): String; implicit context: Context)(String, Smi, Smi, String): String;
extern builtin
StringIndexOf(implicit context: Context)(String, String, Smi): Smi;
// TODO(turbofan): This could be replaced with a fast C-call to StringSearchRaw.
macro AbstractStringIndexOf<A: type, B: type>(
string: ConstSlice<A>, searchString: ConstSlice<B>, fromIndex: Smi): Smi {
for (let i: intptr = SmiUntag(fromIndex);
i <= string.length - searchString.length; i++) {
if (IsSubstringAt(string, searchString, i)) {
return SmiTag(i);
}
}
return -1;
}
struct AbstractStringIndexOfFunctor {
fromIndex: Smi;
}
// Ideally, this would be a method of AbstractStringIndexOfFunctor, but
// currently methods don't support templates.
macro Call<A: type, B: type>(
self: AbstractStringIndexOfFunctor, string: ConstSlice<A>,
searchStr: ConstSlice<B>): Smi {
return AbstractStringIndexOf(string, searchStr, self.fromIndex);
}
macro AbstractStringIndexOf(implicit context: Context)(
string: String, searchString: String, fromIndex: Smi): Smi {
// Special case the empty string.
const searchStringLength = searchString.length_intptr;
const stringLength = string.length_intptr;
if (searchStringLength == 0 && SmiUntag(fromIndex) <= stringLength) {
return fromIndex;
}
// Don't bother to search if the searchString would go past the end
// of the string. This is actually necessary because of runtime
// checks.
if (SmiUntag(fromIndex) + searchStringLength > stringLength) {
return -1;
}
return TwoStringsToSlices<Smi>(
string, searchString, AbstractStringIndexOfFunctor{fromIndex: fromIndex});
}
transitioning macro transitioning macro
ThrowIfNotGlobal(implicit context: Context)(searchValue: JSAny): void { ThrowIfNotGlobal(implicit context: Context)(searchValue: JSAny): void {
let shouldThrow: bool; let shouldThrow: bool;

View File

@ -308,8 +308,6 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
V(ThrowReferenceError) \ V(ThrowReferenceError) \
V(ThrowSymbolIteratorInvalid) \ V(ThrowSymbolIteratorInvalid) \
/* Strings */ \ /* Strings */ \
V(StringIncludes) \
V(StringIndexOf) \
V(StringReplaceOneCharWithString) \ V(StringReplaceOneCharWithString) \
V(StringSubstring) \ V(StringSubstring) \
V(StringToNumber) \ V(StringToNumber) \
@ -351,7 +349,6 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
V(StringAdd) \ V(StringAdd) \
V(StringCharCodeAt) \ V(StringCharCodeAt) \
V(StringEqual) \ V(StringEqual) \
V(StringIndexOfUnchecked) \
V(StringParseFloat) \ V(StringParseFloat) \
V(StringParseInt) \ V(StringParseInt) \
V(SymbolDescriptiveString) \ V(SymbolDescriptiveString) \

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include 'src/builtins/builtins-string-gen.h'
@abstract @abstract
@generateCppClass @generateCppClass
@reserveBitsInInstanceType(6) @reserveBitsInInstanceType(6)
@ -306,3 +308,87 @@ macro TwoStringsToSlices<Result: type, Functor: type>(
} }
} }
} }
macro StaticAssertStringLengthFitsSmi(): void {
const kMaxStringLengthFitsSmi: constexpr bool =
kStringMaxLengthUintptr < kSmiMaxValue;
static_assert(kMaxStringLengthFitsSmi);
}
extern macro StringBuiltinsAssembler::SearchOneByteStringInTwoByteString(
RawPtr<char16>, intptr, RawPtr<char8>, intptr, intptr): intptr;
extern macro StringBuiltinsAssembler::SearchOneByteStringInOneByteString(
RawPtr<char8>, intptr, RawPtr<char8>, intptr, intptr): intptr;
extern macro StringBuiltinsAssembler::SearchTwoByteStringInTwoByteString(
RawPtr<char16>, intptr, RawPtr<char16>, intptr, intptr): intptr;
extern macro StringBuiltinsAssembler::SearchTwoByteStringInOneByteString(
RawPtr<char8>, intptr, RawPtr<char16>, intptr, intptr): intptr;
extern macro StringBuiltinsAssembler::SearchOneByteInOneByteString(
RawPtr<char8>, intptr, RawPtr<char8>, intptr): intptr;
macro AbstractStringIndexOf(
subject: RawPtr<char16>, subjectLen: intptr, search: RawPtr<char8>,
searchLen: intptr, fromIndex: intptr): intptr {
return SearchOneByteStringInTwoByteString(
subject, subjectLen, search, searchLen, fromIndex);
}
macro AbstractStringIndexOf(
subject: RawPtr<char8>, subjectLen: intptr, search: RawPtr<char8>,
searchLen: intptr, fromIndex: intptr): intptr {
if (searchLen == 1) {
return SearchOneByteInOneByteString(subject, subjectLen, search, fromIndex);
}
return SearchOneByteStringInOneByteString(
subject, subjectLen, search, searchLen, fromIndex);
}
macro AbstractStringIndexOf(
subject: RawPtr<char16>, subjectLen: intptr, search: RawPtr<char16>,
searchLen: intptr, fromIndex: intptr): intptr {
return SearchTwoByteStringInTwoByteString(
subject, subjectLen, search, searchLen, fromIndex);
}
macro AbstractStringIndexOf(
subject: RawPtr<char8>, subjectLen: intptr, search: RawPtr<char16>,
searchLen: intptr, fromIndex: intptr): intptr {
return SearchTwoByteStringInOneByteString(
subject, subjectLen, search, searchLen, fromIndex);
}
struct AbstractStringIndexOfFunctor {
fromIndex: Smi;
}
// Ideally, this would be a method of AbstractStringIndexOfFunctor, but
// currently methods don't support templates.
macro Call<A: type, B: type>(
self: AbstractStringIndexOfFunctor, string: ConstSlice<A>,
searchStr: ConstSlice<B>): Smi {
return Convert<Smi>(AbstractStringIndexOf(
string.GCUnsafeStartPointer(), string.length,
searchStr.GCUnsafeStartPointer(), searchStr.length,
Convert<intptr>(self.fromIndex)));
}
macro AbstractStringIndexOf(implicit context: Context)(
string: String, searchString: String, fromIndex: Smi): Smi {
// Special case the empty string.
const searchStringLength = searchString.length_intptr;
const stringLength = string.length_intptr;
if (searchStringLength == 0 && SmiUntag(fromIndex) <= stringLength) {
return fromIndex;
}
// Don't bother to search if the searchString would go past the end
// of the string. This is actually necessary because of runtime
// checks.
if (SmiUntag(fromIndex) + searchStringLength > stringLength) {
return -1;
}
return TwoStringsToSlices<Smi>(
string, searchString, AbstractStringIndexOfFunctor{fromIndex: fromIndex});
}
builtin StringIndexOf(implicit context: Context)(
s: String, searchString: String, start: Smi): Smi {
return AbstractStringIndexOf(s, searchString, SmiMax(start, 0));
}

View File

@ -139,72 +139,6 @@ RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {
return isolate->StackOverflow(); return isolate->StackOverflow();
} }
// ES6 #sec-string.prototype.includes
// String.prototype.includes(searchString [, position])
RUNTIME_FUNCTION(Runtime_StringIncludes) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<Object> receiver = args.at(0);
if (receiver->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined,
isolate->factory()->NewStringFromAsciiChecked(
"String.prototype.includes")));
}
Handle<String> receiver_string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver_string,
Object::ToString(isolate, receiver));
// Check if the search string is a regExp and fail if it is.
Handle<Object> search = args.at(1);
Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
if (is_reg_exp.IsNothing()) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception();
}
if (is_reg_exp.FromJust()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
isolate->factory()->NewStringFromStaticChars(
"String.prototype.includes")));
}
Handle<String> search_string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
Object::ToString(isolate, args.at(1)));
Handle<Object> position;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
Object::ToInteger(isolate, args.at(2)));
uint32_t index = receiver_string->ToValidIndex(*position);
int index_in_str =
String::IndexOf(isolate, receiver_string, search_string, index);
return *isolate->factory()->ToBoolean(index_in_str != -1);
}
// ES6 #sec-string.prototype.indexof
// String.prototype.indexOf(searchString [, position])
RUNTIME_FUNCTION(Runtime_StringIndexOf) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
return String::IndexOf(isolate, args.at(0), args.at(1), args.at(2));
}
// ES6 #sec-string.prototype.indexof
// String.prototype.indexOf(searchString, position)
// Fast version that assumes that does not perform conversions of the incoming
// arguments.
RUNTIME_FUNCTION(Runtime_StringIndexOfUnchecked) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<String> receiver_string = args.at<String>(0);
Handle<String> search_string = args.at<String>(1);
int index = std::min(std::max(args.smi_at(2), 0), receiver_string->length());
return Smi::FromInt(String::IndexOf(isolate, receiver_string, search_string,
static_cast<uint32_t>(index)));
}
RUNTIME_FUNCTION(Runtime_StringLastIndexOf) { RUNTIME_FUNCTION(Runtime_StringLastIndexOf) {
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
return String::LastIndexOf(isolate, args.at(0), args.at(1), return String::LastIndexOf(isolate, args.at(0), args.at(1),

View File

@ -431,9 +431,6 @@ namespace internal {
F(StringEscapeQuotes, 1, 1) \ F(StringEscapeQuotes, 1, 1) \
F(StringGreaterThan, 2, 1) \ F(StringGreaterThan, 2, 1) \
F(StringGreaterThanOrEqual, 2, 1) \ F(StringGreaterThanOrEqual, 2, 1) \
F(StringIncludes, 3, 1) \
F(StringIndexOf, 3, 1) \
F(StringIndexOfUnchecked, 3, 1) \
F(StringLastIndexOf, 2, 1) \ F(StringLastIndexOf, 2, 1) \
F(StringLessThan, 2, 1) \ F(StringLessThan, 2, 1) \
F(StringLessThanOrEqual, 2, 1) \ F(StringLessThanOrEqual, 2, 1) \

View File

@ -465,27 +465,27 @@ KNOWN_OBJECTS = {
("old_space", 0x029a1): "StringSplitCache", ("old_space", 0x029a1): "StringSplitCache",
("old_space", 0x02da9): "RegExpMultipleCache", ("old_space", 0x02da9): "RegExpMultipleCache",
("old_space", 0x031b1): "BuiltinsConstantsTable", ("old_space", 0x031b1): "BuiltinsConstantsTable",
("old_space", 0x035a9): "AsyncFunctionAwaitRejectSharedFun", ("old_space", 0x035b1): "AsyncFunctionAwaitRejectSharedFun",
("old_space", 0x035cd): "AsyncFunctionAwaitResolveSharedFun", ("old_space", 0x035d5): "AsyncFunctionAwaitResolveSharedFun",
("old_space", 0x035f1): "AsyncGeneratorAwaitRejectSharedFun", ("old_space", 0x035f9): "AsyncGeneratorAwaitRejectSharedFun",
("old_space", 0x03615): "AsyncGeneratorAwaitResolveSharedFun", ("old_space", 0x0361d): "AsyncGeneratorAwaitResolveSharedFun",
("old_space", 0x03639): "AsyncGeneratorYieldResolveSharedFun", ("old_space", 0x03641): "AsyncGeneratorYieldResolveSharedFun",
("old_space", 0x0365d): "AsyncGeneratorReturnResolveSharedFun", ("old_space", 0x03665): "AsyncGeneratorReturnResolveSharedFun",
("old_space", 0x03681): "AsyncGeneratorReturnClosedRejectSharedFun", ("old_space", 0x03689): "AsyncGeneratorReturnClosedRejectSharedFun",
("old_space", 0x036a5): "AsyncGeneratorReturnClosedResolveSharedFun", ("old_space", 0x036ad): "AsyncGeneratorReturnClosedResolveSharedFun",
("old_space", 0x036c9): "AsyncIteratorValueUnwrapSharedFun", ("old_space", 0x036d1): "AsyncIteratorValueUnwrapSharedFun",
("old_space", 0x036ed): "PromiseAllResolveElementSharedFun", ("old_space", 0x036f5): "PromiseAllResolveElementSharedFun",
("old_space", 0x03711): "PromiseAllSettledResolveElementSharedFun", ("old_space", 0x03719): "PromiseAllSettledResolveElementSharedFun",
("old_space", 0x03735): "PromiseAllSettledRejectElementSharedFun", ("old_space", 0x0373d): "PromiseAllSettledRejectElementSharedFun",
("old_space", 0x03759): "PromiseAnyRejectElementSharedFun", ("old_space", 0x03761): "PromiseAnyRejectElementSharedFun",
("old_space", 0x0377d): "PromiseCapabilityDefaultRejectSharedFun", ("old_space", 0x03785): "PromiseCapabilityDefaultRejectSharedFun",
("old_space", 0x037a1): "PromiseCapabilityDefaultResolveSharedFun", ("old_space", 0x037a9): "PromiseCapabilityDefaultResolveSharedFun",
("old_space", 0x037c5): "PromiseCatchFinallySharedFun", ("old_space", 0x037cd): "PromiseCatchFinallySharedFun",
("old_space", 0x037e9): "PromiseGetCapabilitiesExecutorSharedFun", ("old_space", 0x037f1): "PromiseGetCapabilitiesExecutorSharedFun",
("old_space", 0x0380d): "PromiseThenFinallySharedFun", ("old_space", 0x03815): "PromiseThenFinallySharedFun",
("old_space", 0x03831): "PromiseThrowerFinallySharedFun", ("old_space", 0x03839): "PromiseThrowerFinallySharedFun",
("old_space", 0x03855): "PromiseValueThunkFinallySharedFun", ("old_space", 0x0385d): "PromiseValueThunkFinallySharedFun",
("old_space", 0x03879): "ProxyRevokeSharedFun", ("old_space", 0x03881): "ProxyRevokeSharedFun",
} }
# Lower 32 bits of first page addresses for various heap spaces. # Lower 32 bits of first page addresses for various heap spaces.