Index: src/builtins/builtins-regexp.cc |
diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc |
index 17978a33f6a0088b2912f1e462b9c2022d556e13..91ada097d94155435a0a2d904b789dc1166658e2 100644 |
--- a/src/builtins/builtins-regexp.cc |
+++ b/src/builtins/builtins-regexp.cc |
@@ -207,6 +207,16 @@ compiler::Node* LoadLastIndex(CodeStubAssembler* a, compiler::Node* context, |
return var_value.value(); |
} |
+// The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified |
+// JSRegExp instance. |
+void FastStoreLastIndex(CodeStubAssembler* a, compiler::Node* context, |
+ compiler::Node* regexp, compiler::Node* value) { |
+ // Store the in-object field. |
+ static const int field_offset = |
+ JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; |
+ a->StoreObjectField(regexp, field_offset, value); |
+} |
+ |
void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context, |
compiler::Node* has_initialmap, compiler::Node* regexp, |
compiler::Node* value) { |
@@ -218,10 +228,7 @@ void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context, |
a->Bind(&if_unmodified); |
{ |
- // Store the in-object field. |
- static const int field_offset = |
- JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; |
- a->StoreObjectField(regexp, field_offset, value); |
+ FastStoreLastIndex(a, context, regexp, value); |
a->Goto(&out); |
} |
@@ -454,6 +461,7 @@ namespace { |
compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate, |
compiler::Node* context, |
compiler::Node* value, |
+ MessageTemplate::Template msg_template, |
char const* method_name) { |
typedef compiler::Node Node; |
typedef CodeStubAssembler::Label Label; |
@@ -475,8 +483,7 @@ compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate, |
// The {value} is not a compatible receiver for this method. |
a->Bind(&throw_exception); |
{ |
- Node* const message_id = |
- a->SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonObject)); |
+ Node* const message_id = a->SmiConstant(Smi::FromInt(msg_template)); |
Node* const method_name_str = a->HeapConstant( |
isolate->factory()->NewStringFromAsciiChecked(method_name, TENURED)); |
@@ -508,6 +515,37 @@ compiler::Node* IsInitialRegExpMap(CodeStubAssembler* a, |
return has_initialmap; |
} |
+// RegExp fast path implementations rely on unmodified JSRegExp instances. |
+// We use a fairly coarse granularity for this and simply check whether both |
+// the regexp itself is unmodified (i.e. its map has not changed) and its |
+// prototype is unmodified. |
+void BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context, |
+ compiler::Node* map, |
+ CodeStubAssembler::Label* if_isunmodified, |
+ CodeStubAssembler::Label* if_ismodified) { |
+ typedef compiler::Node Node; |
+ |
+ Node* const native_context = a->LoadNativeContext(context); |
+ Node* const regexp_fun = |
+ a->LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
+ Node* const initial_map = |
+ a->LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); |
+ Node* const has_initialmap = a->WordEqual(map, initial_map); |
+ |
+ a->GotoUnless(has_initialmap, if_ismodified); |
+ |
+ Node* const initial_proto_initial_map = a->LoadContextElement( |
+ native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX); |
+ Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map)); |
+ Node* const proto_has_initialmap = |
+ a->WordEqual(proto_map, initial_proto_initial_map); |
+ |
+ // TODO(ishell): Update this check once map changes for constant field |
+ // tracking are landing. |
+ |
+ a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified); |
+} |
+ |
} // namespace |
void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) { |
@@ -523,6 +561,7 @@ void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeStubAssembler* a) { |
Node* const int_one = a->IntPtrConstant(1); |
Node* const map = ThrowIfNotJSReceiver(a, isolate, context, receiver, |
+ MessageTemplate::kRegExpNonObject, |
"RegExp.prototype.flags"); |
Variable var_length(a, MachineType::PointerRepresentation()); |
@@ -819,7 +858,6 @@ void Builtins::Generate_RegExpPrototypeUnicodeGetter(CodeStubAssembler* a) { |
"RegExp.prototype.unicode"); |
} |
- |
// The properties $1..$9 are the first nine capturing substrings of the last |
// successful match, or ''. The function RegExpMakeCaptureGetter will be |
// called with indices from 1 to 9. |
@@ -1183,8 +1221,6 @@ MaybeHandle<JSArray> RegExpSplit(Isolate* isolate, Handle<JSRegExp> regexp, |
if (num_elems == limit) break; |
- // TODO(jgruber): Refactor GetLastMatchInfo methods to take an input |
- // argument. |
Handle<Object> num_captures_obj = |
JSReceiver::GetElement(isolate, match_indices, |
RegExpImpl::kLastCaptureCount) |
@@ -1428,5 +1464,196 @@ BUILTIN(RegExpPrototypeSplit) { |
return *NewJSArrayWithElements(isolate, elems, num_elems); |
} |
+namespace { |
+ |
+compiler::Node* ReplaceFastPath(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 |
+ // string replacement. |
+ |
+ typedef CodeStubAssembler::Variable Variable; |
+ typedef CodeStubAssembler::Label Label; |
+ typedef compiler::Node Node; |
+ |
+ Isolate* const isolate = a->isolate(); |
+ |
+ Node* const null = a->NullConstant(); |
+ Node* const int_zero = a->IntPtrConstant(0); |
+ Node* const smi_zero = a->SmiConstant(Smi::kZero); |
+ |
+ Label out(a); |
+ Variable var_result(a, MachineRepresentation::kTagged); |
+ |
+ // Load the last match info. |
+ Node* const native_context = a->LoadNativeContext(context); |
+ Node* const last_match_info = a->LoadContextElement( |
+ native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
+ |
+ // Is {regexp} global? |
+ Label if_isglobal(a), if_isnonglobal(a); |
+ Node* const flags = a->LoadObjectField(regexp, JSRegExp::kFlagsOffset); |
+ Node* const is_global = |
+ a->WordAnd(a->SmiUntag(flags), a->IntPtrConstant(JSRegExp::kGlobal)); |
+ a->Branch(a->WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal); |
+ |
+ a->Bind(&if_isglobal); |
+ { |
+ // Hand off global regexps to runtime. |
+ FastStoreLastIndex(a, context, regexp, smi_zero); |
+ Node* const result = |
+ a->CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context, |
+ subject_string, regexp, replace_string, last_match_info); |
+ var_result.Bind(result); |
+ a->Goto(&out); |
+ } |
+ |
+ a->Bind(&if_isnonglobal); |
+ { |
+ // Run exec, then manually construct the resulting string. |
+ Callable exec_callable = CodeFactory::RegExpExec(isolate); |
+ Node* const match_indices = |
+ a->CallStub(exec_callable, context, regexp, subject_string, smi_zero, |
+ last_match_info); |
+ |
+ Label if_matched(a), if_didnotmatch(a); |
+ a->Branch(a->WordEqual(match_indices, null), &if_didnotmatch, &if_matched); |
+ |
+ a->Bind(&if_didnotmatch); |
+ { |
+ FastStoreLastIndex(a, context, regexp, smi_zero); |
+ var_result.Bind(subject_string); |
+ a->Goto(&out); |
+ } |
+ |
+ a->Bind(&if_matched); |
+ { |
+ Node* const match_elements = a->LoadElements(match_indices); |
+ CodeStubAssembler::ParameterMode mode = |
+ CodeStubAssembler::INTPTR_PARAMETERS; |
+ |
+ Node* const subject_start = smi_zero; |
+ Node* const match_start = a->LoadFixedArrayElement( |
+ match_elements, a->IntPtrConstant(RegExpImpl::kFirstCapture), 0, |
+ mode); |
+ Node* const match_end = a->LoadFixedArrayElement( |
+ match_elements, a->IntPtrConstant(RegExpImpl::kFirstCapture + 1), 0, |
+ mode); |
+ Node* const subject_end = a->LoadStringLength(subject_string); |
+ |
+ Label if_replaceisempty(a), if_replaceisnotempty(a); |
+ Node* const replace_length = a->LoadStringLength(replace_string); |
+ a->Branch(a->SmiEqual(replace_length, smi_zero), &if_replaceisempty, |
+ &if_replaceisnotempty); |
+ |
+ a->Bind(&if_replaceisempty); |
+ { |
+ // TODO(jgruber): We could skip many of the checks that using SubString |
+ // here entails. |
+ |
+ Node* const first_part = |
+ a->SubString(context, subject_string, subject_start, match_start); |
+ Node* const second_part = |
+ a->SubString(context, subject_string, match_end, subject_end); |
+ |
+ Node* const result = a->StringConcat(context, first_part, second_part); |
+ var_result.Bind(result); |
+ a->Goto(&out); |
+ } |
+ |
+ a->Bind(&if_replaceisnotempty); |
+ { |
+ Node* const first_part = |
+ a->SubString(context, subject_string, subject_start, match_start); |
+ Node* const second_part = replace_string; |
+ Node* const third_part = |
+ a->SubString(context, subject_string, match_end, subject_end); |
+ |
+ Node* result = a->StringConcat(context, first_part, second_part); |
+ result = a->StringConcat(context, result, third_part); |
+ |
+ var_result.Bind(result); |
+ a->Goto(&out); |
+ } |
+ } |
+ } |
+ |
+ a->Bind(&out); |
+ return var_result.value(); |
+} |
+ |
+} // namespace |
+ |
+// ES#sec-regexp.prototype-@@replace |
+// RegExp.prototype [ @@replace ] ( string, replaceValue ) |
+void Builtins::Generate_RegExpPrototypeReplace(CodeStubAssembler* a) { |
+ typedef CodeStubAssembler::Label Label; |
+ typedef compiler::Node Node; |
+ |
+ Isolate* const isolate = a->isolate(); |
+ |
+ Node* const maybe_receiver = a->Parameter(0); |
+ Node* const maybe_string = a->Parameter(1); |
+ Node* const replace_value = a->Parameter(2); |
+ Node* const context = a->Parameter(5); |
+ |
+ Node* const int_zero = a->IntPtrConstant(0); |
+ |
+ // Ensure {receiver} is a JSReceiver. |
+ Node* const map = |
+ ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver, |
+ MessageTemplate::kIncompatibleMethodReceiver, |
+ "RegExp.prototype.@@replace"); |
+ Node* const receiver = maybe_receiver; |
+ |
+ // Convert {maybe_string} to a String. |
+ Callable tostring_callable = CodeFactory::ToString(isolate); |
+ Node* const string = a->CallStub(tostring_callable, context, maybe_string); |
+ |
+ // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? |
+ Label checkreplacecallable(a), runtime(a, Label::kDeferred), fastpath(a); |
+ BranchIfFastPath(a, context, map, &checkreplacecallable, &runtime); |
+ |
+ a->Bind(&checkreplacecallable); |
+ Node* const regexp = receiver; |
+ |
+ // 2. Is {replace_value} callable? |
+ Label checkreplacestring(a); |
+ a->GotoIf(a->TaggedIsSmi(replace_value), &checkreplacestring); |
+ |
+ Node* const replace_value_map = a->LoadMap(replace_value); |
+ a->Branch( |
+ a->Word32Equal(a->Word32And(a->LoadMapBitField(replace_value_map), |
+ a->Int32Constant(1 << Map::kIsCallable)), |
+ a->Int32Constant(0)), |
+ &checkreplacestring, &runtime); |
+ |
+ // 3. Does ToString({replace_value}) contain '$'? |
+ a->Bind(&checkreplacestring); |
+ { |
+ Node* const replace_string = |
+ a->CallStub(tostring_callable, context, replace_value); |
+ |
+ Node* const dollar_char = a->IntPtrConstant('$'); |
+ Node* const smi_minusone = a->SmiConstant(Smi::FromInt(-1)); |
+ a->GotoUnless(a->SmiEqual(a->StringIndexOfChar(context, replace_string, |
+ dollar_char, int_zero), |
+ smi_minusone), |
+ &runtime); |
+ |
+ a->Return(ReplaceFastPath(a, context, regexp, string, replace_string)); |
+ } |
+ |
+ a->Bind(&runtime); |
+ { |
+ Node* const result = a->CallRuntime(Runtime::kRegExpReplace, context, |
+ receiver, string, replace_value); |
+ a->Return(result); |
+ } |
+} |
+ |
} // namespace internal |
} // namespace v8 |