Index: src/builtins/builtins-regexp.cc |
diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc |
index 65df206e56146d01165ef4ceb5137310189410fc..884c903a2806534708697dc4a1656b28d705c1b9 100644 |
--- a/src/builtins/builtins-regexp.cc |
+++ b/src/builtins/builtins-regexp.cc |
@@ -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); |
@@ -1618,7 +1840,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. |
@@ -1633,10 +1856,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); |
} |