[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:
parent
9bd7c5e1bd
commit
7be93470c7
@ -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)(
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) \
|
||||
|
Loading…
Reference in New Issue
Block a user