Commit a8e30c0e authored by jgruber's avatar jgruber Committed by Commit bot

[regexp] Add fast-path for global, callable replace

This adds a fast-path for calls to RegExp.prototype[@@replace] for cases in
which the given regexp is unmodified and global, and the given replace argument
is callable.

The fast-path implementation itself is almost identical to the original JS
implementation except that it currently does not reuse result_array.

SunSpider/unpack-code relies heavily on this codepath.

BUG=v8:5339

Review-Url: https://chromiumcodereview.appspot.com/2433923003
Cr-Commit-Position: refs/heads/master@{#40504}
parent 1f697f42
......@@ -1440,10 +1440,233 @@ BUILTIN(RegExpPrototypeSplit) {
namespace {
compiler::Node* ReplaceFastPath(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* regexp,
compiler::Node* subject_string,
compiler::Node* replace_string) {
compiler::Node* ReplaceGlobalCallableFastPath(
CodeStubAssembler* a, compiler::Node* context, compiler::Node* regexp,
compiler::Node* subject_string, compiler::Node* replace_callable) {
// The fast path is reached only if {receiver} is a global unmodified
// JSRegExp instance and {replace_callable} is callable.
typedef CodeStubAssembler::Variable Variable;
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
Isolate* const isolate = a->isolate();
Node* const null = a->NullConstant();
Node* const undefined = a->UndefinedConstant();
Node* const int_zero = a->IntPtrConstant(0);
Node* const int_one = a->IntPtrConstant(1);
Node* const smi_zero = a->SmiConstant(Smi::kZero);
Node* const native_context = a->LoadNativeContext(context);
Label out(a);
Variable var_result(a, MachineRepresentation::kTagged);
// Set last index to 0.
FastStoreLastIndex(a, context, regexp, smi_zero);
// Allocate {result_array}.
Node* result_array;
{
ElementsKind kind = FAST_ELEMENTS;
Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context);
Node* const capacity = a->IntPtrConstant(16);
Node* const length = smi_zero;
Node* const allocation_site = nullptr;
CodeStubAssembler::ParameterMode capacity_mode =
CodeStubAssembler::INTPTR_PARAMETERS;
result_array = a->AllocateJSArray(kind, array_map, capacity, length,
allocation_site, capacity_mode);
}
// Call into runtime for RegExpExecMultiple.
Node* last_match_info = a->LoadContextElement(
native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
Node* const res =
a->CallRuntime(Runtime::kRegExpExecMultiple, context, regexp,
subject_string, last_match_info, result_array);
// Reset last index to 0.
FastStoreLastIndex(a, context, regexp, smi_zero);
// If no matches, return the subject string.
var_result.Bind(subject_string);
a->GotoIf(a->WordEqual(res, null), &out);
// Reload last match info since it might have changed.
last_match_info = a->LoadContextElement(
native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX);
Node* const res_length = a->LoadJSArrayLength(res);
Node* const res_elems = a->LoadElements(res);
a->AssertInstanceType(res_elems, FIXED_ARRAY_TYPE);
CodeStubAssembler::ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS;
Node* const num_capture_registers = a->LoadFixedArrayElement(
last_match_info,
a->IntPtrConstant(RegExpMatchInfo::kNumberOfCapturesIndex), 0, mode);
Label if_hasexplicitcaptures(a), if_noexplicitcaptures(a), create_result(a);
a->Branch(a->SmiEqual(num_capture_registers, a->SmiConstant(Smi::FromInt(2))),
&if_noexplicitcaptures, &if_hasexplicitcaptures);
a->Bind(&if_noexplicitcaptures);
{
// 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.
// The builder will consist of some integers that indicate slices of the
// input string and some replacements that were returned from the replace
// function.
Variable var_match_start(a, MachineRepresentation::kTagged);
var_match_start.Bind(smi_zero);
Node* const end = a->SmiUntag(res_length);
Variable var_i(a, MachineType::PointerRepresentation());
var_i.Bind(int_zero);
Variable* vars[] = {&var_i, &var_match_start};
Label loop(a, 2, vars);
a->Goto(&loop);
a->Bind(&loop);
{
Node* const i = var_i.value();
a->GotoUnless(a->IntPtrLessThan(i, end), &create_result);
CodeStubAssembler::ParameterMode mode =
CodeStubAssembler::INTPTR_PARAMETERS;
Node* const elem = a->LoadFixedArrayElement(res_elems, i, 0, mode);
Label if_issmi(a), if_isstring(a), loop_epilogue(a);
a->Branch(a->TaggedIsSmi(elem), &if_issmi, &if_isstring);
a->Bind(&if_issmi);
{
// Integers represent slices of the original string.
Label if_isnegativeorzero(a), if_ispositive(a);
a->BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero,
&if_ispositive);
a->Bind(&if_ispositive);
{
Node* const int_elem = a->SmiUntag(elem);
Node* const new_match_start =
a->IntPtrAdd(a->WordShr(int_elem, a->IntPtrConstant(11)),
a->WordAnd(int_elem, a->IntPtrConstant(0x7ff)));
var_match_start.Bind(a->SmiTag(new_match_start));
a->Goto(&loop_epilogue);
}
a->Bind(&if_isnegativeorzero);
{
Node* const next_i = a->IntPtrAdd(i, int_one);
var_i.Bind(next_i);
Node* const next_elem =
a->LoadFixedArrayElement(res_elems, next_i, 0, mode);
Node* const new_match_start = a->SmiSub(next_elem, elem);
var_match_start.Bind(new_match_start);
a->Goto(&loop_epilogue);
}
}
a->Bind(&if_isstring);
{
a->Assert(a->IsStringInstanceType(a->LoadInstanceType(elem)));
Callable call_callable = CodeFactory::Call(isolate);
Node* const replacement_obj =
a->CallJS(call_callable, context, replace_callable, undefined, elem,
var_match_start.value(), subject_string);
Node* const replacement_str = a->ToString(context, replacement_obj);
a->StoreFixedArrayElement(res_elems, i, replacement_str);
Node* const elem_length = a->LoadStringLength(elem);
Node* const new_match_start =
a->SmiAdd(var_match_start.value(), elem_length);
var_match_start.Bind(new_match_start);
a->Goto(&loop_epilogue);
}
a->Bind(&loop_epilogue);
{
var_i.Bind(a->IntPtrAdd(var_i.value(), int_one));
a->Goto(&loop);
}
}
}
a->Bind(&if_hasexplicitcaptures);
{
CodeStubAssembler::ParameterMode mode =
CodeStubAssembler::INTPTR_PARAMETERS;
Node* const from = int_zero;
Node* const to = a->SmiUntag(res_length);
const int increment = 1;
a->BuildFastLoop(
MachineType::PointerRepresentation(), from, to,
[res_elems, isolate, native_context, context, undefined,
replace_callable, mode](CodeStubAssembler* a, Node* index) {
Node* const elem =
a->LoadFixedArrayElement(res_elems, index, 0, mode);
Label do_continue(a);
a->GotoIf(a->TaggedIsSmi(elem), &do_continue);
// elem must be an Array.
// Use the apply argument as backing for global RegExp properties.
a->AssertInstanceType(elem, JS_ARRAY_TYPE);
// TODO(jgruber): Remove indirection through Call->ReflectApply.
Callable call_callable = CodeFactory::Call(isolate);
Node* const reflect_apply = a->LoadContextElement(
native_context, Context::REFLECT_APPLY_INDEX);
Node* const replacement_obj =
a->CallJS(call_callable, context, reflect_apply, undefined,
replace_callable, undefined, elem);
// Overwrite the i'th element in the results with the string we got
// back from the callback function.
Node* const replacement_str = a->ToString(context, replacement_obj);
a->StoreFixedArrayElement(res_elems, index, replacement_str,
UPDATE_WRITE_BARRIER, mode);
a->Goto(&do_continue);
a->Bind(&do_continue);
},
increment, CodeStubAssembler::IndexAdvanceMode::kPost);
a->Goto(&create_result);
}
a->Bind(&create_result);
{
Node* const result = a->CallRuntime(Runtime::kStringBuilderConcat, context,
res, res_length, subject_string);
var_result.Bind(result);
a->Goto(&out);
}
a->Bind(&out);
return var_result.value();
}
compiler::Node* ReplaceSimpleStringFastPath(CodeStubAssembler* a,
compiler::Node* context,
compiler::Node* regexp,
compiler::Node* subject_string,
compiler::Node* replace_string) {
// The fast path is reached only if {receiver} is an unmodified
// JSRegExp instance, {replace_value} is non-callable, and
// ToString({replace_value}) does not contain '$', i.e. we're doing a simple
......@@ -1574,7 +1797,6 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const context = a->Parameter(5);
Node* const int_zero = a->IntPtrConstant(0);
Node* const smi_zero = a->SmiConstant(Smi::kZero);
// Ensure {receiver} is a JSReceiver.
Node* const map =
......@@ -1595,7 +1817,7 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
Node* const regexp = receiver;
// 2. Is {replace_value} callable?
Label checkreplacestring(a), if_iscallable(a, Label::kDeferred);
Label checkreplacestring(a), if_iscallable(a);
a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring);
Node* const replace_value_map = a->LoadMap(replace_value);
......@@ -1615,7 +1837,8 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
smi_minusone),
&runtime);
a->Return(ReplaceFastPath(a, context, regexp, string, replace_string));
a->Return(ReplaceSimpleStringFastPath(a, context, regexp, string,
replace_string));
}
// {regexp} is unmodified and {replace_value} is callable.
......@@ -1630,10 +1853,8 @@ void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) {
a->Bind(&if_isglobal);
{
FastStoreLastIndex(a, context, regexp, smi_zero);
Node* const result =
a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithFunction,
context, string, regexp, replace_callable);
Node* const result = ReplaceGlobalCallableFastPath(
a, context, regexp, string, replace_callable);
a->Return(result);
}
......
......@@ -930,6 +930,24 @@ Node* CodeAssembler::CallJS(Callable const& callable, Node* context,
return CallStubN(callable.descriptor(), argc + 1, target, args, result_size);
}
Node* CodeAssembler::CallJS(Callable const& callable, Node* context,
Node* function, Node* receiver, Node* arg1,
Node* arg2, Node* arg3, size_t result_size) {
const int argc = 3;
Node* target = HeapConstant(callable.code());
Node** args = zone()->NewArray<Node*>(argc + 4);
args[0] = function;
args[1] = Int32Constant(argc);
args[2] = receiver;
args[3] = arg1;
args[4] = arg2;
args[5] = arg3;
args[6] = context;
return CallStubN(callable.descriptor(), argc + 1, target, args, result_size);
}
Node* CodeAssembler::CallCFunction2(MachineType return_type,
MachineType arg0_type,
MachineType arg1_type, Node* function,
......
......@@ -443,6 +443,9 @@ class V8_EXPORT_PRIVATE CodeAssembler {
Node* receiver, Node* arg1, size_t result_size = 1);
Node* CallJS(Callable const& callable, Node* context, Node* function,
Node* receiver, Node* arg1, Node* arg2, size_t result_size = 1);
Node* CallJS(Callable const& callable, Node* context, Node* function,
Node* receiver, Node* arg1, Node* arg2, Node* arg3,
size_t result_size = 1);
// Call to a C function with two arguments.
Node* CallCFunction2(MachineType return_type, MachineType arg0_type,
......
......@@ -973,13 +973,13 @@ class VectorBackedMatch : public String::Match {
ZoneVector<Handle<Object>>* captures_;
};
// Only called from RegExpExecMultiple so it doesn't need to maintain
// Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain
// separate last match info. See comment on that function.
template <bool has_capture>
MaybeHandle<Object> SearchRegExpMultiple(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<RegExpMatchInfo> last_match_array,
Handle<FixedArray> result_elements) {
static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
Handle<JSRegExp> regexp,
Handle<RegExpMatchInfo> last_match_array,
Handle<JSArray> result_array) {
DCHECK(subject->IsFlat());
DCHECK_NE(has_capture, regexp->CaptureCount() == 0);
......@@ -999,20 +999,27 @@ MaybeHandle<Object> SearchRegExpMultiple(
for (int i = 0; i < capture_registers; i++) {
last_match[i] = Smi::cast(last_match_cache->get(i))->value();
}
Handle<FixedArray> cached_fixed_array(FixedArray::cast(cached_answer));
Handle<FixedArray> cached_fixed_array =
Handle<FixedArray>(FixedArray::cast(cached_answer));
// The cache FixedArray is a COW-array and we need to return a copy.
Handle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
cached_fixed_array, isolate->factory()->fixed_array_map());
JSArray::SetContent(result_array, copied_fixed_array);
RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
last_match);
DeleteArray(last_match);
// The cache FixedArray is a COW-array and we need to return a copy.
return isolate->factory()->CopyFixedArrayWithMap(
cached_fixed_array, isolate->factory()->fixed_array_map());
return *result_array;
}
}
RegExpImpl::GlobalCache global_cache(regexp, subject, isolate);
if (global_cache.HasException()) return MaybeHandle<Object>();
if (global_cache.HasException()) return isolate->heap()->exception();
// Ensured in Runtime_RegExpExecMultiple.
DCHECK(result_array->HasFastObjectElements());
Handle<FixedArray> result_elements(
FixedArray::cast(result_array->elements()));
if (result_elements->length() < 16) {
result_elements = isolate->factory()->NewFixedArrayWithHoles(16);
}
......@@ -1079,7 +1086,7 @@ MaybeHandle<Object> SearchRegExpMultiple(
}
}
if (global_cache.HasException()) return MaybeHandle<Object>();
if (global_cache.HasException()) return isolate->heap()->exception();
if (match_start >= 0) {
// Finished matching, with at least one match.
......@@ -1091,9 +1098,6 @@ MaybeHandle<Object> SearchRegExpMultiple(
RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
global_cache.LastSuccessfulMatch());
Handle<FixedArray> result_fixed_array = builder.array();
result_fixed_array->Shrink(builder.length());
if (subject_length > kMinLengthToCache) {
// Store the last successful match into the array for caching.
// TODO(yangguo): do not expose last match to JS and simplify caching.
......@@ -1104,194 +1108,22 @@ MaybeHandle<Object> SearchRegExpMultiple(
for (int i = 0; i < capture_registers; i++) {
last_match_cache->set(i, Smi::FromInt(last_match[i]));
}
// Cache the result and turn the FixedArray into a COW array.
Handle<FixedArray> result_fixed_array = builder.array();
result_fixed_array->Shrink(builder.length());
// Cache the result and copy the FixedArray into a COW array.
Handle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
result_fixed_array, isolate->factory()->fixed_array_map());
RegExpResultsCache::Enter(
isolate, subject, handle(regexp->data(), isolate), result_fixed_array,
isolate, subject, handle(regexp->data(), isolate), copied_fixed_array,
last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
}
// The cache FixedArray is a COW-array and we need to return a copy.
return isolate->factory()->CopyFixedArrayWithMap(
result_fixed_array, isolate->factory()->fixed_array_map());
} else {
return isolate->factory()->null_value(); // No matches at all.
}
}
// This is only called for StringReplaceGlobalRegExpWithFunction. This sets
// lastMatchInfoOverride to maintain the last match info, so we don't need to
// set any other last match array info.
MaybeHandle<Object> RegExpExecMultiple(Isolate* isolate,
Handle<JSRegExp> regexp,
Handle<String> subject,
Handle<RegExpMatchInfo> last_match_info,
Handle<FixedArray> result_array) {
subject = String::Flatten(subject);
CHECK(regexp->GetFlags() & JSRegExp::kGlobal);
if (regexp->CaptureCount() == 0) {
return SearchRegExpMultiple<false>(isolate, subject, regexp,
last_match_info, result_array);
return *builder.ToJSArray(result_array);
} else {
return SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info,
result_array);
return isolate->heap()->null_value(); // No matches at all.
}
}
// Helper function for replacing regular expressions with the result of a
// function application in String.prototype.replace.
MaybeHandle<String> StringReplaceGlobalRegExpWithFunction(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<Object> replace_obj) {
Factory* factory = isolate->factory();
// TODO(jgruber): Convert result_array into a List<Handle<Object>> (or
// similar) and adapt / remove FixedArrayBuilder.
Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
Handle<FixedArray> result_array = factory->NewFixedArrayWithHoles(16);
Handle<Object> res;
ASSIGN_RETURN_ON_EXCEPTION(isolate, res,
RegExpExecMultiple(isolate, regexp, subject,
last_match_info, result_array),
String);
// Reload the last match info since it might have changed in the meantime.
last_match_info = isolate->regexp_last_match_info();
if (res->IsNull(isolate)) return subject; // No matches at all.
result_array = Handle<FixedArray>::cast(res);
const int result_length = result_array->length();
const int num_captures = last_match_info->NumberOfCaptureRegisters() / 2;
if (num_captures == 1) {
// If the number of captures is one 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.
// The builder will consist of some integers that indicate slices of the
// input string and some replacements that were returned from the replace
// function.
int match_start = 0;
for (int i = 0; i < result_length; i++) {
Handle<Object> elem = FixedArray::get(*result_array, i, isolate);
if (elem->IsSmi()) {
// Integers represent slices of the original string.
// TODO(jgruber): Maybe we don't need this weird encoding anymore (in
// preparation to invoking StringBuilderConcat), but can just copy into
// the result string with the IncrementalStringBuilder as we go?
const int elem_value = Handle<Smi>::cast(elem)->value();
if (elem_value > 0) {
match_start = (elem_value >> 11) + (elem_value & 0x7ff);
} else {
Handle<Object> next_elem =
FixedArray::get(*result_array, ++i, isolate);
const int next_elem_value = Handle<Smi>::cast(next_elem)->value();
match_start = next_elem_value - elem_value;
}
} else {
DCHECK(elem->IsString());
Handle<String> elem_string = Handle<String>::cast(elem);
// Overwrite the i'th element in the results with the string we got
// back from the callback function.
const int argc = 3;
ScopedVector<Handle<Object>> argv(argc);
argv[0] = elem_string;
argv[1] = handle(Smi::FromInt(match_start), isolate);
argv[2] = subject;
Handle<Object> replacement_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(),
argc, argv.start()),
String);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
Object::ToString(isolate, replacement_obj),
String);
result_array->set(i, *replacement);
match_start += elem_string->length();
}
}
} else {
DCHECK(num_captures > 1);
for (int i = 0; i < result_length; i++) {
Handle<Object> elem = FixedArray::get(*result_array, i, isolate);
if (elem->IsSmi()) continue;
// TODO(jgruber): We can skip this whole round-trip through a JS array
// for result_array.
Handle<JSArray> elem_array = Handle<JSArray>::cast(elem);
Handle<FixedArray> elem_array_elems(
FixedArray::cast(elem_array->elements()), isolate);
const int argc = elem_array_elems->length();
ScopedVector<Handle<Object>> argv(argc);
for (int j = 0; j < argc; j++) {
argv[j] = FixedArray::get(*elem_array_elems, j, isolate);
}
// TODO(jgruber): This call is another pattern we could refactor.
Handle<Object> replacement_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(),
argc, argv.start()),
String);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
Object::ToString(isolate, replacement_obj),
String);
result_array->set(i, *replacement);
}
}
if (result_length == 0) {
return factory->empty_string();
} else if (result_length == 1) {
Handle<Object> first = FixedArray::get(*result_array, 0, isolate);
if (first->IsString()) return Handle<String>::cast(first);
}
bool one_byte = subject->HasOnlyOneByteChars();
const int length = StringBuilderConcatLength(subject->length(), *result_array,
result_length, &one_byte);
if (length == -1) {
isolate->Throw(isolate->heap()->illegal_argument_string());
return MaybeHandle<String>();
}
if (one_byte) {
Handle<SeqOneByteString> answer;
ASSIGN_RETURN_ON_EXCEPTION(isolate, answer,
isolate->factory()->NewRawOneByteString(length),
String);
StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array,
result_length);
return answer;
} else {
DCHECK(!one_byte);
Handle<SeqTwoByteString> answer;
ASSIGN_RETURN_ON_EXCEPTION(isolate, answer,
isolate->factory()->NewRawTwoByteString(length),
String);
StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array,
result_length);
return answer;
}
UNREACHABLE();
return MaybeHandle<String>();
}
MaybeHandle<String> StringReplaceNonGlobalRegExpWithFunction(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<Object> replace_obj) {
......@@ -1371,87 +1203,76 @@ MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp,
const int flags = regexp->GetFlags();
const bool global = (flags & JSRegExp::kGlobal) != 0;
const bool functional_replace = replace_obj->IsCallable();
if (!functional_replace) {
Handle<String> replace;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replace,
Object::ToString(isolate, replace_obj), String);
replace = String::Flatten(replace);
// Functional fast-paths are dispatched directly by replace builtin.
DCHECK(!replace_obj->IsCallable());
Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
Handle<String> replace;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replace,
Object::ToString(isolate, replace_obj), String);
replace = String::Flatten(replace);
if (!global) {
// Non-global regexp search, string replace.
Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
Handle<Object> match_indices_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, match_indices_obj,
RegExpImpl::Exec(regexp, string, 0, last_match_info), String);
if (!global) {
// Non-global regexp search, string replace.
if (match_indices_obj->IsNull(isolate)) {
RETURN_ON_EXCEPTION(
isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String);
return string;
}
Handle<Object> match_indices_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, match_indices_obj,
RegExpImpl::Exec(regexp, string, 0, last_match_info), String);
auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
if (match_indices_obj->IsNull(isolate)) {
RETURN_ON_EXCEPTION(
isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String);
return string;
}
const int start_index = match_indices->Capture(0);
const int end_index = match_indices->Capture(1);
auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(string, 0, start_index));
const int start_index = match_indices->Capture(0);
const int end_index = match_indices->Capture(1);
if (replace->length() > 0) {
MatchInfoBackedMatch m(isolate, string, match_indices);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, replacement, String::GetSubstitution(isolate, &m, replace),
String);
builder.AppendString(replacement);
}
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(string, 0, start_index));
builder.AppendString(
factory->NewSubString(string, end_index, string->length()));
return builder.Finish();
} else {
// Global regexp search, string replace.
DCHECK(global);
RETURN_ON_EXCEPTION(
isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String);
if (replace->length() > 0) {
MatchInfoBackedMatch m(isolate, string, match_indices);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
String::GetSubstitution(isolate, &m, replace),
String);
builder.AppendString(replacement);
}
if (replace->length() == 0) {
if (string->HasOnlyOneByteChars()) {
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
} else {
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
}
}
builder.AppendString(
factory->NewSubString(string, end_index, string->length()));
return builder.Finish();
} else {
// Global regexp search, string replace.
DCHECK(global);
RETURN_ON_EXCEPTION(isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0),
String);
Object* result = StringReplaceGlobalRegExpWithString(
isolate, string, regexp, replace, last_match_info);
if (result->IsString()) {
if (replace->length() == 0) {
if (string->HasOnlyOneByteChars()) {
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
} else {
return MaybeHandle<String>();
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
}
}
} else {
DCHECK(functional_replace);
if (global) {
// Global regexp search, function replace.
return StringReplaceGlobalRegExpWithFunction(isolate, string, regexp,
replace_obj);
Object* result = StringReplaceGlobalRegExpWithString(
isolate, string, regexp, replace, last_match_info);
if (result->IsString()) {
return handle(String::cast(result), isolate);
} else {
// Non-global regexp search, function replace.
return StringReplaceNonGlobalRegExpWithFunction(isolate, string, regexp,
replace_obj);
return MaybeHandle<String>();
}
}
......@@ -1461,16 +1282,27 @@ MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp,
} // namespace
RUNTIME_FUNCTION(Runtime_StringReplaceGlobalRegExpWithFunction) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
// This is only called for StringReplaceGlobalRegExpWithFunction.
RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) {
HandleScope handles(isolate);
DCHECK(args.length() == 4);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, replace, 2);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, last_match_info, 2);
CONVERT_ARG_HANDLE_CHECKED(JSArray, result_array, 3);
CHECK(result_array->HasFastObjectElements());
RETURN_RESULT_OR_FAILURE(isolate, StringReplaceGlobalRegExpWithFunction(
isolate, subject, regexp, replace));
subject = String::Flatten(subject);
CHECK(regexp->GetFlags() & JSRegExp::kGlobal);
if (regexp->CaptureCount() == 0) {
return SearchRegExpMultiple<false>(isolate, subject, regexp,
last_match_info, result_array);
} else {
return SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info,
result_array);
}
}
RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) {
......
......@@ -461,6 +461,7 @@ namespace internal {
F(RegExpConstructResult, 3, 1) \
F(RegExpCreate, 1, 1) \
F(RegExpExec, 4, 1) \
F(RegExpExecMultiple, 4, 1) \
F(RegExpExecReThrow, 4, 1) \
F(RegExpFlags, 1, 1) \
F(RegExpInitializeAndCompile, 3, 1) \
......@@ -468,7 +469,6 @@ namespace internal {
F(RegExpReplace, 3, 1) \
F(RegExpSource, 1, 1) \
F(StringReplaceGlobalRegExpWithString, 4, 1) \
F(StringReplaceGlobalRegExpWithFunction, 3, 1) \
F(StringReplaceNonGlobalRegExpWithFunction, 3, 1) \
F(StringSplit, 3, 1)
......
......@@ -174,6 +174,11 @@ class FixedArrayBuilder {
int capacity() { return array_->length(); }
Handle<JSArray> ToJSArray(Handle<JSArray> target_array) {
JSArray::SetContent(target_array, array_);
target_array->set_length(Smi::FromInt(length_));
return target_array;
}
private:
Handle<FixedArray> array_;
......
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