[performance|regexp] Avoid unnecessary JSArray creation

Matches were transmitted in a JSArray, although a FixedArray is
enough.

Change-Id: I71145c6b55d57a15e330a3865f00d038e613dde3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4171631
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85332}
This commit is contained in:
Marja Hölttä 2023-01-17 11:32:05 +01:00 committed by V8 LUCI CQ
parent 9bd7c5e1bd
commit 7be93470c7
4 changed files with 58 additions and 72 deletions

View File

@ -10,21 +10,23 @@ extern builtin
SubString(implicit context: Context)(String, Smi, Smi): String;
extern runtime RegExpExecMultiple(implicit context: Context)(
JSRegExp, String, RegExpMatchInfo, JSArray): Null|JSArray;
JSRegExp, String, RegExpMatchInfo): Null|FixedArray;
extern transitioning runtime
RegExpReplaceRT(Context, JSReceiver, String, Object): String;
extern transitioning runtime
StringBuilderConcat(implicit context: Context)(JSArray, Smi, String): String;
StringBuilderConcat(implicit context: Context)(FixedArray, Smi, String): String;
extern transitioning runtime
StringReplaceNonGlobalRegExpWithFunction(implicit context: Context)(
String, JSRegExp, Callable): String;
// matchesCapacity is the length of the matchesElements FixedArray, and
// matchesElements is allowed to contain holes at the end.
transitioning macro RegExpReplaceCallableNoExplicitCaptures(
implicit context: Context)(
matchesElements: FixedArray, matchesLength: intptr, string: String,
replaceFn: Callable): void {
matchesElements: FixedArray, matchesCapacity: intptr, string: String,
replaceFn: Callable): intptr {
let matchStart: Smi = 0;
for (let i: intptr = 0; i < matchesLength; i++) {
for (let i: intptr = 0; i < matchesCapacity; i++) {
typeswitch (matchesElements.objects[i]) {
// Element represents a slice.
case (elSmi: Smi): {
@ -54,18 +56,29 @@ transitioning macro RegExpReplaceCallableNoExplicitCaptures(
matchesElements.objects[i] = replacement;
matchStart += elString.length_smi;
}
case (TheHole): deferred {
// No more elements.
return i;
}
case (Object): deferred {
unreachable;
}
}
}
return matchesCapacity;
}
// matchesCapacity is the length of the matchesElements FixedArray, and
// matchesElements is allowed to contain holes at the end.
transitioning macro
RegExpReplaceCallableWithExplicitCaptures(implicit context: Context)(
matchesElements: FixedArray, matchesLength: intptr,
replaceFn: Callable): void {
for (let i: intptr = 0; i < matchesLength; i++) {
matchesElements: FixedArray, matchesCapacity: intptr,
replaceFn: Callable): intptr {
for (let i: intptr = 0; i < matchesCapacity; i++) {
if (matchesElements.objects[i] == TheHole) {
// No more elements.
return i;
}
const elArray =
Cast<JSArray>(matchesElements.objects[i]) otherwise continue;
@ -78,29 +91,27 @@ RegExpReplaceCallableWithExplicitCaptures(implicit context: Context)(
// we got back from the callback function.
matchesElements.objects[i] = ToString_Inline(replacementObj);
}
return matchesCapacity;
}
transitioning macro RegExpReplaceFastGlobalCallable(implicit context: Context)(
regexp: FastJSRegExp, string: String, replaceFn: Callable): String {
regexp.lastIndex = 0;
const kInitialCapacity: intptr = 16;
const kInitialLength: Smi = 0;
const result: Null|JSArray = RegExpExecMultiple(
regexp, string, GetRegExpLastMatchInfo(),
AllocateJSArray(
ElementsKind::PACKED_ELEMENTS, GetFastPackedElementsJSArrayMap(),
kInitialCapacity, kInitialLength));
const result: Null|FixedArray =
RegExpExecMultiple(regexp, string, GetRegExpLastMatchInfo());
regexp.lastIndex = 0;
// If no matches, return the subject string.
if (result == Null) return string;
const matches: JSArray = UnsafeCast<JSArray>(result);
const matchesLength: Smi = Cast<Smi>(matches.length) otherwise unreachable;
const matchesLengthInt: intptr = Convert<intptr>(matchesLength);
const matchesElements: FixedArray = UnsafeCast<FixedArray>(matches.elements);
const matches: FixedArray = UnsafeCast<FixedArray>(result);
// The FixedArray will contain holes at the end and we've lost the information
// of its real length. This is OK because the users iterate it from the
// beginning.
const matchesCapacity: Smi = Cast<Smi>(matches.length) otherwise unreachable;
const matchesCapacityInt: intptr = Convert<intptr>(matchesCapacity);
// Reload last match info since it might have changed.
const nofCaptures: Smi = GetRegExpLastMatchInfo().NumberOfCaptures();
@ -108,15 +119,16 @@ transitioning macro RegExpReplaceFastGlobalCallable(implicit context: Context)(
// If the number of captures is two then there are no explicit captures in
// the regexp, just the implicit capture that captures the whole match. In
// this case we can simplify quite a bit and end up with something faster.
let matchesLength: intptr;
if (nofCaptures == 2) {
RegExpReplaceCallableNoExplicitCaptures(
matchesElements, matchesLengthInt, string, replaceFn);
matchesLength = RegExpReplaceCallableNoExplicitCaptures(
matches, matchesCapacityInt, string, replaceFn);
} else {
RegExpReplaceCallableWithExplicitCaptures(
matchesElements, matchesLengthInt, replaceFn);
matchesLength = RegExpReplaceCallableWithExplicitCaptures(
matches, matchesCapacityInt, replaceFn);
}
return StringBuilderConcat(matches, matchesLength, string);
return StringBuilderConcat(matches, Convert<Smi>(matchesLength), string);
}
transitioning macro RegExpReplaceFastString(implicit context: Context)(

View File

@ -1166,8 +1166,7 @@ Handle<JSObject> ConstructNamedCaptureGroupsObject(
template <bool has_capture>
static Object SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
Handle<JSRegExp> regexp,
Handle<RegExpMatchInfo> last_match_array,
Handle<JSArray> result_array) {
Handle<RegExpMatchInfo> last_match_array) {
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
DCHECK_NE(has_capture, regexp->capture_count() == 0);
DCHECK(subject->IsFlat());
@ -1197,9 +1196,10 @@ static Object SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
if (cached_answer.IsFixedArray()) {
int capture_registers = JSRegExp::RegistersForCaptureCount(capture_count);
int32_t* last_match = NewArray<int32_t>(capture_registers);
std::unique_ptr<int32_t[]> last_match(new int32_t[capture_registers]);
int32_t* raw_last_match = last_match.get();
for (int i = 0; i < capture_registers; i++) {
last_match[i] = Smi::ToInt(last_match_cache.get(i));
raw_last_match[i] = Smi::ToInt(last_match_cache.get(i));
}
Handle<FixedArray> cached_fixed_array =
Handle<FixedArray>(FixedArray::cast(cached_answer), isolate);
@ -1207,24 +1207,17 @@ static Object SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
Handle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
cached_fixed_array, isolate->factory()->fixed_array_map());
JSArray::SetContent(result_array, copied_fixed_array);
RegExp::SetLastMatchInfo(isolate, last_match_array, subject,
capture_count, last_match);
DeleteArray(last_match);
return *result_array;
capture_count, raw_last_match);
return *copied_fixed_array;
}
}
RegExpGlobalCache global_cache(regexp, subject, isolate);
if (global_cache.HasException()) return ReadOnlyRoots(isolate).exception();
// Ensured in Runtime_RegExpExecMultiple.
DCHECK(result_array->HasObjectElements());
Handle<FixedArray> result_elements(FixedArray::cast(result_array->elements()),
isolate);
if (result_elements->length() < 16) {
result_elements = isolate->factory()->NewFixedArrayWithHoles(16);
}
Handle<FixedArray> result_elements =
isolate->factory()->NewFixedArrayWithHoles(16);
FixedArrayBuilder builder(result_elements);
@ -1338,7 +1331,7 @@ static Object SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
isolate, subject, handle(regexp->data(), isolate), copied_fixed_array,
last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
}
return *builder.ToJSArray(result_array);
return *builder.array();
} else {
return ReadOnlyRoots(isolate).null_value(); // No matches at all.
}
@ -1465,26 +1458,24 @@ V8_WARN_UNUSED_RESULT MaybeHandle<String> RegExpReplace(
// This is only called for StringReplaceGlobalRegExpWithFunction.
RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) {
HandleScope handles(isolate);
DCHECK_EQ(4, args.length());
DCHECK_EQ(3, args.length());
Handle<JSRegExp> regexp = args.at<JSRegExp>(0);
Handle<String> subject = args.at<String>(1);
Handle<RegExpMatchInfo> last_match_info = args.at<RegExpMatchInfo>(2);
Handle<JSArray> result_array = args.at<JSArray>(3);
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
CHECK(result_array->HasObjectElements());
subject = String::Flatten(isolate, subject);
CHECK(regexp->flags() & JSRegExp::kGlobal);
Object result;
if (regexp->capture_count() == 0) {
result = SearchRegExpMultiple<false>(isolate, subject, regexp,
last_match_info, result_array);
result =
SearchRegExpMultiple<false>(isolate, subject, regexp, last_match_info);
} else {
result = SearchRegExpMultiple<true>(isolate, subject, regexp,
last_match_info, result_array);
result =
SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info);
}
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
return result;

View File

@ -232,38 +232,23 @@ RUNTIME_FUNCTION(Runtime_StringCharCodeAt) {
RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
Handle<JSArray> array = args.at<JSArray>(0);
int32_t array_length;
if (!args[1].ToInt32(&array_length)) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewInvalidStringLengthError());
}
Handle<String> special = args.at<String>(2);
Handle<FixedArray> array = args.at<FixedArray>(0);
size_t actual_array_length = 0;
CHECK(TryNumberToSize(array->length(), &actual_array_length));
CHECK_GE(array_length, 0);
CHECK(static_cast<size_t>(array_length) <= actual_array_length);
int array_length = args.smi_value_at(1);
Handle<String> special = args.at<String>(2);
// This assumption is used by the slice encoding in one or two smis.
DCHECK_GE(Smi::kMaxValue, String::kMaxLength);
CHECK(array->HasFastElements());
JSObject::EnsureCanContainHeapObjectElements(array);
int special_length = special->length();
if (!array->HasObjectElements()) {
return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string());
}
int length;
bool one_byte = special->IsOneByteRepresentation();
{
DisallowGarbageCollection no_gc;
FixedArray fixed_array = FixedArray::cast(array->elements());
if (fixed_array.length() < array_length) {
array_length = fixed_array.length();
}
FixedArray fixed_array = *array;
if (array_length == 0) {
return ReadOnlyRoots(isolate).empty_string();
@ -287,8 +272,7 @@ RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, answer, isolate->factory()->NewRawOneByteString(length));
DisallowGarbageCollection no_gc;
StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
FixedArray::cast(array->elements()),
StringBuilderConcatHelper(*special, answer->GetChars(no_gc), *array,
array_length);
return *answer;
} else {
@ -296,8 +280,7 @@ RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, answer, isolate->factory()->NewRawTwoByteString(length));
DisallowGarbageCollection no_gc;
StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
FixedArray::cast(array->elements()),
StringBuilderConcatHelper(*special, answer->GetChars(no_gc), *array,
array_length);
return *answer;
}

View File

@ -415,7 +415,7 @@ namespace internal {
F(RegExpExecTreatMatchAtEndAsFailure, 4, 1) \
F(RegExpExperimentalOneshotExec, 4, 1) \
F(RegExpExperimentalOneshotExecTreatMatchAtEndAsFailure, 4, 1) \
F(RegExpExecMultiple, 4, 1) \
F(RegExpExecMultiple, 3, 1) \
F(RegExpInitializeAndCompile, 3, 1) \
F(RegExpReplaceRT, 3, 1) \
F(RegExpSplit, 3, 1) \