Index: src/builtins/builtins-regexp.cc |
diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc |
index d0e5598655e57383dae6edd10cea97ba4e92dcde..dfca3151f2e73835ebc60d35028331cc19d36469 100644 |
--- a/src/builtins/builtins-regexp.cc |
+++ b/src/builtins/builtins-regexp.cc |
@@ -549,6 +549,16 @@ void BranchIfFastPath(CodeStubAssembler* a, Node* context, Node* map, |
a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified); |
} |
+void BranchIfFastRegExpResult(CodeStubAssembler* a, Node* context, Node* map, |
+ CLabel* if_isunmodified, CLabel* if_ismodified) { |
+ Node* const native_context = a->LoadNativeContext(context); |
+ Node* const initial_regexp_result_map = |
+ a->LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); |
+ |
+ a->Branch(a->WordEqual(map, initial_regexp_result_map), if_isunmodified, |
+ if_ismodified); |
+} |
+ |
} // namespace |
void Builtins::Generate_RegExpPrototypeFlagsGetter(CodeAssemblerState* state) { |
@@ -763,6 +773,67 @@ Node* FastFlagGetter(CodeStubAssembler* a, Node* const regexp, |
return is_flag_set; |
} |
+// Load through the GetProperty stub. |
+compiler::Node* SlowFlagGetter(CodeStubAssembler* a, |
+ compiler::Node* const context, |
+ compiler::Node* const regexp, |
+ JSRegExp::Flag flag) { |
+ Factory* factory = a->isolate()->factory(); |
+ |
+ CLabel out(a); |
+ CVariable var_result(a, MachineType::PointerRepresentation()); |
+ |
+ Node* name; |
+ |
+ switch (flag) { |
+ case JSRegExp::kGlobal: |
+ name = a->HeapConstant(factory->global_string()); |
+ break; |
+ case JSRegExp::kIgnoreCase: |
+ name = a->HeapConstant(factory->ignoreCase_string()); |
+ break; |
+ case JSRegExp::kMultiline: |
+ name = a->HeapConstant(factory->multiline_string()); |
+ break; |
+ case JSRegExp::kSticky: |
+ name = a->HeapConstant(factory->sticky_string()); |
+ break; |
+ case JSRegExp::kUnicode: |
+ name = a->HeapConstant(factory->unicode_string()); |
+ break; |
+ default: |
+ UNREACHABLE(); |
+ } |
+ |
+ Callable getproperty_callable = CodeFactory::GetProperty(a->isolate()); |
+ Node* const value = a->CallStub(getproperty_callable, context, regexp, name); |
+ |
+ CLabel if_true(a), if_false(a); |
+ a->BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
+ |
+ a->Bind(&if_true); |
+ { |
+ var_result.Bind(a->IntPtrConstant(1)); |
+ a->Goto(&out); |
+ } |
+ |
+ a->Bind(&if_false); |
+ { |
+ var_result.Bind(a->IntPtrConstant(0)); |
+ a->Goto(&out); |
+ } |
+ |
+ a->Bind(&out); |
+ return var_result.value(); |
+} |
+ |
+compiler::Node* FlagGetter(CodeStubAssembler* a, compiler::Node* const context, |
+ compiler::Node* const regexp, JSRegExp::Flag flag, |
+ bool is_fastpath) { |
+ return is_fastpath ? FastFlagGetter(a, regexp, flag) |
+ : SlowFlagGetter(a, context, regexp, flag); |
+} |
+ |
void Generate_FlagGetter(CodeStubAssembler* a, JSRegExp::Flag flag, |
v8::Isolate::UseCounterFeature counter, |
const char* method_name) { |
@@ -1049,75 +1120,361 @@ void Builtins::Generate_RegExpPrototypeTest(CodeAssemblerState* state) { |
a.Return(result); |
} |
-// ES#sec-regexp.prototype-@@match |
-// RegExp.prototype [ @@match ] ( string ) |
-BUILTIN(RegExpPrototypeMatch) { |
- HandleScope scope(isolate); |
- CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@match"); |
+namespace { |
- Handle<Object> string_obj = args.atOrUndefined(isolate, 1); |
+Node* AdvanceStringIndex(CodeStubAssembler* a, Node* const string, |
+ Node* const index, Node* const is_unicode) { |
+ CVariable var_result(a, MachineRepresentation::kTagged); |
- Handle<String> string; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string, |
- Object::ToString(isolate, string_obj)); |
+ // Default to last_index + 1. |
+ Node* const index_plus_one = a->SmiAdd(index, a->SmiConstant(1)); |
+ var_result.Bind(index_plus_one); |
- Handle<Object> global_obj; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
- isolate, global_obj, |
- JSReceiver::GetProperty(recv, isolate->factory()->global_string())); |
- const bool global = global_obj->BooleanValue(); |
+ CLabel if_isunicode(a), out(a); |
+ a->Branch(is_unicode, &if_isunicode, &out); |
- if (!global) { |
- RETURN_RESULT_OR_FAILURE( |
- isolate, |
- RegExpUtils::RegExpExec(isolate, recv, string, |
- isolate->factory()->undefined_value())); |
+ a->Bind(&if_isunicode); |
+ { |
+ Node* const string_length = a->LoadStringLength(string); |
+ a->GotoUnless(a->SmiLessThan(index_plus_one, string_length), &out); |
+ |
+ Node* const lead = a->StringCharCodeAt(string, index); |
+ a->GotoUnless(a->Word32Equal(a->Word32And(lead, a->Int32Constant(0xFC00)), |
+ a->Int32Constant(0xD800)), |
+ &out); |
+ |
+ Node* const trail = a->StringCharCodeAt(string, index_plus_one); |
+ a->GotoUnless(a->Word32Equal(a->Word32And(trail, a->Int32Constant(0xFC00)), |
+ a->Int32Constant(0xDC00)), |
+ &out); |
+ |
+ // At a surrogate pair, return index + 2. |
+ Node* const index_plus_two = a->SmiAdd(index, a->SmiConstant(2)); |
+ var_result.Bind(index_plus_two); |
+ |
+ a->Goto(&out); |
} |
- Handle<Object> unicode_obj; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
- isolate, unicode_obj, |
- JSReceiver::GetProperty(recv, isolate->factory()->unicode_string())); |
- const bool unicode = unicode_obj->BooleanValue(); |
+ a->Bind(&out); |
+ return var_result.value(); |
+} |
- RETURN_FAILURE_ON_EXCEPTION(isolate, |
- RegExpUtils::SetLastIndex(isolate, recv, 0)); |
+// Utility class implementing a growable fixed array through CSA. |
+class GrowableFixedArray { |
+ public: |
+ explicit GrowableFixedArray(CodeStubAssembler* a) |
+ : assembler_(a), |
+ var_array_(a, MachineRepresentation::kTagged), |
+ var_length_(a, MachineType::PointerRepresentation()), |
+ var_capacity_(a, MachineType::PointerRepresentation()) { |
+ Initialize(); |
+ } |
- static const int kInitialArraySize = 8; |
- Handle<FixedArray> elems = |
- isolate->factory()->NewFixedArrayWithHoles(kInitialArraySize); |
+ Node* length() const { return var_length_.value(); } |
- int n = 0; |
- for (;; n++) { |
- Handle<Object> result; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
- isolate, result, |
- RegExpUtils::RegExpExec(isolate, recv, string, |
- isolate->factory()->undefined_value())); |
+ CVariable* var_array() { return &var_array_; } |
+ CVariable* var_length() { return &var_length_; } |
+ CVariable* var_capacity() { return &var_capacity_; } |
- if (result->IsNull(isolate)) { |
- if (n == 0) return isolate->heap()->null_value(); |
- break; |
+ void Push(Node* const value) { |
+ CodeStubAssembler* a = assembler_; |
+ |
+ const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; |
+ const CodeStubAssembler::ParameterMode mode = |
+ CodeStubAssembler::INTPTR_PARAMETERS; |
+ |
+ Node* const length = var_length_.value(); |
+ Node* const capacity = var_capacity_.value(); |
+ |
+ CLabel grow(a), store(a); |
+ a->Branch(a->IntPtrEqual(capacity, length), &grow, &store); |
+ |
+ a->Bind(&grow); |
+ { |
+ Node* const new_capacity = NewCapacity(a, capacity); |
+ Node* const new_array = GrowFixedArray(capacity, new_capacity, mode); |
+ |
+ var_capacity_.Bind(new_capacity); |
+ var_array_.Bind(new_array); |
+ a->Goto(&store); |
} |
- Handle<Object> match_obj; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj, |
- Object::GetElement(isolate, result, 0)); |
+ a->Bind(&store); |
+ { |
+ Node* const array = var_array_.value(); |
+ a->StoreFixedArrayElement(array, length, value, barrier_mode, 0, mode); |
- Handle<String> match; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match, |
- Object::ToString(isolate, match_obj)); |
+ Node* const new_length = a->IntPtrAdd(length, a->IntPtrConstant(1)); |
+ var_length_.Bind(new_length); |
+ } |
+ } |
+ |
+ Node* ToJSArray(Node* const context) { |
+ CodeStubAssembler* a = assembler_; |
+ |
+ const ElementsKind kind = FAST_ELEMENTS; |
+ |
+ Node* const native_context = a->LoadNativeContext(context); |
+ Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context); |
+ |
+ Node* const result_length = a->SmiTag(length()); |
+ Node* const result = a->AllocateUninitializedJSArrayWithoutElements( |
+ kind, array_map, result_length, nullptr); |
+ |
+ // Note: We do not currently shrink the fixed array. |
+ |
+ a->StoreObjectField(result, JSObject::kElementsOffset, var_array_.value()); |
+ |
+ return result; |
+ } |
+ |
+ private: |
+ void Initialize() { |
+ CodeStubAssembler* a = assembler_; |
+ |
+ const ElementsKind kind = FAST_ELEMENTS; |
+ const CodeStubAssembler::ParameterMode mode = |
+ CodeStubAssembler::INTPTR_PARAMETERS; |
+ |
+ static const int kInitialArraySize = 8; |
+ Node* const capacity = a->IntPtrConstant(kInitialArraySize); |
+ Node* const array = a->AllocateFixedArray(kind, capacity, mode); |
- elems = FixedArray::SetAndGrow(elems, n, match); |
+ a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity, |
+ Heap::kTheHoleValueRootIndex, mode); |
- if (match->length() == 0) { |
- RETURN_FAILURE_ON_EXCEPTION(isolate, RegExpUtils::SetAdvancedStringIndex( |
- isolate, recv, string, unicode)); |
+ var_array_.Bind(array); |
+ var_capacity_.Bind(capacity); |
+ var_length_.Bind(a->IntPtrConstant(0)); |
+ } |
+ |
+ Node* NewCapacity(CodeStubAssembler* a, Node* const current_capacity) { |
+ CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0))); |
+ |
+ // Growth rate is analog to JSObject::NewElementsCapacity: |
+ // new_capacity = (current_capacity + (current_capacity >> 1)) + 16. |
+ |
+ Node* const new_capacity = a->IntPtrAdd( |
+ a->IntPtrAdd(current_capacity, a->WordShr(current_capacity, 1)), |
+ a->IntPtrConstant(16)); |
+ |
+ return new_capacity; |
+ } |
+ |
+ Node* GrowFixedArray(Node* const current_capacity, Node* const new_capacity, |
+ CodeStubAssembler::ParameterMode mode) { |
+ DCHECK(mode == CodeStubAssembler::INTPTR_PARAMETERS); |
+ |
+ CodeStubAssembler* a = assembler_; |
+ |
+ CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0))); |
+ CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, current_capacity)); |
+ |
+ const ElementsKind kind = FAST_ELEMENTS; |
+ const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; |
+ |
+ Node* const from_array = var_array_.value(); |
+ Node* const to_array = a->AllocateFixedArray(kind, new_capacity, mode); |
+ a->CopyFixedArrayElements(kind, from_array, kind, to_array, |
+ current_capacity, new_capacity, barrier_mode, |
+ mode); |
+ |
+ return to_array; |
+ } |
+ |
+ private: |
+ CodeStubAssembler* const assembler_; |
+ CVariable var_array_; |
+ CVariable var_length_; |
+ CVariable var_capacity_; |
+}; |
+ |
+void Generate_RegExpPrototypeMatchBody(CodeStubAssembler* a, |
+ Node* const receiver, Node* const string, |
+ Node* const context, |
+ const bool is_fastpath) { |
+ 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); |
+ |
+ Node* const regexp = receiver; |
+ Node* const is_global = |
+ FlagGetter(a, context, regexp, JSRegExp::kGlobal, is_fastpath); |
+ |
+ CLabel if_isglobal(a), if_isnotglobal(a); |
+ a->Branch(is_global, &if_isglobal, &if_isnotglobal); |
+ |
+ a->Bind(&if_isnotglobal); |
+ { |
+ Node* const result = |
+ is_fastpath ? RegExpPrototypeExecInternal(a, context, regexp, string) |
+ : RegExpExec(a, context, regexp, string); |
+ a->Return(result); |
+ } |
+ |
+ a->Bind(&if_isglobal); |
+ { |
+ Node* const is_unicode = |
+ FlagGetter(a, context, regexp, JSRegExp::kUnicode, is_fastpath); |
+ |
+ if (is_fastpath) { |
+ FastStoreLastIndex(a, context, regexp, smi_zero); |
+ } else { |
+ SlowStoreLastIndex(a, context, regexp, smi_zero); |
+ } |
+ |
+ // Allocate an array to store the resulting match strings. |
+ |
+ GrowableFixedArray array(a); |
+ |
+ // Loop preparations. Within the loop, collect results from RegExpExec |
+ // and store match strings in the array. |
+ |
+ CVariable* vars[] = {array.var_array(), array.var_length(), |
+ array.var_capacity()}; |
+ CLabel loop(a, 3, vars), out(a); |
+ a->Goto(&loop); |
+ |
+ a->Bind(&loop); |
+ { |
+ Node* const result = |
+ is_fastpath ? RegExpPrototypeExecInternal(a, context, regexp, string) |
+ : RegExpExec(a, context, regexp, string); |
+ |
+ CLabel if_didmatch(a), if_didnotmatch(a); |
+ a->Branch(a->WordEqual(result, null), &if_didnotmatch, &if_didmatch); |
+ |
+ a->Bind(&if_didnotmatch); |
+ { |
+ // Return null if there were no matches, otherwise just exit the loop. |
+ a->GotoUnless(a->IntPtrEqual(array.length(), int_zero), &out); |
+ a->Return(null); |
+ } |
+ |
+ a->Bind(&if_didmatch); |
+ { |
+ Node* match = nullptr; |
+ if (is_fastpath) { |
+ // TODO(jgruber): We could optimize further here and in other |
+ // methods (e.g. @@search) by bypassing RegExp result construction. |
+ Node* const result_fixed_array = a->LoadElements(result); |
+ const CodeStubAssembler::ParameterMode mode = |
+ CodeStubAssembler::INTPTR_PARAMETERS; |
+ match = |
+ a->LoadFixedArrayElement(result_fixed_array, int_zero, 0, mode); |
+ |
+ // The match is guaranteed to be a string on the fast path. |
+ CSA_ASSERT(a, a->IsStringInstanceType(a->LoadInstanceType(match))); |
+ } else { |
+ DCHECK(!is_fastpath); |
+ |
+ CVariable var_match(a, MachineRepresentation::kTagged); |
+ CLabel fast_result(a), slow_result(a), match_loaded(a); |
+ BranchIfFastRegExpResult(a, context, a->LoadMap(result), &fast_result, |
+ &slow_result); |
+ |
+ a->Bind(&fast_result); |
+ { |
+ // TODO(jgruber): We could optimize further here and in other |
+ // methods (e.g. @@search) by bypassing RegExp result construction. |
+ Node* const result_fixed_array = a->LoadElements(result); |
+ const CodeStubAssembler::ParameterMode mode = |
+ CodeStubAssembler::INTPTR_PARAMETERS; |
+ Node* const match = |
+ a->LoadFixedArrayElement(result_fixed_array, int_zero, 0, mode); |
+ |
+ // The match is guaranteed to be a string on the fast path. |
+ CSA_ASSERT(a, a->IsStringInstanceType(a->LoadInstanceType(match))); |
+ |
+ var_match.Bind(match); |
+ a->Goto(&match_loaded); |
+ } |
+ |
+ a->Bind(&slow_result); |
+ { |
+ // TODO(ishell): Use GetElement stub once it's available. |
+ Node* const name = smi_zero; |
+ Callable getproperty_callable = CodeFactory::GetProperty(isolate); |
+ Node* const match = |
+ a->CallStub(getproperty_callable, context, result, name); |
+ |
+ var_match.Bind(match); |
+ a->Goto(&match_loaded); |
+ } |
+ |
+ a->Bind(&match_loaded); |
+ match = a->ToString(context, var_match.value()); |
+ } |
+ DCHECK(match != nullptr); |
+ |
+ // Store the match, growing the fixed array if needed. |
+ |
+ array.Push(match); |
+ |
+ // Advance last index if the match is the empty string. |
+ |
+ Node* const match_length = a->LoadStringLength(match); |
+ a->GotoUnless(a->SmiEqual(match_length, smi_zero), &loop); |
+ |
+ Node* last_index = is_fastpath ? FastLoadLastIndex(a, context, regexp) |
+ : SlowLoadLastIndex(a, context, regexp); |
+ |
+ Callable tolength_callable = CodeFactory::ToLength(isolate); |
+ last_index = a->CallStub(tolength_callable, context, last_index); |
+ |
+ Node* const new_last_index = |
+ AdvanceStringIndex(a, string, last_index, is_unicode); |
+ |
+ if (is_fastpath) { |
+ FastStoreLastIndex(a, context, regexp, new_last_index); |
+ } else { |
+ SlowStoreLastIndex(a, context, regexp, new_last_index); |
+ } |
+ |
+ a->Goto(&loop); |
+ } |
+ } |
+ |
+ a->Bind(&out); |
+ { |
+ // Wrap the match in a JSArray. |
+ |
+ Node* const result = array.ToJSArray(context); |
+ a->Return(result); |
} |
} |
+} |
+ |
+} // namespace |
+ |
+// ES#sec-regexp.prototype-@@match |
+// RegExp.prototype [ @@match ] ( string ) |
+void Builtins::Generate_RegExpPrototypeMatch(CodeAssemblerState* state) { |
+ CodeStubAssembler a(state); |
+ |
+ Node* const maybe_receiver = a.Parameter(0); |
+ Node* const maybe_string = a.Parameter(1); |
+ Node* const context = a.Parameter(4); |
+ |
+ // Ensure {maybe_receiver} is a JSReceiver. |
+ Node* const map = ThrowIfNotJSReceiver( |
+ &a, a.isolate(), context, maybe_receiver, |
+ MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.@@match"); |
+ Node* const receiver = maybe_receiver; |
+ |
+ // Convert {maybe_string} to a String. |
+ Node* const string = a.ToString(context, maybe_string); |
+ |
+ CLabel fast_path(&a), slow_path(&a); |
+ BranchIfFastPath(&a, context, map, &fast_path, &slow_path); |
+ |
+ a.Bind(&fast_path); |
+ Generate_RegExpPrototypeMatchBody(&a, receiver, string, context, true); |
- elems->Shrink(n); |
- return *isolate->factory()->NewJSArrayWithElements(elems); |
+ a.Bind(&slow_path); |
+ Generate_RegExpPrototypeMatchBody(&a, receiver, string, context, false); |
} |
namespace { |
@@ -1176,16 +1533,16 @@ void Generate_RegExpPrototypeSearchBody(CodeStubAssembler* a, |
} |
// Return the index of the match. |
- { |
- CLabel fast_result(a), slow_result(a, CLabel::kDeferred); |
- |
- Node* const native_context = a->LoadNativeContext(context); |
- Node* const initial_regexp_result_map = |
- a->LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); |
- Node* const match_indices_map = a->LoadMap(match_indices); |
+ if (is_fastpath) { |
+ Node* const index = a->LoadObjectField( |
+ match_indices, JSRegExpResult::kIndexOffset, MachineType::AnyTagged()); |
+ a->Return(index); |
+ } else { |
+ DCHECK(!is_fastpath); |
- a->Branch(a->WordEqual(match_indices_map, initial_regexp_result_map), |
- &fast_result, &slow_result); |
+ CLabel fast_result(a), slow_result(a, CLabel::kDeferred); |
+ BranchIfFastRegExpResult(a, context, a->LoadMap(match_indices), |
+ &fast_result, &slow_result); |
a->Bind(&fast_result); |
{ |