[torque] Port String.prototype.match/search

- No noticeable changes in runtime performance according to js-perf-test/RegExp/{base_search.js, base_match.js} benchmarks
- Slight builtin code sizes reduction:

BEFORE
======
TFJ Builtin, StringPrototypeSearch, 1592
TFJ Builtin, StringPrototypeMatch, 1592

AFTER
=====
TFJ Builtin, StringPrototypeSearch, 1432
TFJ Builtin, StringPrototypeMatch, 1432

Bug: v8:8996
Change-Id: Ifeadac1c924a36003a41be3b805438522f8188be
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2670247
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Peter Wong <peter.wm.wong@gmail.com>
Cr-Commit-Position: refs/heads/master@{#72592}
This commit is contained in:
Peter Wong 2021-02-03 10:43:10 -06:00 committed by Commit Bot
parent 5cc7bfd04c
commit 3985d6a461
9 changed files with 134 additions and 93 deletions

View File

@ -1327,6 +1327,7 @@ torque_files = [
"src/builtins/string-includes.tq",
"src/builtins/string-indexof.tq",
"src/builtins/string-iterator.tq",
"src/builtins/string-match-search.tq",
"src/builtins/string-pad.tq",
"src/builtins/string-repeat.tq",
"src/builtins/string-replaceall.tq",

View File

@ -465,6 +465,7 @@ extern macro NameStringConstant(): String;
extern macro NullConstant(): Null;
extern macro NumberStringConstant(): String;
extern macro ReturnStringConstant(): String;
extern macro SearchSymbolConstant(): Symbol;
extern macro StringStringConstant(): String;
extern macro TheHoleConstant(): TheHole;
extern macro ToPrimitiveSymbolConstant(): PublicSymbol;

View File

@ -751,16 +751,12 @@ namespace internal {
TFJ(StringFromCharCode, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.prototype.lastindexof */ \
CPP(StringPrototypeLastIndexOf) \
/* ES6 #sec-string.prototype.match */ \
TFJ(StringPrototypeMatch, 1, kReceiver, kRegexp) \
/* ES #sec-string.prototype.matchAll */ \
TFJ(StringPrototypeMatchAll, 1, kReceiver, kRegexp) \
/* ES6 #sec-string.prototype.localecompare */ \
CPP(StringPrototypeLocaleCompare) \
/* ES6 #sec-string.prototype.replace */ \
TFJ(StringPrototypeReplace, 2, kReceiver, kSearch, kReplace) \
/* ES6 #sec-string.prototype.search */ \
TFJ(StringPrototypeSearch, 1, kReceiver, kRegexp) \
/* ES6 #sec-string.prototype.split */ \
TFJ(StringPrototypeSplit, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-string.raw */ \

View File

@ -858,6 +858,29 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(
prototype_check_assembler.CheckAndBranch(prototype, if_isunmodified,
if_ismodified);
}
void RegExpBuiltinsAssembler::BranchIfFastRegExpForSearch(
TNode<Context> context, TNode<HeapObject> object, Label* if_isunmodified,
Label* if_ismodified) {
BranchIfFastRegExp(
context, object, LoadMap(object),
PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
DescriptorIndexNameValue{JSRegExp::kSymbolSearchFunctionDescriptorIndex,
RootIndex::ksearch_symbol,
Context::REGEXP_SEARCH_FUNCTION_INDEX},
if_isunmodified, if_ismodified);
}
void RegExpBuiltinsAssembler::BranchIfFastRegExpForMatch(
TNode<Context> context, TNode<HeapObject> object, Label* if_isunmodified,
Label* if_ismodified) {
BranchIfFastRegExp(
context, object, LoadMap(object),
PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
DescriptorIndexNameValue{JSRegExp::kSymbolMatchFunctionDescriptorIndex,
RootIndex::kmatch_symbol,
Context::REGEXP_MATCH_FUNCTION_INDEX},
if_isunmodified, if_ismodified);
}
void RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
TNode<Context> context, TNode<HeapObject> object, Label* if_isunmodified,

View File

@ -98,6 +98,14 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
base::Optional<DescriptorIndexNameValue> additional_property_to_check,
Label* if_isunmodified, Label* if_ismodified);
void BranchIfFastRegExpForSearch(TNode<Context> context,
TNode<HeapObject> object,
Label* if_isunmodified,
Label* if_ismodified);
void BranchIfFastRegExpForMatch(TNode<Context> context,
TNode<HeapObject> object,
Label* if_isunmodified, Label* if_ismodified);
// Strict: Does not tolerate any changes to the prototype map.
// Permissive: Allows changes to the prototype map except for the exec
// property.

View File

@ -1160,87 +1160,6 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
}
}
class StringMatchSearchAssembler : public StringBuiltinsAssembler {
public:
explicit StringMatchSearchAssembler(compiler::CodeAssemblerState* state)
: StringBuiltinsAssembler(state) {}
protected:
enum Variant { kMatch, kSearch };
void Generate(Variant variant, const char* method_name,
TNode<Object> receiver, TNode<Object> maybe_regexp,
TNode<Context> context) {
Label call_regexp_match_search(this);
Builtins::Name builtin;
Handle<Symbol> symbol;
DescriptorIndexNameValue property_to_check;
if (variant == kMatch) {
builtin = Builtins::kRegExpMatchFast;
symbol = isolate()->factory()->match_symbol();
property_to_check = DescriptorIndexNameValue{
JSRegExp::kSymbolMatchFunctionDescriptorIndex,
RootIndex::kmatch_symbol, Context::REGEXP_MATCH_FUNCTION_INDEX};
} else {
builtin = Builtins::kRegExpSearchFast;
symbol = isolate()->factory()->search_symbol();
property_to_check = DescriptorIndexNameValue{
JSRegExp::kSymbolSearchFunctionDescriptorIndex,
RootIndex::ksearch_symbol, Context::REGEXP_SEARCH_FUNCTION_INDEX};
}
RequireObjectCoercible(context, receiver, method_name);
MaybeCallFunctionAtSymbol(
context, maybe_regexp, receiver, symbol, property_to_check,
[=] { Return(CallBuiltin(builtin, context, maybe_regexp, receiver)); },
[=](TNode<Object> fn) {
Return(Call(context, fn, maybe_regexp, receiver));
});
// maybe_regexp is not a RegExp nor has [@@match / @@search] property.
{
RegExpBuiltinsAssembler regexp_asm(state());
TNode<String> receiver_string = ToString_Inline(context, receiver);
TNode<NativeContext> native_context = LoadNativeContext(context);
TNode<HeapObject> regexp_function = CAST(
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX));
TNode<Map> initial_map = CAST(LoadObjectField(
regexp_function, JSFunction::kPrototypeOrInitialMapOffset));
TNode<Object> regexp = regexp_asm.RegExpCreate(
context, initial_map, maybe_regexp, EmptyStringConstant());
// TODO(jgruber): Handle slow flag accesses on the fast path and make this
// permissive.
Label fast_path(this), slow_path(this);
regexp_asm.BranchIfFastRegExp(
context, CAST(regexp), initial_map,
PrototypeCheckAssembler::kCheckPrototypePropertyConstness,
property_to_check, &fast_path, &slow_path);
BIND(&fast_path);
Return(CallBuiltin(builtin, context, regexp, receiver_string));
BIND(&slow_path);
{
TNode<Object> maybe_func = GetProperty(context, regexp, symbol);
Return(Call(context, maybe_func, regexp, receiver_string));
}
}
}
};
// ES6 #sec-string.prototype.match
TF_BUILTIN(StringPrototypeMatch, StringMatchSearchAssembler) {
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto maybe_regexp = Parameter<Object>(Descriptor::kRegexp);
auto context = Parameter<Context>(Descriptor::kContext);
Generate(kMatch, "String.prototype.match", receiver, maybe_regexp, context);
}
// ES #sec-string.prototype.matchAll
TF_BUILTIN(StringPrototypeMatchAll, StringBuiltinsAssembler) {
char const* method_name = "String.prototype.matchAll";
@ -1340,14 +1259,6 @@ TF_BUILTIN(StringPrototypeMatchAll, StringBuiltinsAssembler) {
Return(Call(context, match_all_func, rx, s));
}
// ES6 #sec-string.prototype.search
TF_BUILTIN(StringPrototypeSearch, StringMatchSearchAssembler) {
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto maybe_regexp = Parameter<Object>(Descriptor::kRegexp);
auto context = Parameter<Context>(Descriptor::kContext);
Generate(kSearch, "String.prototype.search", receiver, maybe_regexp, context);
}
TNode<JSArray> StringBuiltinsAssembler::StringToArray(
TNode<NativeContext> context, TNode<String> subject_string,
TNode<Smi> subject_length, TNode<Number> limit_number) {

View File

@ -6,6 +6,20 @@
namespace regexp {
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForMatch(
implicit context: Context)(HeapObject): never labels IsFast,
IsSlow;
macro IsFastRegExpForMatch(implicit context: Context)(o: HeapObject): bool {
BranchIfFastRegExpForMatch(o) otherwise return true, return false;
}
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForSearch(
implicit context: Context)(HeapObject): never labels IsFast,
IsSlow;
macro IsFastRegExpForSearch(implicit context: Context)(o: HeapObject): bool {
BranchIfFastRegExpForSearch(o) otherwise return true, return false;
}
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
implicit context: Context)(HeapObject): never labels IsFast,
IsSlow;

View File

@ -0,0 +1,86 @@
// 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.
namespace string {
struct StringMatchFunctor {
macro FnSymbol(): Symbol {
return MatchSymbolConstant();
}
macro CanCallFast(implicit context: Context)(maybeRegExp: HeapObject): bool {
return regexp::IsFastRegExpForMatch(maybeRegExp);
}
transitioning macro CallFast(implicit context: Context)(
regexp: FastJSRegExp, string: String): JSAny {
return regexp::RegExpMatchFast(regexp, string);
}
}
struct StringSearchFunctor {
macro FnSymbol(): Symbol {
return SearchSymbolConstant();
}
macro CanCallFast(implicit context: Context)(maybeRegExp: HeapObject): bool {
return regexp::IsFastRegExpForSearch(maybeRegExp);
}
transitioning macro CallFast(implicit context: Context)(
regexp: FastJSRegExp, string: String): JSAny {
return regexp::RegExpSearchFast(regexp, string);
}
}
transitioning macro StringMatchSearch<F: type>(
implicit context: NativeContext, receiver: JSAny)(
regexp: JSAny, functor: F, methodName: constexpr string): JSAny {
// 1. Let O be ? RequireObjectCoercible(this value).
RequireObjectCoercible(receiver, methodName);
try {
// 3. Let string be ? ToString(O).
const string = Cast<String>(receiver) otherwise Slow;
const heapRegexp = Cast<HeapObject>(regexp) otherwise Slow;
if (!functor.CanCallFast(heapRegexp)) goto Slow;
return functor.CallFast(UnsafeCast<FastJSRegExp>(heapRegexp), string);
} label Slow deferred {
// 2. If regexp is neither undefined nor null, then
if (regexp != Undefined && regexp != Null) {
try {
// a. Let fn be ? GetMethod(regexp, @@match/@@search).
// b. If fn is not undefined, then
const fn = GetMethod(regexp, functor.FnSymbol())
otherwise FnSymbolIsNullOrUndefined;
// i. Return ? Call(fn, regexp, « O »).
return Call(context, fn, regexp, receiver);
} label FnSymbolIsNullOrUndefined {}
}
// 3. Let string be ? ToString(O).
const string = ToString_Inline(receiver);
// 4. Let rx be ? RegExpCreate(regexp, undefined).
const rx = regexp::RegExpCreate(context, regexp, kEmptyString);
// 5. Return ? Invoke(rx, @@match/@@search, « string »).
const fn = GetProperty(rx, functor.FnSymbol());
return Call(context, fn, rx, string);
}
}
// https://tc39.es/ecma262/#sec-string.prototype.match
transitioning javascript builtin
StringPrototypeMatch(
js-implicit context: NativeContext, receiver: JSAny)(regexp: JSAny): JSAny {
return StringMatchSearch(
regexp, StringMatchFunctor{}, 'String.prototype.match');
}
// https://tc39.es/ecma262/#sec-string.prototype.search
transitioning javascript builtin
StringPrototypeSearch(
js-implicit context: NativeContext, receiver: JSAny)(regexp: JSAny): JSAny {
return StringMatchSearch(
regexp, StringSearchFunctor{}, 'String.prototype.search');
}
}

View File

@ -189,6 +189,7 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
V(regexp_to_string, regexp_to_string, RegexpToString) \
V(resolve_string, resolve_string, ResolveString) \
V(return_string, return_string, ReturnString) \
V(search_symbol, search_symbol, SearchSymbol) \
V(species_symbol, species_symbol, SpeciesSymbol) \
V(StaleRegister, stale_register, StaleRegister) \
V(StoreHandler0Map, store_handler0_map, StoreHandler0Map) \