Commit 10540937 authored by Joshua Litt's avatar Joshua Litt Committed by Commit Bot

Reland "[regexp] Re-execute regexp when '.indices' is accessed."

This is a reland of f2a74165

Original change's description:
> [regexp] Re-execute regexp when '.indices' is accessed.
>
> Instead of storing a pointer to the last_match_info, which may
> change, this cl modifies JSRegExpResult to store a pointer to
> the original JSRegExp which generated it, as well as additional
> data needed to re-execute the match.
>
> Basically a straight copy and tidy off jgruber@'s prototype:
> https://chromium-review.googlesource.com/c/v8/v8/+/1876810
>
> Bug: v8:9548
> Change-Id: I11b7deae681b8287e41e8d0e342291ff484751fb
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1910129
> Commit-Queue: Joshua Litt <joshualitt@chromium.org>
> Reviewed-by: Jakob Gruber <jgruber@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#65053}

Bug: v8:9548
Change-Id: Ieeba4b1ae59ef0c7946d654dc314adfae09d24b5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1925554Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65096}
parent 002d5be8
...@@ -851,9 +851,16 @@ void Accessors::RegExpResultIndicesGetter( ...@@ -851,9 +851,16 @@ void Accessors::RegExpResultIndicesGetter(
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<JSRegExpResult> regexp_result( Handle<JSRegExpResult> regexp_result(
Handle<JSRegExpResult>::cast(Utils::OpenHandle(*info.Holder()))); Handle<JSRegExpResult>::cast(Utils::OpenHandle(*info.Holder())));
Handle<Object> indices( MaybeHandle<JSArray> maybe_indices(
JSRegExpResult::GetAndCacheIndices(isolate, regexp_result)); JSRegExpResult::GetAndCacheIndices(isolate, regexp_result));
info.GetReturnValue().Set(Utils::ToLocal(indices)); Handle<JSArray> indices;
if (!maybe_indices.ToHandle(&indices)) {
isolate->OptionalRescheduleException(false);
Handle<Object> result = isolate->factory()->undefined_value();
info.GetReturnValue().Set(Utils::ToLocal(result));
} else {
info.GetReturnValue().Set(Utils::ToLocal(indices));
}
} }
Handle<AccessorInfo> Accessors::MakeRegExpResultIndicesInfo(Isolate* isolate) { Handle<AccessorInfo> Accessors::MakeRegExpResultIndicesInfo(Isolate* isolate) {
......
...@@ -78,7 +78,7 @@ TNode<RawPtrT> RegExpBuiltinsAssembler::LoadCodeObjectEntry(TNode<Code> code) { ...@@ -78,7 +78,7 @@ TNode<RawPtrT> RegExpBuiltinsAssembler::LoadCodeObjectEntry(TNode<Code> code) {
TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult( TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult(
TNode<Context> context, TNode<Smi> length, TNode<Smi> index, TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
TNode<String> input, TNode<RegExpMatchInfo> match_info, TNode<String> input, TNode<JSRegExp> regexp, TNode<Number> last_index,
TNode<FixedArray>* elements_out) { TNode<FixedArray>* elements_out) {
CSA_ASSERT(this, SmiLessThanOrEqual( CSA_ASSERT(this, SmiLessThanOrEqual(
length, SmiConstant(JSArray::kMaxFastArrayLength))); length, SmiConstant(JSArray::kMaxFastArrayLength)));
...@@ -117,10 +117,20 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult( ...@@ -117,10 +117,20 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult(
StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kNamesOffset, StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kNamesOffset,
undefined_value); undefined_value);
// Stash match_info in order to build JSRegExpResultIndices lazily when the // Stash regexp in order to re-execute and build JSRegExpResultIndices lazily
// 'indices' property is accessed. // when the 'indices' property is accessed.
StoreObjectField(result, JSRegExpResult::kCachedIndicesOrMatchInfoOffset, StoreObjectField(result, JSRegExpResult::kCachedIndicesOrRegexpOffset,
match_info); regexp);
StoreObjectField(result, JSRegExpResult::kRegexpInputOffset, input);
// If non-smi last_index then store an SmiZero instead.
{
TNode<Smi> last_index_smi = Select<Smi>(
TaggedIsSmi(last_index), [=] { return CAST(last_index); },
[=] { return SmiZero(); });
StoreObjectField(result, JSRegExpResult::kRegexpLastIndexOffset,
last_index_smi);
}
// Finish elements initialization. // Finish elements initialization.
...@@ -162,8 +172,9 @@ void RegExpBuiltinsAssembler::SlowStoreLastIndex(SloppyTNode<Context> context, ...@@ -162,8 +172,9 @@ void RegExpBuiltinsAssembler::SlowStoreLastIndex(SloppyTNode<Context> context,
} }
TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
TNode<Context> context, TNode<JSReceiver> maybe_regexp, TNode<Context> context, TNode<JSRegExp> regexp,
TNode<RegExpMatchInfo> match_info, TNode<String> string) { TNode<RegExpMatchInfo> match_info, TNode<String> string,
TNode<Number> last_index) {
Label named_captures(this), out(this); Label named_captures(this), out(this);
TNode<IntPtrT> num_indices = SmiUntag(CAST(UnsafeLoadFixedArrayElement( TNode<IntPtrT> num_indices = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
...@@ -181,8 +192,9 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( ...@@ -181,8 +192,9 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
CAST(CallBuiltin(Builtins::kSubString, context, string, start, end)); CAST(CallBuiltin(Builtins::kSubString, context, string, start, end));
TNode<FixedArray> result_elements; TNode<FixedArray> result_elements;
TNode<JSRegExpResult> result = AllocateRegExpResult( TNode<JSRegExpResult> result =
context, num_results, start, string, match_info, &result_elements); AllocateRegExpResult(context, num_results, start, string, regexp,
last_index, &result_elements);
UnsafeStoreFixedArrayElement(result_elements, 0, first); UnsafeStoreFixedArrayElement(result_elements, 0, first);
...@@ -231,16 +243,14 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( ...@@ -231,16 +243,14 @@ TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
{ {
CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1))); CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1)));
// We reach this point only if captures exist, implying that this is an
// IRREGEXP JSRegExp.
TNode<JSRegExp> regexp = CAST(maybe_regexp);
// Preparations for named capture properties. Exit early if the result does // Preparations for named capture properties. Exit early if the result does
// not have any named captures to minimize performance impact. // not have any named captures to minimize performance impact.
TNode<FixedArray> data = TNode<FixedArray> data =
CAST(LoadObjectField(regexp, JSRegExp::kDataOffset)); CAST(LoadObjectField(regexp, JSRegExp::kDataOffset));
// We reach this point only if captures exist, implying that this is an
// IRREGEXP JSRegExp.
CSA_ASSERT(this, CSA_ASSERT(this,
SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)), SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)),
SmiConstant(JSRegExp::IRREGEXP))); SmiConstant(JSRegExp::IRREGEXP)));
......
...@@ -27,7 +27,7 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -27,7 +27,7 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
// and input string. // and input string.
TNode<JSRegExpResult> AllocateRegExpResult( TNode<JSRegExpResult> AllocateRegExpResult(
TNode<Context> context, TNode<Smi> length, TNode<Smi> index, TNode<Context> context, TNode<Smi> length, TNode<Smi> index,
TNode<String> input, TNode<RegExpMatchInfo> match_info, TNode<String> input, TNode<JSRegExp> regexp, TNode<Number> last_index,
TNode<FixedArray>* elements_out = nullptr); TNode<FixedArray>* elements_out = nullptr);
TNode<Object> FastLoadLastIndexBeforeSmiCheck(TNode<JSRegExp> regexp); TNode<Object> FastLoadLastIndexBeforeSmiCheck(TNode<JSRegExp> regexp);
...@@ -58,8 +58,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler { ...@@ -58,8 +58,9 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
TNode<RegExpMatchInfo> match_info); TNode<RegExpMatchInfo> match_info);
TNode<JSRegExpResult> ConstructNewResultFromMatchInfo( TNode<JSRegExpResult> ConstructNewResultFromMatchInfo(
TNode<Context> context, TNode<JSReceiver> maybe_regexp, TNode<Context> context, TNode<JSRegExp> regexp,
TNode<RegExpMatchInfo> match_info, TNode<String> string); TNode<RegExpMatchInfo> match_info, TNode<String> string,
TNode<Number> last_index);
// Fast path check logic. // Fast path check logic.
// //
......
...@@ -161,12 +161,14 @@ namespace regexp { ...@@ -161,12 +161,14 @@ namespace regexp {
let isFastRegExp: bool = false; let isFastRegExp: bool = false;
try { try {
if (IsFastRegExpPermissive(iteratingRegExp)) { if (IsFastRegExpPermissive(iteratingRegExp)) {
const regexp = UnsafeCast<JSRegExp>(iteratingRegExp);
const lastIndex = LoadLastIndexAsLength(regexp, true);
const matchIndices: RegExpMatchInfo = const matchIndices: RegExpMatchInfo =
RegExpPrototypeExecBodyWithoutResultFast( RegExpPrototypeExecBodyWithoutResultFast(
UnsafeCast<JSRegExp>(iteratingRegExp), iteratingString) regexp, iteratingString, lastIndex)
otherwise IfNoMatch; otherwise IfNoMatch;
match = ConstructNewResultFromMatchInfo( match = ConstructNewResultFromMatchInfo(
iteratingRegExp, matchIndices, iteratingString); regexp, matchIndices, iteratingString, lastIndex);
isFastRegExp = true; isFastRegExp = true;
} else { } else {
match = RegExpExec(iteratingRegExp, iteratingString); match = RegExpExec(iteratingRegExp, iteratingString);
......
...@@ -20,7 +20,8 @@ namespace regexp { ...@@ -20,7 +20,8 @@ namespace regexp {
// Call exec. // Call exec.
try { try {
const matchIndices: RegExpMatchInfo = const matchIndices: RegExpMatchInfo =
RegExpPrototypeExecBodyWithoutResultFast(regexp, string) RegExpPrototypeExecBodyWithoutResultFast(
UnsafeCast<JSRegExp>(regexp), string)
otherwise DidNotMatch; otherwise DidNotMatch;
// Successful match. // Successful match.
......
...@@ -52,7 +52,7 @@ namespace regexp { ...@@ -52,7 +52,7 @@ namespace regexp {
} }
extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
implicit context: Context)(JSReceiver, RegExpMatchInfo, String): implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number):
JSRegExpResult; JSRegExpResult;
const kGlobalOrSticky: constexpr int31 const kGlobalOrSticky: constexpr int31
...@@ -70,31 +70,15 @@ namespace regexp { ...@@ -70,31 +70,15 @@ namespace regexp {
// IfDidNotMatch otherwise. // IfDidNotMatch otherwise.
transitioning macro RegExpPrototypeExecBodyWithoutResult(implicit context: transitioning macro RegExpPrototypeExecBodyWithoutResult(implicit context:
Context)( Context)(
maybeRegexp: JSReceiver, string: String, regexp: JSRegExp, string: String, regexpLastIndex: Number,
isFastPath: constexpr bool): RegExpMatchInfo labels IfDidNotMatch { isFastPath: constexpr bool): RegExpMatchInfo labels IfDidNotMatch {
if (isFastPath) { if (isFastPath) {
assert(HasInitialRegExpMap(maybeRegexp)); assert(HasInitialRegExpMap(regexp));
} else { } else {
IncrementUseCounter(context, SmiConstant(kRegExpExecCalledOnSlowRegExp)); IncrementUseCounter(context, SmiConstant(kRegExpExecCalledOnSlowRegExp));
if (!Is<JSRegExp>(maybeRegexp)) {
ThrowTypeError(
kIncompatibleMethodReceiver, 'RegExp.prototype.exec', maybeRegexp);
}
} }
const regexp = UnsafeCast<JSRegExp>(maybeRegexp);
let lastIndex: Number; let lastIndex = regexpLastIndex;
const regexpLastIndex = LoadLastIndex(regexp, isFastPath);
if (isFastPath) {
// ToLength on a positive smi is a nop and can be skipped.
assert(TaggedIsPositiveSmi(regexpLastIndex));
lastIndex = UnsafeCast<Number>(regexpLastIndex);
} else {
// Omit ToLength if lastindex is a non-negative smi.
lastIndex = TaggedIsPositiveSmi(regexpLastIndex) ?
UnsafeCast<Number>(regexpLastIndex) :
ToLength_Inline(regexpLastIndex);
}
// Check whether the regexp is global or sticky, which determines whether we // Check whether the regexp is global or sticky, which determines whether we
// update last index later on. // update last index later on.
...@@ -141,18 +125,35 @@ namespace regexp { ...@@ -141,18 +125,35 @@ namespace regexp {
transitioning macro RegExpPrototypeExecBodyWithoutResultFast( transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
implicit context: Context)(regexp: JSRegExp, string: String): implicit context: Context)(regexp: JSRegExp, string: String):
RegExpMatchInfo labels IfDidNotMatch { RegExpMatchInfo labels IfDidNotMatch {
return RegExpPrototypeExecBodyWithoutResult(regexp, string, true) const lastIndex = LoadLastIndexAsLength(regexp, true);
return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
otherwise IfDidNotMatch;
}
transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
implicit context:
Context)(regexp: JSRegExp, string: String, lastIndex: Number):
RegExpMatchInfo labels IfDidNotMatch {
return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
otherwise IfDidNotMatch; otherwise IfDidNotMatch;
} }
// ES#sec-regexp.prototype.exec // ES#sec-regexp.prototype.exec
// RegExp.prototype.exec ( string ) // RegExp.prototype.exec ( string )
transitioning macro RegExpPrototypeExecBody(implicit context: Context)( transitioning macro RegExpPrototypeExecBody(implicit context: Context)(
maybeRegexp: JSReceiver, string: String, receiver: JSReceiver, string: String, isFastPath: constexpr bool): JSAny {
isFastPath: constexpr bool): JSAny { let regexp: JSRegExp;
if constexpr (isFastPath) {
regexp = UnsafeCast<JSRegExp>(receiver);
} else {
regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
kIncompatibleMethodReceiver, 'RegExp.prototype.exec', receiver);
}
const lastIndex = LoadLastIndexAsLength(regexp, isFastPath);
const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult( const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult(
maybeRegexp, string, isFastPath) otherwise return Null; regexp, string, lastIndex, isFastPath) otherwise return Null;
return ConstructNewResultFromMatchInfo(maybeRegexp, matchIndices, string); return ConstructNewResultFromMatchInfo(
regexp, matchIndices, string, lastIndex);
} }
macro LoadRegExpFunction(implicit context: Context)( macro LoadRegExpFunction(implicit context: Context)(
...@@ -322,6 +323,26 @@ namespace regexp { ...@@ -322,6 +323,26 @@ namespace regexp {
SlowLoadLastIndex(regexp); SlowLoadLastIndex(regexp);
} }
@export
transitioning macro LoadLastIndexAsLength(implicit context: Context)(
regexp: JSRegExp, isFastPath: constexpr bool): Number {
const lastIndex = LoadLastIndex(regexp, isFastPath);
if (isFastPath) {
// ToLength on a positive smi is a nop and can be skipped.
return UnsafeCast<PositiveSmi>(lastIndex);
} else {
// Omit ToLength if last_index is a non-negative smi.
typeswitch (lastIndex) {
case (i: PositiveSmi): {
return i;
}
case (o: JSAny): {
return ToLength_Inline(o);
}
}
}
}
@export @export
transitioning macro StoreLastIndex(implicit context: Context)( transitioning macro StoreLastIndex(implicit context: Context)(
regexp: JSAny, value: Number, isFastPath: constexpr bool): void { regexp: JSAny, value: Number, isFastPath: constexpr bool): void {
......
...@@ -4842,14 +4842,15 @@ bool Genesis::InstallABunchOfRandomThings() { ...@@ -4842,14 +4842,15 @@ bool Genesis::InstallABunchOfRandomThings() {
// Private internal only fields. All of the remaining fields have special // Private internal only fields. All of the remaining fields have special
// symbols to prevent their use in Javascript. // symbols to prevent their use in Javascript.
// cached_indices_or_match_info descriptor.
{ {
PropertyAttributes attribs = DONT_ENUM; PropertyAttributes attribs = DONT_ENUM;
// cached_indices_or_regexp descriptor.
{ {
Descriptor d = Descriptor::DataField( Descriptor d = Descriptor::DataField(
isolate(), isolate(),
factory()->regexp_result_cached_indices_or_match_info_symbol(), factory()->regexp_result_cached_indices_or_regexp_symbol(),
JSRegExpResult::kCachedIndicesOrMatchInfoIndex, attribs, JSRegExpResult::kCachedIndicesOrRegExpIndex, attribs,
Representation::Tagged()); Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d); initial_map->AppendDescriptor(isolate(), &d);
} }
...@@ -4861,6 +4862,24 @@ bool Genesis::InstallABunchOfRandomThings() { ...@@ -4861,6 +4862,24 @@ bool Genesis::InstallABunchOfRandomThings() {
JSRegExpResult::kNamesIndex, attribs, Representation::Tagged()); JSRegExpResult::kNamesIndex, attribs, Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d); initial_map->AppendDescriptor(isolate(), &d);
} }
// regexp_input_index descriptor.
{
Descriptor d = Descriptor::DataField(
isolate(), factory()->regexp_result_regexp_input_symbol(),
JSRegExpResult::kRegExpInputIndex, attribs,
Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
}
// regexp_last_index descriptor.
{
Descriptor d = Descriptor::DataField(
isolate(), factory()->regexp_result_regexp_last_index_symbol(),
JSRegExpResult::kRegExpLastIndex, attribs,
Representation::Tagged());
initial_map->AppendDescriptor(isolate(), &d);
}
} }
native_context()->set_regexp_result_map(*initial_map); native_context()->set_regexp_result_map(*initial_map);
......
...@@ -326,36 +326,38 @@ ...@@ -326,36 +326,38 @@
V(_, writable_string, "writable") \ V(_, writable_string, "writable") \
V(_, zero_string, "0") V(_, zero_string, "0")
#define PRIVATE_SYMBOL_LIST_GENERATOR(V, _) \ #define PRIVATE_SYMBOL_LIST_GENERATOR(V, _) \
V(_, call_site_frame_array_symbol) \ V(_, call_site_frame_array_symbol) \
V(_, call_site_frame_index_symbol) \ V(_, call_site_frame_index_symbol) \
V(_, console_context_id_symbol) \ V(_, console_context_id_symbol) \
V(_, console_context_name_symbol) \ V(_, console_context_name_symbol) \
V(_, class_fields_symbol) \ V(_, class_fields_symbol) \
V(_, class_positions_symbol) \ V(_, class_positions_symbol) \
V(_, detailed_stack_trace_symbol) \ V(_, detailed_stack_trace_symbol) \
V(_, elements_transition_symbol) \ V(_, elements_transition_symbol) \
V(_, error_end_pos_symbol) \ V(_, error_end_pos_symbol) \
V(_, error_script_symbol) \ V(_, error_script_symbol) \
V(_, error_start_pos_symbol) \ V(_, error_start_pos_symbol) \
V(_, frozen_symbol) \ V(_, frozen_symbol) \
V(_, generic_symbol) \ V(_, generic_symbol) \
V(_, home_object_symbol) \ V(_, home_object_symbol) \
V(_, interpreter_trampoline_symbol) \ V(_, interpreter_trampoline_symbol) \
V(_, megamorphic_symbol) \ V(_, megamorphic_symbol) \
V(_, native_context_index_symbol) \ V(_, native_context_index_symbol) \
V(_, nonextensible_symbol) \ V(_, nonextensible_symbol) \
V(_, not_mapped_symbol) \ V(_, not_mapped_symbol) \
V(_, promise_debug_marker_symbol) \ V(_, promise_debug_marker_symbol) \
V(_, promise_forwarding_handler_symbol) \ V(_, promise_forwarding_handler_symbol) \
V(_, promise_handled_by_symbol) \ V(_, promise_handled_by_symbol) \
V(_, regexp_result_cached_indices_or_match_info_symbol) \ V(_, regexp_result_cached_indices_or_regexp_symbol) \
V(_, regexp_result_names_symbol) \ V(_, regexp_result_names_symbol) \
V(_, sealed_symbol) \ V(_, regexp_result_regexp_input_symbol) \
V(_, stack_trace_symbol) \ V(_, regexp_result_regexp_last_index_symbol) \
V(_, strict_function_transition_symbol) \ V(_, sealed_symbol) \
V(_, wasm_exception_tag_symbol) \ V(_, stack_trace_symbol) \
V(_, wasm_exception_values_symbol) \ V(_, strict_function_transition_symbol) \
V(_, wasm_exception_tag_symbol) \
V(_, wasm_exception_values_symbol) \
V(_, uninitialized_symbol) V(_, uninitialized_symbol)
#define PUBLIC_SYMBOL_LIST_GENERATOR(V, _) \ #define PUBLIC_SYMBOL_LIST_GENERATOR(V, _) \
......
...@@ -6,46 +6,80 @@ ...@@ -6,46 +6,80 @@
#include "src/objects/js-array-inl.h" #include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h" #include "src/objects/js-regexp-inl.h"
#include "src/regexp/regexp.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
Handle<JSArray> JSRegExpResult::GetAndCacheIndices( MaybeHandle<JSArray> JSRegExpResult::GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result) { Isolate* isolate, Handle<JSRegExpResult> regexp_result) {
// Check for cached indices. We do a slow lookup and set of // Check for cached indices. We do a slow lookup and set of
// the cached_indices_or_match_info and names fields just in // the cached_indices_or_match_info and names fields just in
// case they have been migrated to dictionaries. // case they have been migrated to dictionaries.
Handle<Object> indices_or_match_info( Handle<Object> indices_or_regexp(
GetProperty(isolate, regexp_result, GetProperty(
isolate->factory() isolate, regexp_result,
->regexp_result_cached_indices_or_match_info_symbol()) isolate->factory()->regexp_result_cached_indices_or_regexp_symbol())
.ToHandleChecked()); .ToHandleChecked());
if (indices_or_match_info->IsRegExpMatchInfo()) { if (indices_or_regexp->IsJSRegExp()) {
// Build and cache indices for next lookup. // Build and cache indices for next lookup.
// TODO(joshualitt): Instead of caching the indices, we could call // TODO(joshualitt): Instead of caching the indices, we could call
// ReconfigureToDataProperty on 'indices' setting its value to this // ReconfigureToDataProperty on 'indices' setting its value to this
// newly created array. However, care would have to be taken to ensure // newly created array. However, care would have to be taken to ensure
// a new map is not created each time. // a new map is not created each time.
Handle<RegExpMatchInfo> match_info(
RegExpMatchInfo::cast(*indices_or_match_info), isolate); // Grab regexp, its last_index, and the original subject string from the
// result and the re-execute the regexp to generate a new MatchInfo.
Handle<JSRegExp> regexp(JSRegExp::cast(*indices_or_regexp), isolate);
Handle<Object> input_object(
GetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_input_symbol())
.ToHandleChecked());
Handle<String> subject(String::cast(*input_object), isolate);
Handle<Object> last_index_object(
GetProperty(
isolate, regexp_result,
isolate->factory()->regexp_result_regexp_last_index_symbol())
.ToHandleChecked());
int capture_count = regexp->CaptureCount();
Handle<RegExpMatchInfo> match_info =
RegExpMatchInfo::New(isolate, capture_count);
int last_index = Smi::ToInt(*last_index_object);
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
RegExp::Exec(isolate, regexp, subject, last_index, match_info),
JSArray);
DCHECK_EQ(*result, *match_info);
Handle<Object> maybe_names( Handle<Object> maybe_names(
GetProperty(isolate, regexp_result, GetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_names_symbol()) isolate->factory()->regexp_result_names_symbol())
.ToHandleChecked()); .ToHandleChecked());
indices_or_match_info = indices_or_regexp =
JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names); JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);
// Cache the result and clear the names array. // Cache the result and clear the names array, last_index and subject.
SetProperty( SetProperty(
isolate, regexp_result, isolate, regexp_result,
isolate->factory()->regexp_result_cached_indices_or_match_info_symbol(), isolate->factory()->regexp_result_cached_indices_or_regexp_symbol(),
indices_or_match_info) indices_or_regexp)
.ToHandleChecked(); .ToHandleChecked();
SetProperty(isolate, regexp_result, SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_names_symbol(), isolate->factory()->regexp_result_names_symbol(),
isolate->factory()->undefined_value()) isolate->factory()->undefined_value())
.ToHandleChecked(); .ToHandleChecked();
SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_last_index_symbol(),
isolate->factory()->undefined_value())
.ToHandleChecked();
SetProperty(isolate, regexp_result,
isolate->factory()->regexp_result_regexp_input_symbol(),
isolate->factory()->undefined_value())
.ToHandleChecked();
} }
return Handle<JSArray>::cast(indices_or_match_info); return Handle<JSArray>::cast(indices_or_regexp);
} }
Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices( Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
......
...@@ -239,7 +239,7 @@ class JSRegExpResult : public JSArray { ...@@ -239,7 +239,7 @@ class JSRegExpResult : public JSArray {
DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kHeaderSize, DEFINE_FIELD_OFFSET_CONSTANTS(JSArray::kHeaderSize,
TORQUE_GENERATED_JS_REG_EXP_RESULT_FIELDS) TORQUE_GENERATED_JS_REG_EXP_RESULT_FIELDS)
static Handle<JSArray> GetAndCacheIndices( static MaybeHandle<JSArray> GetAndCacheIndices(
Isolate* isolate, Handle<JSRegExpResult> regexp_result); Isolate* isolate, Handle<JSRegExpResult> regexp_result);
// Indices of in-object properties. // Indices of in-object properties.
...@@ -248,9 +248,11 @@ class JSRegExpResult : public JSArray { ...@@ -248,9 +248,11 @@ class JSRegExpResult : public JSArray {
static const int kGroupsIndex = 2; static const int kGroupsIndex = 2;
// Private internal only fields. // Private internal only fields.
static const int kCachedIndicesOrMatchInfoIndex = 3; static const int kCachedIndicesOrRegExpIndex = 3;
static const int kNamesIndex = 4; static const int kNamesIndex = 4;
static const int kInObjectPropertyCount = 5; static const int kRegExpInputIndex = 5;
static const int kRegExpLastIndex = 6;
static const int kInObjectPropertyCount = 7;
OBJECT_CONSTRUCTORS(JSRegExpResult, JSArray); OBJECT_CONSTRUCTORS(JSRegExpResult, JSArray);
}; };
......
...@@ -33,8 +33,10 @@ extern class JSRegExpResult extends JSArray { ...@@ -33,8 +33,10 @@ extern class JSRegExpResult extends JSArray {
groups: JSAny; groups: JSAny;
// The below fields are for internal use only. // The below fields are for internal use only.
cached_indices_or_match_info: JSRegExpResultIndices|RegExpMatchInfo; cached_indices_or_regexp: JSRegExpResultIndices|JSRegExp;
names: FixedArray|Undefined; names: FixedArray|Undefined;
regexp_input: String;
regexp_last_index: Smi;
} }
@hasSameInstanceTypeAsParent @hasSameInstanceTypeAsParent
......
...@@ -4097,12 +4097,22 @@ WeakArrayList PrototypeUsers::Compact(Handle<WeakArrayList> array, Heap* heap, ...@@ -4097,12 +4097,22 @@ WeakArrayList PrototypeUsers::Compact(Handle<WeakArrayList> array, Heap* heap,
return *new_array; return *new_array;
} }
Handle<RegExpMatchInfo> RegExpMatchInfo::New(Isolate* isolate,
int capture_count) {
Handle<RegExpMatchInfo> match_info = isolate->factory()->NewRegExpMatchInfo();
return ReserveCaptures(isolate, match_info, capture_count);
}
Handle<RegExpMatchInfo> RegExpMatchInfo::ReserveCaptures( Handle<RegExpMatchInfo> RegExpMatchInfo::ReserveCaptures(
Isolate* isolate, Handle<RegExpMatchInfo> match_info, int capture_count) { Isolate* isolate, Handle<RegExpMatchInfo> match_info, int capture_count) {
DCHECK_GE(match_info->length(), kLastMatchOverhead); DCHECK_GE(match_info->length(), kLastMatchOverhead);
const int required_length = kFirstCaptureIndex + capture_count;
return Handle<RegExpMatchInfo>::cast( int capture_register_count = (capture_count + 1) * 2;
const int required_length = kFirstCaptureIndex + capture_register_count;
Handle<RegExpMatchInfo> result = Handle<RegExpMatchInfo>::cast(
EnsureSpaceInFixedArray(isolate, match_info, required_length)); EnsureSpaceInFixedArray(isolate, match_info, required_length));
result->SetNumberOfCaptureRegisters(capture_register_count);
return result;
} }
// static // static
......
...@@ -45,6 +45,9 @@ class V8_EXPORT_PRIVATE RegExpMatchInfo : NON_EXPORTED_BASE(public FixedArray) { ...@@ -45,6 +45,9 @@ class V8_EXPORT_PRIVATE RegExpMatchInfo : NON_EXPORTED_BASE(public FixedArray) {
inline int Capture(int i); inline int Capture(int i);
inline void SetCapture(int i, int value); inline void SetCapture(int i, int value);
// Creates a new RegExpMatchInfo with space for capture_count captures.
static Handle<RegExpMatchInfo> New(Isolate* isolate, int capture_count);
// Reserves space for captures. // Reserves space for captures.
static Handle<RegExpMatchInfo> ReserveCaptures( static Handle<RegExpMatchInfo> ReserveCaptures(
Isolate* isolate, Handle<RegExpMatchInfo> match_info, int capture_count); Isolate* isolate, Handle<RegExpMatchInfo> match_info, int capture_count);
......
...@@ -673,12 +673,8 @@ Handle<RegExpMatchInfo> RegExp::SetLastMatchInfo( ...@@ -673,12 +673,8 @@ Handle<RegExpMatchInfo> RegExp::SetLastMatchInfo(
// regexp, RegExpExecStub finds that the match info is too small, it restarts // regexp, RegExpExecStub finds that the match info is too small, it restarts
// execution in RegExpImpl::Exec, which finally grows the match info right // execution in RegExpImpl::Exec, which finally grows the match info right
// here. // here.
Handle<RegExpMatchInfo> result =
int capture_register_count = (capture_count + 1) * 2; RegExpMatchInfo::ReserveCaptures(isolate, last_match_info, capture_count);
Handle<RegExpMatchInfo> result = RegExpMatchInfo::ReserveCaptures(
isolate, last_match_info, capture_register_count);
result->SetNumberOfCaptureRegisters(capture_register_count);
if (*result != *last_match_info) { if (*result != *last_match_info) {
if (*last_match_info == *isolate->regexp_last_match_info()) { if (*last_match_info == *isolate->regexp_last_match_info()) {
// This inner condition is only needed for special situations like the // This inner condition is only needed for special situations like the
...@@ -689,6 +685,7 @@ Handle<RegExpMatchInfo> RegExp::SetLastMatchInfo( ...@@ -689,6 +685,7 @@ Handle<RegExpMatchInfo> RegExp::SetLastMatchInfo(
} }
} }
int capture_register_count = (capture_count + 1) * 2;
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
if (match != nullptr) { if (match != nullptr) {
for (int i = 0; i < capture_register_count; i += 2) { for (int i = 0; i < capture_register_count; i += 2) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// 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.
// //
// Flags: --harmony-regexp-match-indices --expose-gc // Flags: --harmony-regexp-match-indices --expose-gc --stack-size=100
// Sanity test. // Sanity test.
{ {
...@@ -104,6 +104,13 @@ ...@@ -104,6 +104,13 @@
assertEquals(m.indices.groups, {'Z': [4, 5]}) assertEquals(m.indices.groups, {'Z': [4, 5]})
} }
// Test atomic regexp.
{
const m = /undefined/.exec();
assertEquals(m.indices, [[0, 9]]);
}
// Test deleting unrelated fields does not break. // Test deleting unrelated fields does not break.
{ {
const m = /undefined/.exec(); const m = /undefined/.exec();
...@@ -111,3 +118,20 @@ ...@@ -111,3 +118,20 @@
gc(); gc();
assertEquals(m.indices, [[0, 9]]); assertEquals(m.indices, [[0, 9]]);
} }
// Stack overflow.
{
const re = /a+(?<Z>z)?/;
const m = re.exec("xaaaz");
function rec() {
try {
return rec();
} catch (e) {
assertEquals(m.indices, [[1, 5], [4, 5]]);
assertEquals(m.indices.groups, {'Z': [4, 5]})
return true;
}
}
assertTrue(rec());
}
...@@ -281,54 +281,54 @@ KNOWN_MAPS = { ...@@ -281,54 +281,54 @@ KNOWN_MAPS = {
("read_only_space", 0x01d79): (92, "EnumCacheMap"), ("read_only_space", 0x01d79): (92, "EnumCacheMap"),
("read_only_space", 0x01e11): (86, "ArrayBoilerplateDescriptionMap"), ("read_only_space", 0x01e11): (86, "ArrayBoilerplateDescriptionMap"),
("read_only_space", 0x01ff9): (95, "InterceptorInfoMap"), ("read_only_space", 0x01ff9): (95, "InterceptorInfoMap"),
("read_only_space", 0x049a1): (71, "PromiseFulfillReactionJobTaskMap"), ("read_only_space", 0x049d1): (71, "PromiseFulfillReactionJobTaskMap"),
("read_only_space", 0x049e9): (72, "PromiseRejectReactionJobTaskMap"), ("read_only_space", 0x04a19): (72, "PromiseRejectReactionJobTaskMap"),
("read_only_space", 0x04a31): (73, "CallableTaskMap"), ("read_only_space", 0x04a61): (73, "CallableTaskMap"),
("read_only_space", 0x04a79): (74, "CallbackTaskMap"), ("read_only_space", 0x04aa9): (74, "CallbackTaskMap"),
("read_only_space", 0x04ac1): (75, "PromiseResolveThenableJobTaskMap"), ("read_only_space", 0x04af1): (75, "PromiseResolveThenableJobTaskMap"),
("read_only_space", 0x04b09): (78, "FunctionTemplateInfoMap"), ("read_only_space", 0x04b39): (78, "FunctionTemplateInfoMap"),
("read_only_space", 0x04b51): (79, "ObjectTemplateInfoMap"), ("read_only_space", 0x04b81): (79, "ObjectTemplateInfoMap"),
("read_only_space", 0x04b99): (80, "AccessCheckInfoMap"), ("read_only_space", 0x04bc9): (80, "AccessCheckInfoMap"),
("read_only_space", 0x04be1): (81, "AccessorInfoMap"), ("read_only_space", 0x04c11): (81, "AccessorInfoMap"),
("read_only_space", 0x04c29): (82, "AccessorPairMap"), ("read_only_space", 0x04c59): (82, "AccessorPairMap"),
("read_only_space", 0x04c71): (83, "AliasedArgumentsEntryMap"), ("read_only_space", 0x04ca1): (83, "AliasedArgumentsEntryMap"),
("read_only_space", 0x04cb9): (84, "AllocationMementoMap"), ("read_only_space", 0x04ce9): (84, "AllocationMementoMap"),
("read_only_space", 0x04d01): (87, "AsmWasmDataMap"), ("read_only_space", 0x04d31): (87, "AsmWasmDataMap"),
("read_only_space", 0x04d49): (88, "AsyncGeneratorRequestMap"), ("read_only_space", 0x04d79): (88, "AsyncGeneratorRequestMap"),
("read_only_space", 0x04d91): (90, "ClassPositionsMap"), ("read_only_space", 0x04dc1): (90, "ClassPositionsMap"),
("read_only_space", 0x04dd9): (91, "DebugInfoMap"), ("read_only_space", 0x04e09): (91, "DebugInfoMap"),
("read_only_space", 0x04e21): (94, "FunctionTemplateRareDataMap"), ("read_only_space", 0x04e51): (94, "FunctionTemplateRareDataMap"),
("read_only_space", 0x04e69): (97, "InterpreterDataMap"), ("read_only_space", 0x04e99): (97, "InterpreterDataMap"),
("read_only_space", 0x04eb1): (98, "PromiseCapabilityMap"), ("read_only_space", 0x04ee1): (98, "PromiseCapabilityMap"),
("read_only_space", 0x04ef9): (99, "PromiseReactionMap"), ("read_only_space", 0x04f29): (99, "PromiseReactionMap"),
("read_only_space", 0x04f41): (100, "PrototypeInfoMap"), ("read_only_space", 0x04f71): (100, "PrototypeInfoMap"),
("read_only_space", 0x04f89): (101, "ScriptMap"), ("read_only_space", 0x04fb9): (101, "ScriptMap"),
("read_only_space", 0x04fd1): (105, "SourcePositionTableWithFrameCacheMap"), ("read_only_space", 0x05001): (105, "SourcePositionTableWithFrameCacheMap"),
("read_only_space", 0x05019): (106, "SourceTextModuleInfoEntryMap"), ("read_only_space", 0x05049): (106, "SourceTextModuleInfoEntryMap"),
("read_only_space", 0x05061): (107, "StackFrameInfoMap"), ("read_only_space", 0x05091): (107, "StackFrameInfoMap"),
("read_only_space", 0x050a9): (108, "StackTraceFrameMap"), ("read_only_space", 0x050d9): (108, "StackTraceFrameMap"),
("read_only_space", 0x050f1): (109, "TemplateObjectDescriptionMap"), ("read_only_space", 0x05121): (109, "TemplateObjectDescriptionMap"),
("read_only_space", 0x05139): (110, "Tuple2Map"), ("read_only_space", 0x05169): (110, "Tuple2Map"),
("read_only_space", 0x05181): (111, "Tuple3Map"), ("read_only_space", 0x051b1): (111, "Tuple3Map"),
("read_only_space", 0x051c9): (112, "WasmCapiFunctionDataMap"), ("read_only_space", 0x051f9): (112, "WasmCapiFunctionDataMap"),
("read_only_space", 0x05211): (113, "WasmDebugInfoMap"), ("read_only_space", 0x05241): (113, "WasmDebugInfoMap"),
("read_only_space", 0x05259): (114, "WasmExceptionTagMap"), ("read_only_space", 0x05289): (114, "WasmExceptionTagMap"),
("read_only_space", 0x052a1): (115, "WasmExportedFunctionDataMap"), ("read_only_space", 0x052d1): (115, "WasmExportedFunctionDataMap"),
("read_only_space", 0x052e9): (116, "WasmIndirectFunctionTableMap"), ("read_only_space", 0x05319): (116, "WasmIndirectFunctionTableMap"),
("read_only_space", 0x05331): (117, "WasmJSFunctionDataMap"), ("read_only_space", 0x05361): (117, "WasmJSFunctionDataMap"),
("read_only_space", 0x05379): (96, "InternalClassMap"), ("read_only_space", 0x053a9): (96, "InternalClassMap"),
("read_only_space", 0x053c1): (103, "SmiPairMap"), ("read_only_space", 0x053f1): (103, "SmiPairMap"),
("read_only_space", 0x05409): (102, "SmiBoxMap"), ("read_only_space", 0x05439): (102, "SmiBoxMap"),
("read_only_space", 0x05451): (104, "SortStateMap"), ("read_only_space", 0x05481): (104, "SortStateMap"),
("read_only_space", 0x05499): (85, "AllocationSiteWithWeakNextMap"), ("read_only_space", 0x054c9): (85, "AllocationSiteWithWeakNextMap"),
("read_only_space", 0x054e1): (85, "AllocationSiteWithoutWeakNextMap"), ("read_only_space", 0x05511): (85, "AllocationSiteWithoutWeakNextMap"),
("read_only_space", 0x05529): (76, "LoadHandler1Map"), ("read_only_space", 0x05559): (76, "LoadHandler1Map"),
("read_only_space", 0x05571): (76, "LoadHandler2Map"), ("read_only_space", 0x055a1): (76, "LoadHandler2Map"),
("read_only_space", 0x055b9): (76, "LoadHandler3Map"), ("read_only_space", 0x055e9): (76, "LoadHandler3Map"),
("read_only_space", 0x05601): (77, "StoreHandler0Map"), ("read_only_space", 0x05631): (77, "StoreHandler0Map"),
("read_only_space", 0x05649): (77, "StoreHandler1Map"), ("read_only_space", 0x05679): (77, "StoreHandler1Map"),
("read_only_space", 0x05691): (77, "StoreHandler2Map"), ("read_only_space", 0x056c1): (77, "StoreHandler2Map"),
("read_only_space", 0x056d9): (77, "StoreHandler3Map"), ("read_only_space", 0x05709): (77, "StoreHandler3Map"),
("map_space", 0x00119): (1057, "ExternalMap"), ("map_space", 0x00119): (1057, "ExternalMap"),
("map_space", 0x00161): (1073, "JSMessageObjectMap"), ("map_space", 0x00161): (1073, "JSMessageObjectMap"),
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment