Index: src/builtins/builtins-regexp.cc |
diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc |
index eef9953a8a565cd97f7e62fdda95743db041e2b6..49506f4697321d7234fc4fd449e1ebc7c33a83c7 100644 |
--- a/src/builtins/builtins-regexp.cc |
+++ b/src/builtins/builtins-regexp.cc |
@@ -2,16 +2,10 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "src/builtins/builtins-regexp.h" |
- |
-#include "src/builtins/builtins-constructor.h" |
#include "src/builtins/builtins-utils.h" |
#include "src/builtins/builtins.h" |
-#include "src/code-factory.h" |
-#include "src/code-stub-assembler.h" |
#include "src/counters.h" |
#include "src/objects-inl.h" |
-#include "src/objects/regexp-match-info.h" |
#include "src/regexp/jsregexp.h" |
#include "src/regexp/regexp-utils.h" |
#include "src/string-builder.h" |
@@ -19,1434 +13,121 @@ |
namespace v8 { |
namespace internal { |
-typedef CodeStubAssembler::ParameterMode ParameterMode; |
- |
- |
// ----------------------------------------------------------------------------- |
// ES6 section 21.2 RegExp Objects |
-Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) { |
- // Load the in-object field. |
- static const int field_offset = |
- JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; |
- return LoadObjectField(regexp, field_offset); |
-} |
- |
-Node* RegExpBuiltinsAssembler::SlowLoadLastIndex(Node* context, Node* regexp) { |
- // Load through the GetProperty stub. |
- return GetProperty(context, regexp, isolate()->factory()->lastIndex_string()); |
-} |
- |
-Node* RegExpBuiltinsAssembler::LoadLastIndex(Node* context, Node* regexp, |
- bool is_fastpath) { |
- return is_fastpath ? FastLoadLastIndex(regexp) |
- : SlowLoadLastIndex(context, regexp); |
-} |
- |
-// The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified |
-// JSRegExp instance. |
-void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) { |
- // Store the in-object field. |
- static const int field_offset = |
- JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; |
- StoreObjectField(regexp, field_offset, value); |
-} |
- |
-void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp, |
- Node* value) { |
- // Store through runtime. |
- // TODO(ishell): Use SetPropertyStub here once available. |
- Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); |
- Node* const language_mode = SmiConstant(Smi::FromInt(STRICT)); |
- CallRuntime(Runtime::kSetProperty, context, regexp, name, value, |
- language_mode); |
-} |
- |
-void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp, |
- Node* value, bool is_fastpath) { |
- if (is_fastpath) { |
- FastStoreLastIndex(regexp, value); |
- } else { |
- SlowStoreLastIndex(context, regexp, value); |
- } |
-} |
- |
-Node* RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( |
- Node* const context, Node* const regexp, Node* const match_info, |
- Node* const string) { |
- Label named_captures(this), out(this); |
- |
- Node* const num_indices = SmiUntag(LoadFixedArrayElement( |
- match_info, RegExpMatchInfo::kNumberOfCapturesIndex)); |
- Node* const num_results = SmiTag(WordShr(num_indices, 1)); |
- Node* const start = |
- LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex); |
- Node* const end = LoadFixedArrayElement( |
- match_info, RegExpMatchInfo::kFirstCaptureIndex + 1); |
- |
- // Calculate the substring of the first match before creating the result array |
- // to avoid an unnecessary write barrier storing the first result. |
- Node* const first = SubString(context, string, start, end); |
- |
- Node* const result = |
- AllocateRegExpResult(context, num_results, start, string); |
- Node* const result_elements = LoadElements(result); |
- |
- StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER); |
- |
- // If no captures exist we can skip named capture handling as well. |
- GotoIf(SmiEqual(num_results, SmiConstant(1)), &out); |
- |
- // Store all remaining captures. |
- Node* const limit = IntPtrAdd( |
- IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices); |
- |
- Variable var_from_cursor( |
- this, MachineType::PointerRepresentation(), |
- IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2)); |
- Variable var_to_cursor(this, MachineType::PointerRepresentation(), |
- IntPtrConstant(1)); |
- |
- Variable* vars[] = {&var_from_cursor, &var_to_cursor}; |
- Label loop(this, 2, vars); |
- |
- Goto(&loop); |
- Bind(&loop); |
- { |
- Node* const from_cursor = var_from_cursor.value(); |
- Node* const to_cursor = var_to_cursor.value(); |
- Node* const start = LoadFixedArrayElement(match_info, from_cursor); |
- |
- Label next_iter(this); |
- GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter); |
- |
- Node* const from_cursor_plus1 = IntPtrAdd(from_cursor, IntPtrConstant(1)); |
- Node* const end = LoadFixedArrayElement(match_info, from_cursor_plus1); |
- |
- Node* const capture = SubString(context, string, start, end); |
- StoreFixedArrayElement(result_elements, to_cursor, capture); |
- Goto(&next_iter); |
- |
- Bind(&next_iter); |
- var_from_cursor.Bind(IntPtrAdd(from_cursor, IntPtrConstant(2))); |
- var_to_cursor.Bind(IntPtrAdd(to_cursor, IntPtrConstant(1))); |
- Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop, |
- &named_captures); |
- } |
- |
- Bind(&named_captures); |
- { |
- // We reach this point only if captures exist, implying that this is an |
- // IRREGEXP JSRegExp. |
- |
- CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); |
- CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1))); |
- |
- // Preparations for named capture properties. Exit early if the result does |
- // not have any named captures to minimize performance impact. |
- |
- Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset); |
- CSA_ASSERT(this, SmiEqual(LoadFixedArrayElement(data, JSRegExp::kTagIndex), |
- SmiConstant(JSRegExp::IRREGEXP))); |
- |
- // The names fixed array associates names at even indices with a capture |
- // index at odd indices. |
- Node* const names = |
- LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex); |
- GotoIf(SmiEqual(names, SmiConstant(0)), &out); |
- |
- // Allocate a new object to store the named capture properties. |
- // TODO(jgruber): Could be optimized by adding the object map to the heap |
- // root list. |
- |
- Node* const native_context = LoadNativeContext(context); |
- Node* const map = LoadContextElement( |
- native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP); |
- Node* const properties = |
- AllocateNameDictionary(NameDictionary::kInitialCapacity); |
- |
- Node* const group_object = AllocateJSObjectFromMap(map, properties); |
- |
- // Store it on the result as a 'group' property. |
- |
- { |
- Node* const name = HeapConstant(isolate()->factory()->group_string()); |
- CallRuntime(Runtime::kCreateDataProperty, context, result, name, |
- group_object); |
- } |
- |
- // One or more named captures exist, add a property for each one. |
- |
- CSA_ASSERT(this, HasInstanceType(names, FIXED_ARRAY_TYPE)); |
- Node* const names_length = LoadAndUntagFixedArrayBaseLength(names); |
- CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrConstant(0))); |
- |
- Variable var_i(this, MachineType::PointerRepresentation()); |
- var_i.Bind(IntPtrConstant(0)); |
- |
- Variable* vars[] = {&var_i}; |
- const int vars_count = sizeof(vars) / sizeof(vars[0]); |
- Label loop(this, vars_count, vars); |
- |
- Goto(&loop); |
- Bind(&loop); |
- { |
- Node* const i = var_i.value(); |
- Node* const i_plus_1 = IntPtrAdd(i, IntPtrConstant(1)); |
- Node* const i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1)); |
- |
- Node* const name = LoadFixedArrayElement(names, i); |
- Node* const index = LoadFixedArrayElement(names, i_plus_1); |
- Node* const capture = |
- LoadFixedArrayElement(result_elements, SmiUntag(index)); |
- |
- CallRuntime(Runtime::kCreateDataProperty, context, group_object, name, |
- capture); |
- |
- var_i.Bind(i_plus_2); |
- Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out, |
- &loop); |
- } |
- } |
- |
- Bind(&out); |
- return result; |
-} |
- |
-void RegExpBuiltinsAssembler::GetStringPointers( |
- Node* const string_data, Node* const offset, Node* const last_index, |
- Node* const string_length, String::Encoding encoding, |
- Variable* var_string_start, Variable* var_string_end) { |
- DCHECK_EQ(var_string_start->rep(), MachineType::PointerRepresentation()); |
- DCHECK_EQ(var_string_end->rep(), MachineType::PointerRepresentation()); |
- |
- const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING) |
- ? UINT8_ELEMENTS |
- : UINT16_ELEMENTS; |
- |
- Node* const from_offset = ElementOffsetFromIndex( |
- IntPtrAdd(offset, last_index), kind, INTPTR_PARAMETERS); |
- var_string_start->Bind(IntPtrAdd(string_data, from_offset)); |
- |
- Node* const to_offset = ElementOffsetFromIndex( |
- IntPtrAdd(offset, string_length), kind, INTPTR_PARAMETERS); |
- var_string_end->Bind(IntPtrAdd(string_data, to_offset)); |
-} |
- |
-Node* RegExpBuiltinsAssembler::IrregexpExec(Node* const context, |
- Node* const regexp, |
- Node* const string, |
- Node* const last_index, |
- Node* const match_info) { |
-// Just jump directly to runtime if native RegExp is not selected at compile |
-// time or if regexp entry in generated code is turned off runtime switch or |
-// at compilation. |
-#ifdef V8_INTERPRETED_REGEXP |
- return CallRuntime(Runtime::kRegExpExec, context, regexp, string, last_index, |
- match_info); |
-#else // V8_INTERPRETED_REGEXP |
- CSA_ASSERT(this, TaggedIsNotSmi(regexp)); |
- CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); |
- |
- CSA_ASSERT(this, TaggedIsNotSmi(string)); |
- CSA_ASSERT(this, IsString(string)); |
- |
- CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(last_index))); |
- CSA_ASSERT(this, IsFixedArrayMap(LoadReceiverMap(match_info))); |
- |
- Node* const int_zero = IntPtrConstant(0); |
- |
- ToDirectStringAssembler to_direct(state(), string); |
- |
- Variable var_result(this, MachineRepresentation::kTagged); |
- Label out(this), runtime(this, Label::kDeferred); |
- |
- // External constants. |
- Node* const regexp_stack_memory_size_address = ExternalConstant( |
- ExternalReference::address_of_regexp_stack_memory_size(isolate())); |
- Node* const static_offsets_vector_address = ExternalConstant( |
- ExternalReference::address_of_static_offsets_vector(isolate())); |
- Node* const pending_exception_address = ExternalConstant( |
- ExternalReference(Isolate::kPendingExceptionAddress, isolate())); |
- |
- // Ensure that a RegExp stack is allocated. |
- { |
- Node* const stack_size = |
- Load(MachineType::IntPtr(), regexp_stack_memory_size_address); |
- GotoIf(IntPtrEqual(stack_size, int_zero), &runtime); |
- } |
- |
- Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset); |
- { |
- // Check that the RegExp has been compiled (data contains a fixed array). |
- CSA_ASSERT(this, TaggedIsNotSmi(data)); |
- CSA_ASSERT(this, HasInstanceType(data, FIXED_ARRAY_TYPE)); |
- |
- // Check the type of the RegExp. Only continue if type is |
- // JSRegExp::IRREGEXP. |
- Node* const tag = LoadFixedArrayElement(data, JSRegExp::kTagIndex); |
- GotoIfNot(SmiEqual(tag, SmiConstant(JSRegExp::IRREGEXP)), &runtime); |
- |
- // Check (number_of_captures + 1) * 2 <= offsets vector size |
- // Or number_of_captures <= offsets vector size / 2 - 1 |
- Node* const capture_count = |
- LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex); |
- CSA_ASSERT(this, TaggedIsSmi(capture_count)); |
- |
- STATIC_ASSERT(Isolate::kJSRegexpStaticOffsetsVectorSize >= 2); |
- GotoIf(SmiAbove( |
- capture_count, |
- SmiConstant(Isolate::kJSRegexpStaticOffsetsVectorSize / 2 - 1)), |
- &runtime); |
- } |
- |
- // Unpack the string if possible. |
- |
- to_direct.TryToDirect(&runtime); |
- |
- Node* const smi_string_length = LoadStringLength(string); |
- |
- // Bail out to runtime for invalid {last_index} values. |
- GotoIfNot(TaggedIsSmi(last_index), &runtime); |
- GotoIf(SmiAboveOrEqual(last_index, smi_string_length), &runtime); |
- |
- // Load the irregexp code object and offsets into the subject string. Both |
- // depend on whether the string is one- or two-byte. |
- |
- Node* const int_last_index = SmiUntag(last_index); |
- |
- Variable var_string_start(this, MachineType::PointerRepresentation()); |
- Variable var_string_end(this, MachineType::PointerRepresentation()); |
- Variable var_code(this, MachineRepresentation::kTagged); |
- |
- { |
- Node* const int_string_length = SmiUntag(smi_string_length); |
- Node* const direct_string_data = to_direct.PointerToData(&runtime); |
- |
- Label next(this), if_isonebyte(this), if_istwobyte(this, Label::kDeferred); |
- Branch(IsOneByteStringInstanceType(to_direct.instance_type()), |
- &if_isonebyte, &if_istwobyte); |
- |
- Bind(&if_isonebyte); |
- { |
- GetStringPointers(direct_string_data, to_direct.offset(), int_last_index, |
- int_string_length, String::ONE_BYTE_ENCODING, |
- &var_string_start, &var_string_end); |
- var_code.Bind( |
- LoadFixedArrayElement(data, JSRegExp::kIrregexpLatin1CodeIndex)); |
- Goto(&next); |
- } |
- |
- Bind(&if_istwobyte); |
- { |
- GetStringPointers(direct_string_data, to_direct.offset(), int_last_index, |
- int_string_length, String::TWO_BYTE_ENCODING, |
- &var_string_start, &var_string_end); |
- var_code.Bind( |
- LoadFixedArrayElement(data, JSRegExp::kIrregexpUC16CodeIndex)); |
- Goto(&next); |
- } |
- |
- Bind(&next); |
- } |
- |
- // Check that the irregexp code has been generated for the actual string |
- // encoding. If it has, the field contains a code object otherwise it contains |
- // smi (code flushing support). |
- |
- Node* const code = var_code.value(); |
- GotoIf(TaggedIsSmi(code), &runtime); |
- CSA_ASSERT(this, HasInstanceType(code, CODE_TYPE)); |
- |
- Label if_success(this), if_failure(this), |
- if_exception(this, Label::kDeferred); |
- { |
- IncrementCounter(isolate()->counters()->regexp_entry_native(), 1); |
- |
- Callable exec_callable = CodeFactory::RegExpExec(isolate()); |
- Node* const result = CallStub( |
- exec_callable, context, string, TruncateWordToWord32(int_last_index), |
- var_string_start.value(), var_string_end.value(), code); |
- |
- // Check the result. |
- // We expect exactly one result since the stub forces the called regexp to |
- // behave as non-global. |
- GotoIf(SmiEqual(result, SmiConstant(1)), &if_success); |
- GotoIf(SmiEqual(result, SmiConstant(NativeRegExpMacroAssembler::FAILURE)), |
- &if_failure); |
- GotoIf(SmiEqual(result, SmiConstant(NativeRegExpMacroAssembler::EXCEPTION)), |
- &if_exception); |
- |
- CSA_ASSERT( |
- this, SmiEqual(result, SmiConstant(NativeRegExpMacroAssembler::RETRY))); |
- Goto(&runtime); |
- } |
- |
- Bind(&if_success); |
- { |
- // Check that the last match info has space for the capture registers and |
- // the additional information. Ensure no overflow in add. |
- STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset); |
- Node* const available_slots = |
- SmiSub(LoadFixedArrayBaseLength(match_info), |
- SmiConstant(RegExpMatchInfo::kLastMatchOverhead)); |
- Node* const capture_count = |
- LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex); |
- // Calculate number of register_count = (capture_count + 1) * 2. |
- Node* const register_count = |
- SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1); |
- GotoIf(SmiGreaterThan(register_count, available_slots), &runtime); |
- |
- // Fill match_info. |
- |
- StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex, |
- register_count, SKIP_WRITE_BARRIER); |
- StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex, |
- string); |
- StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex, |
- string); |
- |
- // Fill match and capture offsets in match_info. |
- { |
- Node* const limit_offset = ElementOffsetFromIndex( |
- register_count, INT32_ELEMENTS, SMI_PARAMETERS, 0); |
- |
- Node* const to_offset = ElementOffsetFromIndex( |
- IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), FAST_ELEMENTS, |
- INTPTR_PARAMETERS, RegExpMatchInfo::kHeaderSize - kHeapObjectTag); |
- Variable var_to_offset(this, MachineType::PointerRepresentation(), |
- to_offset); |
- |
- VariableList vars({&var_to_offset}, zone()); |
- BuildFastLoop( |
- vars, int_zero, limit_offset, |
- [=, &var_to_offset](Node* offset) { |
- Node* const value = Load(MachineType::Int32(), |
- static_offsets_vector_address, offset); |
- Node* const smi_value = SmiFromWord32(value); |
- StoreNoWriteBarrier(MachineRepresentation::kTagged, match_info, |
- var_to_offset.value(), smi_value); |
- Increment(var_to_offset, kPointerSize); |
- }, |
- kInt32Size, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); |
- } |
- |
- var_result.Bind(match_info); |
- Goto(&out); |
- } |
- |
- Bind(&if_failure); |
- { |
- var_result.Bind(NullConstant()); |
- Goto(&out); |
- } |
- |
- Bind(&if_exception); |
- { |
- Node* const pending_exception = |
- Load(MachineType::AnyTagged(), pending_exception_address); |
- |
- // If there is no pending exception, a |
- // stack overflow (on the backtrack stack) was detected in RegExp code. |
- |
- Label stack_overflow(this), rethrow(this); |
- Branch(IsTheHole(pending_exception), &stack_overflow, &rethrow); |
- |
- Bind(&stack_overflow); |
- TailCallRuntime(Runtime::kThrowStackOverflow, context); |
- |
- Bind(&rethrow); |
- TailCallRuntime(Runtime::kRegExpExecReThrow, context); |
- } |
- |
- Bind(&runtime); |
- { |
- Node* const result = CallRuntime(Runtime::kRegExpExec, context, regexp, |
- string, last_index, match_info); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-#endif // V8_INTERPRETED_REGEXP |
-} |
- |
-// ES#sec-regexp.prototype.exec |
-// RegExp.prototype.exec ( string ) |
-// Implements the core of RegExp.prototype.exec but without actually |
-// constructing the JSRegExpResult. Returns either null (if the RegExp did not |
-// match) or a fixed array containing match indices as returned by |
-// RegExpExecStub. |
-Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( |
- Node* const context, Node* const regexp, Node* const string, |
- Label* if_didnotmatch, const bool is_fastpath) { |
- Isolate* const isolate = this->isolate(); |
- |
- Node* const null = NullConstant(); |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- |
- if (!is_fastpath) { |
- ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, |
- "RegExp.prototype.exec"); |
- } |
- |
- CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(string))); |
- CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); |
- |
- Variable var_result(this, MachineRepresentation::kTagged); |
- Label out(this); |
- |
- // Load lastIndex. |
- Variable var_lastindex(this, MachineRepresentation::kTagged); |
- { |
- Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath); |
- var_lastindex.Bind(regexp_lastindex); |
- |
- // Omit ToLength if lastindex is a non-negative smi. |
- Label call_tolength(this, Label::kDeferred), next(this); |
- Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength); |
- |
- Bind(&call_tolength); |
- { |
- Callable tolength_callable = CodeFactory::ToLength(isolate); |
- var_lastindex.Bind( |
- CallStub(tolength_callable, context, regexp_lastindex)); |
- Goto(&next); |
- } |
+BUILTIN(RegExpPrototypeToString) { |
+ HandleScope scope(isolate); |
+ CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.toString"); |
- Bind(&next); |
+ if (*recv == isolate->regexp_function()->prototype()) { |
+ isolate->CountUsage(v8::Isolate::kRegExpPrototypeToString); |
} |
- // Check whether the regexp is global or sticky, which determines whether we |
- // update last index later on. |
- Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); |
- Node* const is_global_or_sticky = WordAnd( |
- SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky)); |
- Node* const should_update_last_index = |
- WordNotEqual(is_global_or_sticky, int_zero); |
- |
- // Grab and possibly update last index. |
- Label run_exec(this); |
- { |
- Label if_doupdate(this), if_dontupdate(this); |
- Branch(should_update_last_index, &if_doupdate, &if_dontupdate); |
- |
- Bind(&if_doupdate); |
- { |
- Node* const lastindex = var_lastindex.value(); |
- |
- Label if_isoob(this, Label::kDeferred); |
- GotoIfNot(TaggedIsSmi(lastindex), &if_isoob); |
- Node* const string_length = LoadStringLength(string); |
- GotoIfNot(SmiLessThanOrEqual(lastindex, string_length), &if_isoob); |
- Goto(&run_exec); |
- |
- Bind(&if_isoob); |
- { |
- StoreLastIndex(context, regexp, smi_zero, is_fastpath); |
- var_result.Bind(null); |
- Goto(if_didnotmatch); |
- } |
- } |
- |
- Bind(&if_dontupdate); |
- { |
- var_lastindex.Bind(smi_zero); |
- Goto(&run_exec); |
- } |
- } |
+ IncrementalStringBuilder builder(isolate); |
- Node* match_indices; |
- Label successful_match(this); |
- Bind(&run_exec); |
+ builder.AppendCharacter('/'); |
{ |
- // Get last match info from the context. |
- Node* const native_context = LoadNativeContext(context); |
- Node* const last_match_info = LoadContextElement( |
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- |
- // Call the exec stub. |
- match_indices = IrregexpExec(context, regexp, string, var_lastindex.value(), |
- last_match_info); |
- var_result.Bind(match_indices); |
- |
- // {match_indices} is either null or the RegExpMatchInfo array. |
- // Return early if exec failed, possibly updating last index. |
- GotoIfNot(WordEqual(match_indices, null), &successful_match); |
- |
- GotoIfNot(should_update_last_index, if_didnotmatch); |
- |
- StoreLastIndex(context, regexp, smi_zero, is_fastpath); |
- Goto(if_didnotmatch); |
+ Handle<Object> source; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
+ isolate, source, |
+ JSReceiver::GetProperty(recv, isolate->factory()->source_string())); |
+ Handle<String> source_str; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, source_str, |
+ Object::ToString(isolate, source)); |
+ builder.AppendString(source_str); |
} |
- Bind(&successful_match); |
+ builder.AppendCharacter('/'); |
{ |
- GotoIfNot(should_update_last_index, &out); |
- |
- // Update the new last index from {match_indices}. |
- Node* const new_lastindex = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); |
- |
- StoreLastIndex(context, regexp, new_lastindex, is_fastpath); |
- Goto(&out); |
+ Handle<Object> flags; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
+ isolate, flags, |
+ JSReceiver::GetProperty(recv, isolate->factory()->flags_string())); |
+ Handle<String> flags_str; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags_str, |
+ Object::ToString(isolate, flags)); |
+ builder.AppendString(flags_str); |
} |
- Bind(&out); |
- return var_result.value(); |
+ RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); |
} |
-// ES#sec-regexp.prototype.exec |
-// RegExp.prototype.exec ( string ) |
-Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBody(Node* const context, |
- Node* const regexp, |
- Node* const string, |
- const bool is_fastpath) { |
- Node* const null = NullConstant(); |
- |
- Variable var_result(this, MachineRepresentation::kTagged); |
- |
- Label if_didnotmatch(this), out(this); |
- Node* const indices_or_null = RegExpPrototypeExecBodyWithoutResult( |
- context, regexp, string, &if_didnotmatch, is_fastpath); |
- |
- // Successful match. |
- { |
- Node* const match_indices = indices_or_null; |
- Node* const result = |
- ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&if_didnotmatch); |
- { |
- var_result.Bind(null); |
- Goto(&out); |
+// 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. |
+#define DEFINE_CAPTURE_GETTER(i) \ |
+ BUILTIN(RegExpCapture##i##Getter) { \ |
+ HandleScope scope(isolate); \ |
+ return *RegExpUtils::GenericCaptureGetter( \ |
+ isolate, isolate->regexp_last_match_info(), i); \ |
} |
+DEFINE_CAPTURE_GETTER(1) |
+DEFINE_CAPTURE_GETTER(2) |
+DEFINE_CAPTURE_GETTER(3) |
+DEFINE_CAPTURE_GETTER(4) |
+DEFINE_CAPTURE_GETTER(5) |
+DEFINE_CAPTURE_GETTER(6) |
+DEFINE_CAPTURE_GETTER(7) |
+DEFINE_CAPTURE_GETTER(8) |
+DEFINE_CAPTURE_GETTER(9) |
+#undef DEFINE_CAPTURE_GETTER |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver( |
- Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template, |
- char const* method_name) { |
- Label out(this), throw_exception(this, Label::kDeferred); |
- Variable var_value_map(this, MachineRepresentation::kTagged); |
- |
- GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception); |
- |
- // Load the instance type of the {value}. |
- var_value_map.Bind(LoadMap(maybe_receiver)); |
- Node* const value_instance_type = LoadMapInstanceType(var_value_map.value()); |
- |
- Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception); |
- |
- // The {value} is not a compatible receiver for this method. |
- Bind(&throw_exception); |
- { |
- Node* const message_id = SmiConstant(Smi::FromInt(msg_template)); |
- Node* const method_name_str = HeapConstant( |
- isolate()->factory()->NewStringFromAsciiChecked(method_name, TENURED)); |
- |
- Callable callable = CodeFactory::ToString(isolate()); |
- Node* const value_str = CallStub(callable, context, maybe_receiver); |
- |
- CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str, |
- value_str); |
- Unreachable(); |
- } |
+// The properties `input` and `$_` are aliases for each other. When this |
+// value is set, the value it is set to is coerced to a string. |
+// Getter and setter for the input. |
- Bind(&out); |
- return var_value_map.value(); |
+BUILTIN(RegExpInputGetter) { |
+ HandleScope scope(isolate); |
+ Handle<Object> obj(isolate->regexp_last_match_info()->LastInput(), isolate); |
+ return obj->IsUndefined(isolate) ? isolate->heap()->empty_string() |
+ : String::cast(*obj); |
} |
-Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) { |
- Node* const native_context = LoadNativeContext(context); |
- Node* const regexp_fun = |
- LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
- Node* const initial_map = |
- LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); |
- Node* const has_initialmap = WordEqual(map, initial_map); |
- |
- return has_initialmap; |
+BUILTIN(RegExpInputSetter) { |
+ HandleScope scope(isolate); |
+ Handle<Object> value = args.atOrUndefined(isolate, 1); |
+ Handle<String> str; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str, |
+ Object::ToString(isolate, value)); |
+ isolate->regexp_last_match_info()->SetLastInput(*str); |
+ return isolate->heap()->undefined_value(); |
} |
-// 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 RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, |
- Node* const map, |
- Label* const if_isunmodified, |
- Label* const if_ismodified) { |
- Node* const native_context = LoadNativeContext(context); |
- Node* const regexp_fun = |
- LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
- Node* const initial_map = |
- LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); |
- Node* const has_initialmap = WordEqual(map, initial_map); |
- |
- GotoIfNot(has_initialmap, if_ismodified); |
- |
- Node* const initial_proto_initial_map = |
- LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX); |
- Node* const proto_map = LoadMap(LoadMapPrototype(map)); |
- Node* const proto_has_initialmap = |
- WordEqual(proto_map, initial_proto_initial_map); |
- |
- // TODO(ishell): Update this check once map changes for constant field |
- // tracking are landing. |
- |
- Branch(proto_has_initialmap, if_isunmodified, if_ismodified); |
+// Getters for the static properties lastMatch, lastParen, leftContext, and |
+// rightContext of the RegExp constructor. The properties are computed based |
+// on the captures array of the last successful match and the subject string |
+// of the last successful match. |
+BUILTIN(RegExpLastMatchGetter) { |
+ HandleScope scope(isolate); |
+ return *RegExpUtils::GenericCaptureGetter( |
+ isolate, isolate->regexp_last_match_info(), 0); |
} |
-Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context, |
- Node* const map) { |
- Label yup(this), nope(this), out(this); |
- Variable var_result(this, MachineRepresentation::kWord32); |
- |
- BranchIfFastRegExp(context, map, &yup, &nope); |
- |
- Bind(&yup); |
- var_result.Bind(Int32Constant(1)); |
- Goto(&out); |
+BUILTIN(RegExpLastParenGetter) { |
+ HandleScope scope(isolate); |
+ Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); |
+ const int length = match_info->NumberOfCaptureRegisters(); |
+ if (length <= 2) return isolate->heap()->empty_string(); // No captures. |
- Bind(&nope); |
- var_result.Bind(Int32Constant(0)); |
- Goto(&out); |
+ DCHECK_EQ(0, length % 2); |
+ const int last_capture = (length / 2) - 1; |
- Bind(&out); |
- return var_result.value(); |
+ // We match the SpiderMonkey behavior: return the substring defined by the |
+ // last pair (after the first pair) of elements of the capture array even if |
+ // it is empty. |
+ return *RegExpUtils::GenericCaptureGetter(isolate, match_info, last_capture); |
} |
-void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map, |
- Label* if_isunmodified, |
- Label* if_ismodified) { |
- Node* const native_context = LoadNativeContext(context); |
- Node* const initial_regexp_result_map = |
- LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); |
- |
- Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified, |
- if_ismodified); |
+BUILTIN(RegExpLeftContextGetter) { |
+ HandleScope scope(isolate); |
+ Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); |
+ const int start_index = match_info->Capture(0); |
+ Handle<String> last_subject(match_info->LastSubject()); |
+ return *isolate->factory()->NewSubString(last_subject, 0, start_index); |
} |
-// ES#sec-regexp.prototype.exec |
-// RegExp.prototype.exec ( string ) |
-TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const context = Parameter(4); |
- |
- // Ensure {maybe_receiver} is a JSRegExp. |
- Node* const regexp_map = ThrowIfNotInstanceType( |
- context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec"); |
- Node* const receiver = maybe_receiver; |
- |
- // Convert {maybe_string} to a String. |
- Node* const string = ToString(context, maybe_string); |
- |
- Label if_isfastpath(this), if_isslowpath(this); |
- Branch(IsInitialRegExpMap(context, regexp_map), &if_isfastpath, |
- &if_isslowpath); |
- |
- Bind(&if_isfastpath); |
- { |
- Node* const result = |
- RegExpPrototypeExecBody(context, receiver, string, true); |
- Return(result); |
- } |
- |
- Bind(&if_isslowpath); |
- { |
- Node* const result = |
- RegExpPrototypeExecBody(context, receiver, string, false); |
- Return(result); |
- } |
-} |
- |
-Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context, |
- Node* const regexp, |
- bool is_fastpath) { |
- Isolate* isolate = this->isolate(); |
- |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const int_one = IntPtrConstant(1); |
- Variable var_length(this, MachineType::PointerRepresentation(), int_zero); |
- Variable var_flags(this, MachineType::PointerRepresentation()); |
- |
- // First, count the number of characters we will need and check which flags |
- // are set. |
- |
- if (is_fastpath) { |
- // Refer to JSRegExp's flag property on the fast-path. |
- Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset); |
- Node* const flags_intptr = SmiUntag(flags_smi); |
- var_flags.Bind(flags_intptr); |
- |
-#define CASE_FOR_FLAG(FLAG) \ |
- do { \ |
- Label next(this); \ |
- GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \ |
- var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ |
- Goto(&next); \ |
- Bind(&next); \ |
- } while (false) |
- |
- CASE_FOR_FLAG(JSRegExp::kGlobal); |
- CASE_FOR_FLAG(JSRegExp::kIgnoreCase); |
- CASE_FOR_FLAG(JSRegExp::kMultiline); |
- CASE_FOR_FLAG(JSRegExp::kUnicode); |
- CASE_FOR_FLAG(JSRegExp::kSticky); |
-#undef CASE_FOR_FLAG |
- } else { |
- DCHECK(!is_fastpath); |
- |
- // Fall back to GetProperty stub on the slow-path. |
- var_flags.Bind(int_zero); |
- |
-#define CASE_FOR_FLAG(NAME, FLAG) \ |
- do { \ |
- Label next(this); \ |
- Node* const flag = GetProperty( \ |
- context, regexp, isolate->factory()->InternalizeUtf8String(NAME)); \ |
- Label if_isflagset(this); \ |
- BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \ |
- Bind(&if_isflagset); \ |
- var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ |
- var_flags.Bind(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \ |
- Goto(&next); \ |
- Bind(&next); \ |
- } while (false) |
- |
- CASE_FOR_FLAG("global", JSRegExp::kGlobal); |
- CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase); |
- CASE_FOR_FLAG("multiline", JSRegExp::kMultiline); |
- CASE_FOR_FLAG("unicode", JSRegExp::kUnicode); |
- CASE_FOR_FLAG("sticky", JSRegExp::kSticky); |
-#undef CASE_FOR_FLAG |
- } |
- |
- // Allocate a string of the required length and fill it with the corresponding |
- // char for each set flag. |
- |
- { |
- Node* const result = AllocateSeqOneByteString(context, var_length.value()); |
- Node* const flags_intptr = var_flags.value(); |
- |
- Variable var_offset( |
- this, MachineType::PointerRepresentation(), |
- IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag)); |
- |
-#define CASE_FOR_FLAG(FLAG, CHAR) \ |
- do { \ |
- Label next(this); \ |
- GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \ |
- Node* const value = Int32Constant(CHAR); \ |
- StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \ |
- var_offset.value(), value); \ |
- var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \ |
- Goto(&next); \ |
- Bind(&next); \ |
- } while (false) |
- |
- CASE_FOR_FLAG(JSRegExp::kGlobal, 'g'); |
- CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i'); |
- CASE_FOR_FLAG(JSRegExp::kMultiline, 'm'); |
- CASE_FOR_FLAG(JSRegExp::kUnicode, 'u'); |
- CASE_FOR_FLAG(JSRegExp::kSticky, 'y'); |
-#undef CASE_FOR_FLAG |
- |
- return result; |
- } |
-} |
- |
-// ES#sec-isregexp IsRegExp ( argument ) |
-Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context, |
- Node* const maybe_receiver) { |
- Label out(this), if_isregexp(this); |
- |
- Variable var_result(this, MachineRepresentation::kWord32, Int32Constant(0)); |
- |
- GotoIf(TaggedIsSmi(maybe_receiver), &out); |
- GotoIfNot(IsJSReceiver(maybe_receiver), &out); |
- |
- Node* const receiver = maybe_receiver; |
- |
- // Check @@match. |
- { |
- Node* const value = |
- GetProperty(context, receiver, isolate()->factory()->match_symbol()); |
- |
- Label match_isundefined(this), match_isnotundefined(this); |
- Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined); |
- |
- Bind(&match_isundefined); |
- Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isregexp, &out); |
- |
- Bind(&match_isnotundefined); |
- BranchIfToBooleanIsTrue(value, &if_isregexp, &out); |
- } |
- |
- Bind(&if_isregexp); |
- var_result.Bind(Int32Constant(1)); |
- Goto(&out); |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-// ES#sec-regexpinitialize |
-// Runtime Semantics: RegExpInitialize ( obj, pattern, flags ) |
-Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context, |
- Node* const regexp, |
- Node* const maybe_pattern, |
- Node* const maybe_flags) { |
- // Normalize pattern. |
- Node* const pattern = |
- Select(IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); }, |
- [=] { return ToString(context, maybe_pattern); }, |
- MachineRepresentation::kTagged); |
- |
- // Normalize flags. |
- Node* const flags = |
- Select(IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); }, |
- [=] { return ToString(context, maybe_flags); }, |
- MachineRepresentation::kTagged); |
- |
- // Initialize. |
- |
- return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, |
- pattern, flags); |
-} |
- |
-TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const context = Parameter(3); |
- |
- Node* const map = ThrowIfNotJSReceiver(context, maybe_receiver, |
- MessageTemplate::kRegExpNonObject, |
- "RegExp.prototype.flags"); |
- Node* const receiver = maybe_receiver; |
- |
- Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); |
- Branch(IsInitialRegExpMap(context, map), &if_isfastpath, &if_isslowpath); |
- |
- Bind(&if_isfastpath); |
- Return(FlagsGetter(context, receiver, true)); |
- |
- Bind(&if_isslowpath); |
- Return(FlagsGetter(context, receiver, false)); |
-} |
- |
-// ES#sec-regexp-pattern-flags |
-// RegExp ( pattern, flags ) |
-TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) { |
- Node* const pattern = Parameter(1); |
- Node* const flags = Parameter(2); |
- Node* const new_target = Parameter(3); |
- Node* const context = Parameter(5); |
- |
- Isolate* isolate = this->isolate(); |
- |
- Variable var_flags(this, MachineRepresentation::kTagged, flags); |
- Variable var_pattern(this, MachineRepresentation::kTagged, pattern); |
- Variable var_new_target(this, MachineRepresentation::kTagged, new_target); |
- |
- Node* const native_context = LoadNativeContext(context); |
- Node* const regexp_function = |
- LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
- |
- Node* const pattern_is_regexp = IsRegExp(context, pattern); |
- |
- { |
- Label next(this); |
- |
- GotoIfNot(IsUndefined(new_target), &next); |
- var_new_target.Bind(regexp_function); |
- |
- GotoIfNot(pattern_is_regexp, &next); |
- GotoIfNot(IsUndefined(flags), &next); |
- |
- Node* const value = |
- GetProperty(context, pattern, isolate->factory()->constructor_string()); |
- |
- GotoIfNot(WordEqual(value, regexp_function), &next); |
- Return(pattern); |
- |
- Bind(&next); |
- } |
- |
- { |
- Label next(this), if_patternisfastregexp(this), |
- if_patternisslowregexp(this); |
- GotoIf(TaggedIsSmi(pattern), &next); |
- |
- GotoIf(HasInstanceType(pattern, JS_REGEXP_TYPE), &if_patternisfastregexp); |
- |
- Branch(pattern_is_regexp, &if_patternisslowregexp, &next); |
- |
- Bind(&if_patternisfastregexp); |
- { |
- Node* const source = LoadObjectField(pattern, JSRegExp::kSourceOffset); |
- var_pattern.Bind(source); |
- |
- { |
- Label inner_next(this); |
- GotoIfNot(IsUndefined(flags), &inner_next); |
- |
- Node* const value = FlagsGetter(context, pattern, true); |
- var_flags.Bind(value); |
- Goto(&inner_next); |
- |
- Bind(&inner_next); |
- } |
- |
- Goto(&next); |
- } |
- |
- Bind(&if_patternisslowregexp); |
- { |
- { |
- Node* const value = |
- GetProperty(context, pattern, isolate->factory()->source_string()); |
- var_pattern.Bind(value); |
- } |
- |
- { |
- Label inner_next(this); |
- GotoIfNot(IsUndefined(flags), &inner_next); |
- |
- Node* const value = |
- GetProperty(context, pattern, isolate->factory()->flags_string()); |
- var_flags.Bind(value); |
- Goto(&inner_next); |
- |
- Bind(&inner_next); |
- } |
- |
- Goto(&next); |
- } |
- |
- Bind(&next); |
- } |
- |
- // Allocate. |
- |
- Variable var_regexp(this, MachineRepresentation::kTagged); |
- { |
- Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred), |
- next(this); |
- Branch(WordEqual(var_new_target.value(), regexp_function), |
- &allocate_jsregexp, &allocate_generic); |
- |
- Bind(&allocate_jsregexp); |
- { |
- Node* const initial_map = LoadObjectField( |
- regexp_function, JSFunction::kPrototypeOrInitialMapOffset); |
- Node* const regexp = AllocateJSObjectFromMap(initial_map); |
- var_regexp.Bind(regexp); |
- Goto(&next); |
- } |
- |
- Bind(&allocate_generic); |
- { |
- ConstructorBuiltinsAssembler constructor_assembler(this->state()); |
- Node* const regexp = constructor_assembler.EmitFastNewObject( |
- context, regexp_function, var_new_target.value()); |
- var_regexp.Bind(regexp); |
- Goto(&next); |
- } |
- |
- Bind(&next); |
- } |
- |
- Node* const result = RegExpInitialize(context, var_regexp.value(), |
- var_pattern.value(), var_flags.value()); |
- Return(result); |
-} |
- |
-// ES#sec-regexp.prototype.compile |
-// RegExp.prototype.compile ( pattern, flags ) |
-TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_pattern = Parameter(1); |
- Node* const maybe_flags = Parameter(2); |
- Node* const context = Parameter(5); |
- |
- ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, |
- "RegExp.prototype.compile"); |
- Node* const receiver = maybe_receiver; |
- |
- Variable var_flags(this, MachineRepresentation::kTagged, maybe_flags); |
- Variable var_pattern(this, MachineRepresentation::kTagged, maybe_pattern); |
- |
- // Handle a JSRegExp pattern. |
- { |
- Label next(this); |
- |
- GotoIf(TaggedIsSmi(maybe_pattern), &next); |
- GotoIfNot(HasInstanceType(maybe_pattern, JS_REGEXP_TYPE), &next); |
- |
- Node* const pattern = maybe_pattern; |
- |
- // {maybe_flags} must be undefined in this case, otherwise throw. |
- { |
- Label next(this); |
- GotoIf(IsUndefined(maybe_flags), &next); |
- |
- Node* const message_id = SmiConstant(MessageTemplate::kRegExpFlags); |
- TailCallRuntime(Runtime::kThrowTypeError, context, message_id); |
- |
- Bind(&next); |
- } |
- |
- Node* const new_flags = FlagsGetter(context, pattern, true); |
- Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset); |
- |
- var_flags.Bind(new_flags); |
- var_pattern.Bind(new_pattern); |
- |
- Goto(&next); |
- Bind(&next); |
- } |
- |
- Node* const result = RegExpInitialize(context, receiver, var_pattern.value(), |
- var_flags.value()); |
- Return(result); |
-} |
- |
-// ES6 21.2.5.10. |
-TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) { |
- Node* const receiver = Parameter(0); |
- Node* const context = Parameter(3); |
- |
- // Check whether we have an unmodified regexp instance. |
- Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred); |
- |
- GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp); |
- Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isjsregexp, |
- &if_isnotjsregexp); |
- |
- Bind(&if_isjsregexp); |
- { |
- Node* const source = LoadObjectField(receiver, JSRegExp::kSourceOffset); |
- Return(source); |
- } |
- |
- Bind(&if_isnotjsregexp); |
- { |
- Isolate* isolate = this->isolate(); |
- Node* const native_context = LoadNativeContext(context); |
- Node* const regexp_fun = |
- LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
- Node* const initial_map = |
- LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); |
- Node* const initial_prototype = LoadMapPrototype(initial_map); |
- |
- Label if_isprototype(this), if_isnotprototype(this); |
- Branch(WordEqual(receiver, initial_prototype), &if_isprototype, |
- &if_isnotprototype); |
- |
- Bind(&if_isprototype); |
- { |
- const int counter = v8::Isolate::kRegExpPrototypeSourceGetter; |
- Node* const counter_smi = SmiConstant(counter); |
- CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); |
- |
- Node* const result = |
- HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)")); |
- Return(result); |
- } |
- |
- Bind(&if_isnotprototype); |
- { |
- Node* const message_id = |
- SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); |
- Node* const method_name_str = |
- HeapConstant(isolate->factory()->NewStringFromAsciiChecked( |
- "RegExp.prototype.source")); |
- TailCallRuntime(Runtime::kThrowTypeError, context, message_id, |
- method_name_str); |
- } |
- } |
-} |
- |
-BUILTIN(RegExpPrototypeToString) { |
- HandleScope scope(isolate); |
- CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.toString"); |
- |
- if (*recv == isolate->regexp_function()->prototype()) { |
- isolate->CountUsage(v8::Isolate::kRegExpPrototypeToString); |
- } |
- |
- IncrementalStringBuilder builder(isolate); |
- |
- builder.AppendCharacter('/'); |
- { |
- Handle<Object> source; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
- isolate, source, |
- JSReceiver::GetProperty(recv, isolate->factory()->source_string())); |
- Handle<String> source_str; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, source_str, |
- Object::ToString(isolate, source)); |
- builder.AppendString(source_str); |
- } |
- |
- builder.AppendCharacter('/'); |
- { |
- Handle<Object> flags; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
- isolate, flags, |
- JSReceiver::GetProperty(recv, isolate->factory()->flags_string())); |
- Handle<String> flags_str; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags_str, |
- Object::ToString(isolate, flags)); |
- builder.AppendString(flags_str); |
- } |
- |
- RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); |
-} |
- |
-// Fast-path implementation for flag checks on an unmodified JSRegExp instance. |
-Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp, |
- JSRegExp::Flag flag) { |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); |
- Node* const mask = SmiConstant(Smi::FromInt(flag)); |
- Node* const is_flag_set = WordNotEqual(SmiAnd(flags, mask), smi_zero); |
- |
- return is_flag_set; |
-} |
- |
-// Load through the GetProperty stub. |
-Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context, |
- Node* const regexp, |
- JSRegExp::Flag flag) { |
- Factory* factory = isolate()->factory(); |
- |
- Label out(this); |
- Variable var_result(this, MachineRepresentation::kWord32); |
- |
- Handle<String> name; |
- switch (flag) { |
- case JSRegExp::kGlobal: |
- name = factory->global_string(); |
- break; |
- case JSRegExp::kIgnoreCase: |
- name = factory->ignoreCase_string(); |
- break; |
- case JSRegExp::kMultiline: |
- name = factory->multiline_string(); |
- break; |
- case JSRegExp::kSticky: |
- name = factory->sticky_string(); |
- break; |
- case JSRegExp::kUnicode: |
- name = factory->unicode_string(); |
- break; |
- default: |
- UNREACHABLE(); |
- } |
- |
- Node* const value = GetProperty(context, regexp, name); |
- |
- Label if_true(this), if_false(this); |
- BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
- |
- Bind(&if_true); |
- { |
- var_result.Bind(Int32Constant(1)); |
- Goto(&out); |
- } |
- |
- Bind(&if_false); |
- { |
- var_result.Bind(Int32Constant(0)); |
- Goto(&out); |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context, |
- Node* const regexp, |
- JSRegExp::Flag flag, |
- bool is_fastpath) { |
- return is_fastpath ? FastFlagGetter(regexp, flag) |
- : SlowFlagGetter(context, regexp, flag); |
-} |
- |
-void RegExpBuiltinsAssembler::FlagGetter(JSRegExp::Flag flag, |
- v8::Isolate::UseCounterFeature counter, |
- const char* method_name) { |
- Node* const receiver = Parameter(0); |
- Node* const context = Parameter(3); |
- |
- Isolate* isolate = this->isolate(); |
- |
- // Check whether we have an unmodified regexp instance. |
- Label if_isunmodifiedjsregexp(this), |
- if_isnotunmodifiedjsregexp(this, Label::kDeferred); |
- |
- GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp); |
- |
- Node* const receiver_map = LoadMap(receiver); |
- Node* const instance_type = LoadMapInstanceType(receiver_map); |
- |
- Branch(Word32Equal(instance_type, Int32Constant(JS_REGEXP_TYPE)), |
- &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp); |
- |
- Bind(&if_isunmodifiedjsregexp); |
- { |
- // Refer to JSRegExp's flag property on the fast-path. |
- Node* const is_flag_set = FastFlagGetter(receiver, flag); |
- Return(SelectBooleanConstant(is_flag_set)); |
- } |
- |
- Bind(&if_isnotunmodifiedjsregexp); |
- { |
- Node* const native_context = LoadNativeContext(context); |
- Node* const regexp_fun = |
- LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); |
- Node* const initial_map = |
- LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); |
- Node* const initial_prototype = LoadMapPrototype(initial_map); |
- |
- Label if_isprototype(this), if_isnotprototype(this); |
- Branch(WordEqual(receiver, initial_prototype), &if_isprototype, |
- &if_isnotprototype); |
- |
- Bind(&if_isprototype); |
- { |
- Node* const counter_smi = SmiConstant(Smi::FromInt(counter)); |
- CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); |
- Return(UndefinedConstant()); |
- } |
- |
- Bind(&if_isnotprototype); |
- { |
- Node* const message_id = |
- SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); |
- Node* const method_name_str = HeapConstant( |
- isolate->factory()->NewStringFromAsciiChecked(method_name)); |
- CallRuntime(Runtime::kThrowTypeError, context, message_id, |
- method_name_str); |
- Unreachable(); |
- } |
- } |
-} |
- |
-// ES6 21.2.5.4. |
-TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) { |
- FlagGetter(JSRegExp::kGlobal, v8::Isolate::kRegExpPrototypeOldFlagGetter, |
- "RegExp.prototype.global"); |
-} |
- |
-// ES6 21.2.5.5. |
-TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) { |
- FlagGetter(JSRegExp::kIgnoreCase, v8::Isolate::kRegExpPrototypeOldFlagGetter, |
- "RegExp.prototype.ignoreCase"); |
-} |
- |
-// ES6 21.2.5.7. |
-TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) { |
- FlagGetter(JSRegExp::kMultiline, v8::Isolate::kRegExpPrototypeOldFlagGetter, |
- "RegExp.prototype.multiline"); |
-} |
- |
-// ES6 21.2.5.12. |
-TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) { |
- FlagGetter(JSRegExp::kSticky, v8::Isolate::kRegExpPrototypeStickyGetter, |
- "RegExp.prototype.sticky"); |
-} |
- |
-// ES6 21.2.5.15. |
-TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { |
- FlagGetter(JSRegExp::kUnicode, v8::Isolate::kRegExpPrototypeUnicodeGetter, |
- "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. |
-#define DEFINE_CAPTURE_GETTER(i) \ |
- BUILTIN(RegExpCapture##i##Getter) { \ |
- HandleScope scope(isolate); \ |
- return *RegExpUtils::GenericCaptureGetter( \ |
- isolate, isolate->regexp_last_match_info(), i); \ |
- } |
-DEFINE_CAPTURE_GETTER(1) |
-DEFINE_CAPTURE_GETTER(2) |
-DEFINE_CAPTURE_GETTER(3) |
-DEFINE_CAPTURE_GETTER(4) |
-DEFINE_CAPTURE_GETTER(5) |
-DEFINE_CAPTURE_GETTER(6) |
-DEFINE_CAPTURE_GETTER(7) |
-DEFINE_CAPTURE_GETTER(8) |
-DEFINE_CAPTURE_GETTER(9) |
-#undef DEFINE_CAPTURE_GETTER |
- |
-// The properties `input` and `$_` are aliases for each other. When this |
-// value is set, the value it is set to is coerced to a string. |
-// Getter and setter for the input. |
- |
-BUILTIN(RegExpInputGetter) { |
- HandleScope scope(isolate); |
- Handle<Object> obj(isolate->regexp_last_match_info()->LastInput(), isolate); |
- return obj->IsUndefined(isolate) ? isolate->heap()->empty_string() |
- : String::cast(*obj); |
-} |
- |
-BUILTIN(RegExpInputSetter) { |
- HandleScope scope(isolate); |
- Handle<Object> value = args.atOrUndefined(isolate, 1); |
- Handle<String> str; |
- ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str, |
- Object::ToString(isolate, value)); |
- isolate->regexp_last_match_info()->SetLastInput(*str); |
- return isolate->heap()->undefined_value(); |
-} |
- |
-// Getters for the static properties lastMatch, lastParen, leftContext, and |
-// rightContext of the RegExp constructor. The properties are computed based |
-// on the captures array of the last successful match and the subject string |
-// of the last successful match. |
-BUILTIN(RegExpLastMatchGetter) { |
- HandleScope scope(isolate); |
- return *RegExpUtils::GenericCaptureGetter( |
- isolate, isolate->regexp_last_match_info(), 0); |
-} |
- |
-BUILTIN(RegExpLastParenGetter) { |
- HandleScope scope(isolate); |
- Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); |
- const int length = match_info->NumberOfCaptureRegisters(); |
- if (length <= 2) return isolate->heap()->empty_string(); // No captures. |
- |
- DCHECK_EQ(0, length % 2); |
- const int last_capture = (length / 2) - 1; |
- |
- // We match the SpiderMonkey behavior: return the substring defined by the |
- // last pair (after the first pair) of elements of the capture array even if |
- // it is empty. |
- return *RegExpUtils::GenericCaptureGetter(isolate, match_info, last_capture); |
-} |
- |
-BUILTIN(RegExpLeftContextGetter) { |
- HandleScope scope(isolate); |
- Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); |
- const int start_index = match_info->Capture(0); |
- Handle<String> last_subject(match_info->LastSubject()); |
- return *isolate->factory()->NewSubString(last_subject, 0, start_index); |
-} |
- |
-BUILTIN(RegExpRightContextGetter) { |
+BUILTIN(RegExpRightContextGetter) { |
HandleScope scope(isolate); |
Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); |
const int start_index = match_info->Capture(1); |
@@ -1455,1355 +136,5 @@ BUILTIN(RegExpRightContextGetter) { |
return *isolate->factory()->NewSubString(last_subject, start_index, len); |
} |
-// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) |
-Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, |
- Node* string) { |
- Isolate* isolate = this->isolate(); |
- |
- Node* const null = NullConstant(); |
- |
- Variable var_result(this, MachineRepresentation::kTagged); |
- Label out(this), if_isfastpath(this), if_isslowpath(this); |
- |
- Node* const map = LoadMap(regexp); |
- BranchIfFastRegExp(context, map, &if_isfastpath, &if_isslowpath); |
- |
- Bind(&if_isfastpath); |
- { |
- Node* const result = RegExpPrototypeExecBody(context, regexp, string, true); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&if_isslowpath); |
- { |
- // Take the slow path of fetching the exec property, calling it, and |
- // verifying its return value. |
- |
- // Get the exec property. |
- Node* const exec = |
- GetProperty(context, regexp, isolate->factory()->exec_string()); |
- |
- // Is {exec} callable? |
- Label if_iscallable(this), if_isnotcallable(this); |
- |
- GotoIf(TaggedIsSmi(exec), &if_isnotcallable); |
- |
- Node* const exec_map = LoadMap(exec); |
- Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable); |
- |
- Bind(&if_iscallable); |
- { |
- Callable call_callable = CodeFactory::Call(isolate); |
- Node* const result = CallJS(call_callable, context, exec, regexp, string); |
- |
- var_result.Bind(result); |
- GotoIf(WordEqual(result, null), &out); |
- |
- ThrowIfNotJSReceiver(context, result, |
- MessageTemplate::kInvalidRegExpExecResult, "unused"); |
- |
- Goto(&out); |
- } |
- |
- Bind(&if_isnotcallable); |
- { |
- ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, |
- "RegExp.prototype.exec"); |
- |
- Node* const result = |
- RegExpPrototypeExecBody(context, regexp, string, false); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-// ES#sec-regexp.prototype.test |
-// RegExp.prototype.test ( S ) |
-TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const context = Parameter(4); |
- |
- // Ensure {maybe_receiver} is a JSReceiver. |
- Node* const map = ThrowIfNotJSReceiver( |
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, |
- "RegExp.prototype.test"); |
- Node* const receiver = maybe_receiver; |
- |
- // Convert {maybe_string} to a String. |
- Node* const string = ToString(context, maybe_string); |
- |
- Label fast_path(this), slow_path(this); |
- BranchIfFastRegExp(context, map, &fast_path, &slow_path); |
- |
- Bind(&fast_path); |
- { |
- Label if_didnotmatch(this); |
- RegExpPrototypeExecBodyWithoutResult(context, receiver, string, |
- &if_didnotmatch, true); |
- Return(TrueConstant()); |
- |
- Bind(&if_didnotmatch); |
- Return(FalseConstant()); |
- } |
- |
- Bind(&slow_path); |
- { |
- // Call exec. |
- Node* const match_indices = RegExpExec(context, receiver, string); |
- |
- // Return true iff exec matched successfully. |
- Node* const result = |
- SelectBooleanConstant(WordNotEqual(match_indices, NullConstant())); |
- Return(result); |
- } |
-} |
- |
-Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string, |
- Node* const index, |
- Node* const is_unicode) { |
- // Default to last_index + 1. |
- Node* const index_plus_one = SmiAdd(index, SmiConstant(1)); |
- Variable var_result(this, MachineRepresentation::kTagged, index_plus_one); |
- |
- Label if_isunicode(this), out(this); |
- Branch(is_unicode, &if_isunicode, &out); |
- |
- Bind(&if_isunicode); |
- { |
- Node* const string_length = LoadStringLength(string); |
- GotoIfNot(SmiLessThan(index_plus_one, string_length), &out); |
- |
- Node* const lead = StringCharCodeAt(string, index); |
- GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)), |
- Int32Constant(0xD800)), |
- &out); |
- |
- Node* const trail = StringCharCodeAt(string, index_plus_one); |
- GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)), |
- Int32Constant(0xDC00)), |
- &out); |
- |
- // At a surrogate pair, return index + 2. |
- Node* const index_plus_two = SmiAdd(index, SmiConstant(2)); |
- var_result.Bind(index_plus_two); |
- |
- Goto(&out); |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-namespace { |
- |
-// Utility class implementing a growable fixed array through CSA. |
-class GrowableFixedArray { |
- typedef CodeStubAssembler::Label Label; |
- typedef CodeStubAssembler::Variable Variable; |
- |
- public: |
- explicit GrowableFixedArray(CodeStubAssembler* a) |
- : assembler_(a), |
- var_array_(a, MachineRepresentation::kTagged), |
- var_length_(a, MachineType::PointerRepresentation()), |
- var_capacity_(a, MachineType::PointerRepresentation()) { |
- Initialize(); |
- } |
- |
- Node* length() const { return var_length_.value(); } |
- |
- Variable* var_array() { return &var_array_; } |
- Variable* var_length() { return &var_length_; } |
- Variable* var_capacity() { return &var_capacity_; } |
- |
- void Push(Node* const value) { |
- CodeStubAssembler* a = assembler_; |
- |
- Node* const length = var_length_.value(); |
- Node* const capacity = var_capacity_.value(); |
- |
- Label 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 = ResizeFixedArray(length, new_capacity); |
- |
- var_capacity_.Bind(new_capacity); |
- var_array_.Bind(new_array); |
- a->Goto(&store); |
- } |
- |
- a->Bind(&store); |
- { |
- Node* const array = var_array_.value(); |
- a->StoreFixedArrayElement(array, length, value); |
- |
- 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); |
- |
- // Shrink to fit if necessary. |
- { |
- Label next(a); |
- |
- Node* const length = var_length_.value(); |
- Node* const capacity = var_capacity_.value(); |
- |
- a->GotoIf(a->WordEqual(length, capacity), &next); |
- |
- Node* const array = ResizeFixedArray(length, length); |
- var_array_.Bind(array); |
- var_capacity_.Bind(length); |
- a->Goto(&next); |
- |
- a->Bind(&next); |
- } |
- |
- 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; |
- |
- static const int kInitialArraySize = 8; |
- Node* const capacity = a->IntPtrConstant(kInitialArraySize); |
- Node* const array = a->AllocateFixedArray(kind, capacity); |
- |
- a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity, |
- Heap::kTheHoleValueRootIndex); |
- |
- 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; |
- } |
- |
- // Creates a new array with {new_capacity} and copies the first |
- // {element_count} elements from the current array. |
- Node* ResizeFixedArray(Node* const element_count, Node* const new_capacity) { |
- CodeStubAssembler* a = assembler_; |
- |
- CSA_ASSERT(a, a->IntPtrGreaterThan(element_count, a->IntPtrConstant(0))); |
- CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, a->IntPtrConstant(0))); |
- CSA_ASSERT(a, a->IntPtrGreaterThanOrEqual(new_capacity, element_count)); |
- |
- const ElementsKind kind = FAST_ELEMENTS; |
- const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; |
- const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; |
- const CodeStubAssembler::AllocationFlags flags = |
- CodeStubAssembler::kAllowLargeObjectAllocation; |
- |
- Node* const from_array = var_array_.value(); |
- Node* const to_array = |
- a->AllocateFixedArray(kind, new_capacity, mode, flags); |
- a->CopyFixedArrayElements(kind, from_array, kind, to_array, element_count, |
- new_capacity, barrier_mode, mode); |
- |
- return to_array; |
- } |
- |
- private: |
- CodeStubAssembler* const assembler_; |
- Variable var_array_; |
- Variable var_length_; |
- Variable var_capacity_; |
-}; |
- |
-} // namespace |
- |
-void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, |
- Node* const regexp, |
- Node* const string, |
- const bool is_fastpath) { |
- Isolate* const isolate = this->isolate(); |
- |
- Node* const null = NullConstant(); |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- |
- Node* const is_global = |
- FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath); |
- |
- Label if_isglobal(this), if_isnotglobal(this); |
- Branch(is_global, &if_isglobal, &if_isnotglobal); |
- |
- Bind(&if_isnotglobal); |
- { |
- Node* const result = |
- is_fastpath ? RegExpPrototypeExecBody(context, regexp, string, true) |
- : RegExpExec(context, regexp, string); |
- Return(result); |
- } |
- |
- Bind(&if_isglobal); |
- { |
- Node* const is_unicode = |
- FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath); |
- |
- StoreLastIndex(context, regexp, smi_zero, is_fastpath); |
- |
- // Allocate an array to store the resulting match strings. |
- |
- GrowableFixedArray array(this); |
- |
- // Loop preparations. Within the loop, collect results from RegExpExec |
- // and store match strings in the array. |
- |
- Variable* vars[] = {array.var_array(), array.var_length(), |
- array.var_capacity()}; |
- Label loop(this, 3, vars), out(this); |
- Goto(&loop); |
- |
- Bind(&loop); |
- { |
- Variable var_match(this, MachineRepresentation::kTagged); |
- |
- Label if_didmatch(this), if_didnotmatch(this); |
- if (is_fastpath) { |
- // On the fast path, grab the matching string from the raw match index |
- // array. |
- Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( |
- context, regexp, string, &if_didnotmatch, true); |
- |
- Node* const match_from = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex); |
- Node* const match_to = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); |
- |
- Node* match = SubString(context, string, match_from, match_to); |
- var_match.Bind(match); |
- |
- Goto(&if_didmatch); |
- } else { |
- DCHECK(!is_fastpath); |
- Node* const result = RegExpExec(context, regexp, string); |
- |
- Label load_match(this); |
- Branch(WordEqual(result, null), &if_didnotmatch, &load_match); |
- |
- Bind(&load_match); |
- { |
- Label fast_result(this), slow_result(this); |
- BranchIfFastRegExpResult(context, LoadMap(result), &fast_result, |
- &slow_result); |
- |
- Bind(&fast_result); |
- { |
- Node* const result_fixed_array = LoadElements(result); |
- Node* const match = LoadFixedArrayElement(result_fixed_array, 0); |
- |
- // The match is guaranteed to be a string on the fast path. |
- CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(match))); |
- |
- var_match.Bind(match); |
- Goto(&if_didmatch); |
- } |
- |
- Bind(&slow_result); |
- { |
- // TODO(ishell): Use GetElement stub once it's available. |
- Node* const match = GetProperty(context, result, smi_zero); |
- var_match.Bind(ToString(context, match)); |
- Goto(&if_didmatch); |
- } |
- } |
- } |
- |
- Bind(&if_didnotmatch); |
- { |
- // Return null if there were no matches, otherwise just exit the loop. |
- GotoIfNot(IntPtrEqual(array.length(), int_zero), &out); |
- Return(null); |
- } |
- |
- Bind(&if_didmatch); |
- { |
- Node* match = var_match.value(); |
- |
- // 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 = LoadStringLength(match); |
- GotoIfNot(SmiEqual(match_length, smi_zero), &loop); |
- |
- Node* last_index = LoadLastIndex(context, regexp, is_fastpath); |
- |
- Callable tolength_callable = CodeFactory::ToLength(isolate); |
- last_index = CallStub(tolength_callable, context, last_index); |
- |
- Node* const new_last_index = |
- AdvanceStringIndex(string, last_index, is_unicode); |
- |
- StoreLastIndex(context, regexp, new_last_index, is_fastpath); |
- |
- Goto(&loop); |
- } |
- } |
- |
- Bind(&out); |
- { |
- // Wrap the match in a JSArray. |
- |
- Node* const result = array.ToJSArray(context); |
- Return(result); |
- } |
- } |
-} |
- |
-// ES#sec-regexp.prototype-@@match |
-// RegExp.prototype [ @@match ] ( string ) |
-TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const context = Parameter(4); |
- |
- // Ensure {maybe_receiver} is a JSReceiver. |
- Node* const map = ThrowIfNotJSReceiver( |
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, |
- "RegExp.prototype.@@match"); |
- Node* const receiver = maybe_receiver; |
- |
- // Convert {maybe_string} to a String. |
- Node* const string = ToString(context, maybe_string); |
- |
- Label fast_path(this), slow_path(this); |
- BranchIfFastRegExp(context, map, &fast_path, &slow_path); |
- |
- Bind(&fast_path); |
- RegExpPrototypeMatchBody(context, receiver, string, true); |
- |
- Bind(&slow_path); |
- RegExpPrototypeMatchBody(context, receiver, string, false); |
-} |
- |
-void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast( |
- Node* const context, Node* const regexp, Node* const string) { |
- // Grab the initial value of last index. |
- Node* const previous_last_index = FastLoadLastIndex(regexp); |
- |
- // Ensure last index is 0. |
- FastStoreLastIndex(regexp, SmiConstant(Smi::kZero)); |
- |
- // Call exec. |
- Label if_didnotmatch(this); |
- Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( |
- context, regexp, string, &if_didnotmatch, true); |
- |
- // Successful match. |
- { |
- // Reset last index. |
- FastStoreLastIndex(regexp, previous_last_index); |
- |
- // Return the index of the match. |
- Node* const index = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex); |
- Return(index); |
- } |
- |
- Bind(&if_didnotmatch); |
- { |
- // Reset last index and return -1. |
- FastStoreLastIndex(regexp, previous_last_index); |
- Return(SmiConstant(-1)); |
- } |
-} |
- |
-void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( |
- Node* const context, Node* const regexp, Node* const string) { |
- Isolate* const isolate = this->isolate(); |
- |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- |
- // Grab the initial value of last index. |
- Node* const previous_last_index = SlowLoadLastIndex(context, regexp); |
- |
- // Ensure last index is 0. |
- { |
- Label next(this); |
- GotoIf(SameValue(previous_last_index, smi_zero), &next); |
- |
- SlowStoreLastIndex(context, regexp, smi_zero); |
- Goto(&next); |
- Bind(&next); |
- } |
- |
- // Call exec. |
- Node* const exec_result = RegExpExec(context, regexp, string); |
- |
- // Reset last index if necessary. |
- { |
- Label next(this); |
- Node* const current_last_index = SlowLoadLastIndex(context, regexp); |
- |
- GotoIf(SameValue(current_last_index, previous_last_index), &next); |
- |
- SlowStoreLastIndex(context, regexp, previous_last_index); |
- Goto(&next); |
- |
- Bind(&next); |
- } |
- |
- // Return -1 if no match was found. |
- { |
- Label next(this); |
- GotoIfNot(WordEqual(exec_result, NullConstant()), &next); |
- Return(SmiConstant(-1)); |
- Bind(&next); |
- } |
- |
- // Return the index of the match. |
- { |
- Label fast_result(this), slow_result(this, Label::kDeferred); |
- BranchIfFastRegExpResult(context, LoadMap(exec_result), &fast_result, |
- &slow_result); |
- |
- Bind(&fast_result); |
- { |
- Node* const index = |
- LoadObjectField(exec_result, JSRegExpResult::kIndexOffset); |
- Return(index); |
- } |
- |
- Bind(&slow_result); |
- { |
- Return(GetProperty(context, exec_result, |
- isolate->factory()->index_string())); |
- } |
- } |
-} |
- |
-// ES#sec-regexp.prototype-@@search |
-// RegExp.prototype [ @@search ] ( string ) |
-TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const context = Parameter(4); |
- |
- // Ensure {maybe_receiver} is a JSReceiver. |
- Node* const map = ThrowIfNotJSReceiver( |
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, |
- "RegExp.prototype.@@search"); |
- Node* const receiver = maybe_receiver; |
- |
- // Convert {maybe_string} to a String. |
- Node* const string = ToString(context, maybe_string); |
- |
- Label fast_path(this), slow_path(this); |
- BranchIfFastRegExp(context, map, &fast_path, &slow_path); |
- |
- Bind(&fast_path); |
- RegExpPrototypeSearchBodyFast(context, receiver, string); |
- |
- Bind(&slow_path); |
- RegExpPrototypeSearchBodySlow(context, receiver, string); |
-} |
- |
-// Generates the fast path for @@split. {regexp} is an unmodified JSRegExp, |
-// {string} is a String, and {limit} is a Smi. |
-void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context, |
- Node* const regexp, |
- Node* const string, |
- Node* const limit) { |
- Node* const null = NullConstant(); |
- Node* const smi_zero = SmiConstant(0); |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const int_limit = SmiUntag(limit); |
- |
- const ElementsKind kind = FAST_ELEMENTS; |
- const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; |
- |
- Node* const allocation_site = nullptr; |
- Node* const native_context = LoadNativeContext(context); |
- Node* const array_map = LoadJSArrayElementsMap(kind, native_context); |
- |
- Label return_empty_array(this, Label::kDeferred); |
- |
- // If limit is zero, return an empty array. |
- { |
- Label next(this), if_limitiszero(this, Label::kDeferred); |
- Branch(SmiEqual(limit, smi_zero), &return_empty_array, &next); |
- Bind(&next); |
- } |
- |
- Node* const string_length = LoadStringLength(string); |
- |
- // If passed the empty {string}, return either an empty array or a singleton |
- // array depending on whether the {regexp} matches. |
- { |
- Label next(this), if_stringisempty(this, Label::kDeferred); |
- Branch(SmiEqual(string_length, smi_zero), &if_stringisempty, &next); |
- |
- Bind(&if_stringisempty); |
- { |
- Node* const last_match_info = LoadContextElement( |
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- |
- Node* const match_indices = |
- IrregexpExec(context, regexp, string, smi_zero, last_match_info); |
- |
- Label return_singleton_array(this); |
- Branch(WordEqual(match_indices, null), &return_singleton_array, |
- &return_empty_array); |
- |
- Bind(&return_singleton_array); |
- { |
- Node* const length = SmiConstant(1); |
- Node* const capacity = IntPtrConstant(1); |
- Node* const result = AllocateJSArray(kind, array_map, capacity, length, |
- allocation_site, mode); |
- |
- Node* const fixed_array = LoadElements(result); |
- StoreFixedArrayElement(fixed_array, 0, string); |
- |
- Return(result); |
- } |
- } |
- |
- Bind(&next); |
- } |
- |
- // Loop preparations. |
- |
- GrowableFixedArray array(this); |
- |
- Variable var_last_matched_until(this, MachineRepresentation::kTagged); |
- Variable var_next_search_from(this, MachineRepresentation::kTagged); |
- |
- var_last_matched_until.Bind(smi_zero); |
- var_next_search_from.Bind(smi_zero); |
- |
- Variable* vars[] = {array.var_array(), array.var_length(), |
- array.var_capacity(), &var_last_matched_until, |
- &var_next_search_from}; |
- const int vars_count = sizeof(vars) / sizeof(vars[0]); |
- Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this); |
- Goto(&loop); |
- |
- Bind(&loop); |
- { |
- Node* const next_search_from = var_next_search_from.value(); |
- Node* const last_matched_until = var_last_matched_until.value(); |
- |
- // We're done if we've reached the end of the string. |
- { |
- Label next(this); |
- Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out, |
- &next); |
- Bind(&next); |
- } |
- |
- // Search for the given {regexp}. |
- |
- Node* const last_match_info = LoadContextElement( |
- native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- |
- Node* const match_indices = IrregexpExec(context, regexp, string, |
- next_search_from, last_match_info); |
- |
- // We're done if no match was found. |
- { |
- Label next(this); |
- Branch(WordEqual(match_indices, null), &push_suffix_and_out, &next); |
- Bind(&next); |
- } |
- |
- Node* const match_from = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex); |
- |
- // We're done if the match starts beyond the string. |
- { |
- Label next(this); |
- Branch(WordEqual(match_from, string_length), &push_suffix_and_out, &next); |
- Bind(&next); |
- } |
- |
- Node* const match_to = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); |
- |
- // Advance index and continue if the match is empty. |
- { |
- Label next(this); |
- |
- GotoIfNot(SmiEqual(match_to, next_search_from), &next); |
- GotoIfNot(SmiEqual(match_to, last_matched_until), &next); |
- |
- Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode); |
- Node* const new_next_search_from = |
- AdvanceStringIndex(string, next_search_from, is_unicode); |
- var_next_search_from.Bind(new_next_search_from); |
- Goto(&loop); |
- |
- Bind(&next); |
- } |
- |
- // A valid match was found, add the new substring to the array. |
- { |
- Node* const from = last_matched_until; |
- Node* const to = match_from; |
- |
- Node* const substr = SubString(context, string, from, to); |
- array.Push(substr); |
- |
- GotoIf(WordEqual(array.length(), int_limit), &out); |
- } |
- |
- // Add all captures to the array. |
- { |
- Node* const num_registers = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kNumberOfCapturesIndex); |
- Node* const int_num_registers = SmiUntag(num_registers); |
- |
- Variable var_reg(this, MachineType::PointerRepresentation()); |
- var_reg.Bind(IntPtrConstant(2)); |
- |
- Variable* vars[] = {array.var_array(), array.var_length(), |
- array.var_capacity(), &var_reg}; |
- const int vars_count = sizeof(vars) / sizeof(vars[0]); |
- Label nested_loop(this, vars_count, vars), nested_loop_out(this); |
- Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop, |
- &nested_loop_out); |
- |
- Bind(&nested_loop); |
- { |
- Node* const reg = var_reg.value(); |
- Node* const from = LoadFixedArrayElement( |
- match_indices, reg, |
- RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode); |
- Node* const to = LoadFixedArrayElement( |
- match_indices, reg, |
- (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode); |
- |
- Label select_capture(this), select_undefined(this), store_value(this); |
- Variable var_value(this, MachineRepresentation::kTagged); |
- Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined, |
- &select_capture); |
- |
- Bind(&select_capture); |
- { |
- Node* const substr = SubString(context, string, from, to); |
- var_value.Bind(substr); |
- Goto(&store_value); |
- } |
- |
- Bind(&select_undefined); |
- { |
- Node* const undefined = UndefinedConstant(); |
- var_value.Bind(undefined); |
- Goto(&store_value); |
- } |
- |
- Bind(&store_value); |
- { |
- array.Push(var_value.value()); |
- GotoIf(WordEqual(array.length(), int_limit), &out); |
- |
- Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2)); |
- var_reg.Bind(new_reg); |
- |
- Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop, |
- &nested_loop_out); |
- } |
- } |
- |
- Bind(&nested_loop_out); |
- } |
- |
- var_last_matched_until.Bind(match_to); |
- var_next_search_from.Bind(match_to); |
- Goto(&loop); |
- } |
- |
- Bind(&push_suffix_and_out); |
- { |
- Node* const from = var_last_matched_until.value(); |
- Node* const to = string_length; |
- |
- Node* const substr = SubString(context, string, from, to); |
- array.Push(substr); |
- |
- Goto(&out); |
- } |
- |
- Bind(&out); |
- { |
- Node* const result = array.ToJSArray(context); |
- Return(result); |
- } |
- |
- Bind(&return_empty_array); |
- { |
- Node* const length = smi_zero; |
- Node* const capacity = int_zero; |
- Node* const result = AllocateJSArray(kind, array_map, capacity, length, |
- allocation_site, mode); |
- Return(result); |
- } |
-} |
- |
-// Helper that skips a few initial checks. |
-TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) { |
- typedef RegExpSplitDescriptor Descriptor; |
- |
- Node* const regexp = Parameter(Descriptor::kReceiver); |
- Node* const string = Parameter(Descriptor::kString); |
- Node* const maybe_limit = Parameter(Descriptor::kLimit); |
- Node* const context = Parameter(Descriptor::kContext); |
- |
- CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp))); |
- CSA_ASSERT(this, IsString(string)); |
- |
- // TODO(jgruber): Even if map checks send us to the fast path, we still need |
- // to verify the constructor property and jump to the slow path if it has |
- // been changed. |
- |
- // Convert {maybe_limit} to a uint32, capping at the maximal smi value. |
- Variable var_limit(this, MachineRepresentation::kTagged); |
- Label if_limitissmimax(this), limit_done(this); |
- |
- GotoIf(IsUndefined(maybe_limit), &if_limitissmimax); |
- |
- { |
- Node* const limit = ToUint32(context, maybe_limit); |
- GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax); |
- |
- var_limit.Bind(limit); |
- Goto(&limit_done); |
- } |
- |
- Bind(&if_limitissmimax); |
- { |
- // TODO(jgruber): In this case, we can probably avoid generation of limit |
- // checks in Generate_RegExpPrototypeSplitBody. |
- var_limit.Bind(SmiConstant(Smi::kMaxValue)); |
- Goto(&limit_done); |
- } |
- |
- Bind(&limit_done); |
- { |
- Node* const limit = var_limit.value(); |
- RegExpPrototypeSplitBody(context, regexp, string, limit); |
- } |
-} |
- |
-// ES#sec-regexp.prototype-@@split |
-// RegExp.prototype [ @@split ] ( string, limit ) |
-TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const maybe_limit = Parameter(2); |
- Node* const context = Parameter(5); |
- |
- // Ensure {maybe_receiver} is a JSReceiver. |
- Node* const map = ThrowIfNotJSReceiver( |
- context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, |
- "RegExp.prototype.@@split"); |
- Node* const receiver = maybe_receiver; |
- |
- // Convert {maybe_string} to a String. |
- Node* const string = ToString(context, maybe_string); |
- |
- Label stub(this), runtime(this, Label::kDeferred); |
- BranchIfFastRegExp(context, map, &stub, &runtime); |
- |
- Bind(&stub); |
- Callable split_callable = CodeFactory::RegExpSplit(isolate()); |
- Return(CallStub(split_callable, context, receiver, string, maybe_limit)); |
- |
- Bind(&runtime); |
- Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string, |
- maybe_limit)); |
-} |
- |
-Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath( |
- Node* context, Node* regexp, Node* string, Node* replace_callable) { |
- // The fast path is reached only if {receiver} is a global unmodified |
- // JSRegExp instance and {replace_callable} is callable. |
- |
- Isolate* const isolate = this->isolate(); |
- |
- Node* const null = NullConstant(); |
- Node* const undefined = UndefinedConstant(); |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const int_one = IntPtrConstant(1); |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- |
- Node* const native_context = LoadNativeContext(context); |
- |
- Label out(this); |
- Variable var_result(this, MachineRepresentation::kTagged); |
- |
- // Set last index to 0. |
- FastStoreLastIndex(regexp, smi_zero); |
- |
- // Allocate {result_array}. |
- Node* result_array; |
- { |
- ElementsKind kind = FAST_ELEMENTS; |
- Node* const array_map = LoadJSArrayElementsMap(kind, native_context); |
- Node* const capacity = IntPtrConstant(16); |
- Node* const length = smi_zero; |
- Node* const allocation_site = nullptr; |
- ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS; |
- |
- result_array = AllocateJSArray(kind, array_map, capacity, length, |
- allocation_site, capacity_mode); |
- } |
- |
- // Call into runtime for RegExpExecMultiple. |
- Node* last_match_info = |
- LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp, |
- string, last_match_info, result_array); |
- |
- // Reset last index to 0. |
- FastStoreLastIndex(regexp, smi_zero); |
- |
- // If no matches, return the subject string. |
- var_result.Bind(string); |
- GotoIf(WordEqual(res, null), &out); |
- |
- // Reload last match info since it might have changed. |
- last_match_info = |
- LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- |
- Node* const res_length = LoadJSArrayLength(res); |
- Node* const res_elems = LoadElements(res); |
- CSA_ASSERT(this, HasInstanceType(res_elems, FIXED_ARRAY_TYPE)); |
- |
- Node* const num_capture_registers = LoadFixedArrayElement( |
- last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex); |
- |
- Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this), |
- create_result(this); |
- Branch(SmiEqual(num_capture_registers, SmiConstant(Smi::FromInt(2))), |
- &if_noexplicitcaptures, &if_hasexplicitcaptures); |
- |
- 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(this, MachineRepresentation::kTagged); |
- var_match_start.Bind(smi_zero); |
- |
- Node* const end = SmiUntag(res_length); |
- Variable var_i(this, MachineType::PointerRepresentation()); |
- var_i.Bind(int_zero); |
- |
- Variable* vars[] = {&var_i, &var_match_start}; |
- Label loop(this, 2, vars); |
- Goto(&loop); |
- Bind(&loop); |
- { |
- Node* const i = var_i.value(); |
- GotoIfNot(IntPtrLessThan(i, end), &create_result); |
- |
- Node* const elem = LoadFixedArrayElement(res_elems, i); |
- |
- Label if_issmi(this), if_isstring(this), loop_epilogue(this); |
- Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring); |
- |
- Bind(&if_issmi); |
- { |
- // Integers represent slices of the original string. |
- Label if_isnegativeorzero(this), if_ispositive(this); |
- BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero, |
- &if_ispositive); |
- |
- Bind(&if_ispositive); |
- { |
- Node* const int_elem = SmiUntag(elem); |
- Node* const new_match_start = |
- IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)), |
- WordAnd(int_elem, IntPtrConstant(0x7ff))); |
- var_match_start.Bind(SmiTag(new_match_start)); |
- Goto(&loop_epilogue); |
- } |
- |
- Bind(&if_isnegativeorzero); |
- { |
- Node* const next_i = IntPtrAdd(i, int_one); |
- var_i.Bind(next_i); |
- |
- Node* const next_elem = LoadFixedArrayElement(res_elems, next_i); |
- |
- Node* const new_match_start = SmiSub(next_elem, elem); |
- var_match_start.Bind(new_match_start); |
- Goto(&loop_epilogue); |
- } |
- } |
- |
- Bind(&if_isstring); |
- { |
- CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(elem))); |
- |
- Callable call_callable = CodeFactory::Call(isolate); |
- Node* const replacement_obj = |
- CallJS(call_callable, context, replace_callable, undefined, elem, |
- var_match_start.value(), string); |
- |
- Node* const replacement_str = ToString(context, replacement_obj); |
- StoreFixedArrayElement(res_elems, i, replacement_str); |
- |
- Node* const elem_length = LoadStringLength(elem); |
- Node* const new_match_start = |
- SmiAdd(var_match_start.value(), elem_length); |
- var_match_start.Bind(new_match_start); |
- |
- Goto(&loop_epilogue); |
- } |
- |
- Bind(&loop_epilogue); |
- { |
- var_i.Bind(IntPtrAdd(var_i.value(), int_one)); |
- Goto(&loop); |
- } |
- } |
- } |
- |
- Bind(&if_hasexplicitcaptures); |
- { |
- Node* const from = int_zero; |
- Node* const to = SmiUntag(res_length); |
- const int increment = 1; |
- |
- BuildFastLoop( |
- from, to, |
- [this, res_elems, isolate, native_context, context, undefined, |
- replace_callable](Node* index) { |
- Node* const elem = LoadFixedArrayElement(res_elems, index); |
- |
- Label do_continue(this); |
- GotoIf(TaggedIsSmi(elem), &do_continue); |
- |
- // elem must be an Array. |
- // Use the apply argument as backing for global RegExp properties. |
- |
- CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE)); |
- |
- // TODO(jgruber): Remove indirection through Call->ReflectApply. |
- Callable call_callable = CodeFactory::Call(isolate); |
- Node* const reflect_apply = |
- LoadContextElement(native_context, Context::REFLECT_APPLY_INDEX); |
- |
- Node* const replacement_obj = |
- 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 = ToString(context, replacement_obj); |
- StoreFixedArrayElement(res_elems, index, replacement_str); |
- |
- Goto(&do_continue); |
- Bind(&do_continue); |
- }, |
- increment, CodeStubAssembler::INTPTR_PARAMETERS, |
- CodeStubAssembler::IndexAdvanceMode::kPost); |
- |
- Goto(&create_result); |
- } |
- |
- Bind(&create_result); |
- { |
- Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context, |
- res, res_length, string); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( |
- Node* context, Node* regexp, Node* string, 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. |
- |
- Node* const int_zero = IntPtrConstant(0); |
- Node* const smi_zero = SmiConstant(Smi::kZero); |
- |
- Label out(this); |
- Variable var_result(this, MachineRepresentation::kTagged); |
- |
- // Load the last match info. |
- Node* const native_context = LoadNativeContext(context); |
- Node* const last_match_info = |
- LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); |
- |
- // Is {regexp} global? |
- Label if_isglobal(this), if_isnonglobal(this); |
- Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); |
- Node* const is_global = |
- WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal)); |
- Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal); |
- |
- Bind(&if_isglobal); |
- { |
- // Hand off global regexps to runtime. |
- FastStoreLastIndex(regexp, smi_zero); |
- Node* const result = |
- CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context, |
- string, regexp, replace_string, last_match_info); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&if_isnonglobal); |
- { |
- // Run exec, then manually construct the resulting string. |
- Label if_didnotmatch(this); |
- Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( |
- context, regexp, string, &if_didnotmatch, true); |
- |
- // Successful match. |
- { |
- Node* const subject_start = smi_zero; |
- Node* const match_start = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex); |
- Node* const match_end = LoadFixedArrayElement( |
- match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); |
- Node* const subject_end = LoadStringLength(string); |
- |
- Label if_replaceisempty(this), if_replaceisnotempty(this); |
- Node* const replace_length = LoadStringLength(replace_string); |
- Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty, |
- &if_replaceisnotempty); |
- |
- Bind(&if_replaceisempty); |
- { |
- // TODO(jgruber): We could skip many of the checks that using SubString |
- // here entails. |
- |
- Node* const first_part = |
- SubString(context, string, subject_start, match_start); |
- Node* const second_part = |
- SubString(context, string, match_end, subject_end); |
- |
- Node* const result = StringAdd(context, first_part, second_part); |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- |
- Bind(&if_replaceisnotempty); |
- { |
- Node* const first_part = |
- SubString(context, string, subject_start, match_start); |
- Node* const second_part = replace_string; |
- Node* const third_part = |
- SubString(context, string, match_end, subject_end); |
- |
- Node* result = StringAdd(context, first_part, second_part); |
- result = StringAdd(context, result, third_part); |
- |
- var_result.Bind(result); |
- Goto(&out); |
- } |
- } |
- |
- Bind(&if_didnotmatch); |
- { |
- var_result.Bind(string); |
- Goto(&out); |
- } |
- } |
- |
- Bind(&out); |
- return var_result.value(); |
-} |
- |
-// Helper that skips a few initial checks. |
-TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) { |
- typedef RegExpReplaceDescriptor Descriptor; |
- |
- Node* const regexp = Parameter(Descriptor::kReceiver); |
- Node* const string = Parameter(Descriptor::kString); |
- Node* const replace_value = Parameter(Descriptor::kReplaceValue); |
- Node* const context = Parameter(Descriptor::kContext); |
- |
- CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp))); |
- CSA_ASSERT(this, IsString(string)); |
- |
- Label checkreplacestring(this), if_iscallable(this), |
- runtime(this, Label::kDeferred); |
- |
- // 2. Is {replace_value} callable? |
- GotoIf(TaggedIsSmi(replace_value), &checkreplacestring); |
- Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable, |
- &checkreplacestring); |
- |
- // 3. Does ToString({replace_value}) contain '$'? |
- Bind(&checkreplacestring); |
- { |
- Callable tostring_callable = CodeFactory::ToString(isolate()); |
- Node* const replace_string = |
- CallStub(tostring_callable, context, replace_value); |
- |
- Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); |
- Node* const dollar_string = HeapConstant( |
- isolate()->factory()->LookupSingleCharacterStringFromCode('$')); |
- Node* const dollar_ix = CallStub(indexof_callable, context, replace_string, |
- dollar_string, SmiConstant(0)); |
- GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime); |
- |
- Return( |
- ReplaceSimpleStringFastPath(context, regexp, string, replace_string)); |
- } |
- |
- // {regexp} is unmodified and {replace_value} is callable. |
- Bind(&if_iscallable); |
- { |
- Node* const replace_fn = replace_value; |
- |
- // Check if the {regexp} is global. |
- Label if_isglobal(this), if_isnotglobal(this); |
- |
- Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); |
- Branch(is_global, &if_isglobal, &if_isnotglobal); |
- |
- Bind(&if_isglobal); |
- Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn)); |
- |
- Bind(&if_isnotglobal); |
- Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction, |
- context, string, regexp, replace_fn)); |
- } |
- |
- Bind(&runtime); |
- Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string, |
- replace_value)); |
-} |
- |
-// ES#sec-regexp.prototype-@@replace |
-// RegExp.prototype [ @@replace ] ( string, replaceValue ) |
-TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { |
- Node* const maybe_receiver = Parameter(0); |
- Node* const maybe_string = Parameter(1); |
- Node* const replace_value = Parameter(2); |
- Node* const context = Parameter(5); |
- |
- // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic: |
- // |
- // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace) |
- // if (IsCallable(replace)) { |
- // if (IsGlobal(receiver)) { |
- // // Called 'fast-path' but contains several runtime calls. |
- // ReplaceGlobalCallableFastPath() |
- // } else { |
- // CallRuntime(StringReplaceNonGlobalRegExpWithFunction) |
- // } |
- // } else { |
- // if (replace.contains("$")) { |
- // CallRuntime(RegExpReplace) |
- // } else { |
- // ReplaceSimpleStringFastPath() // Bails to runtime for global regexps. |
- // } |
- // } |
- |
- // Ensure {maybe_receiver} is a JSReceiver. |
- Node* const map = ThrowIfNotJSReceiver( |
- 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 = CallStub(tostring_callable, context, maybe_string); |
- |
- // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? |
- Label stub(this), runtime(this, Label::kDeferred); |
- BranchIfFastRegExp(context, map, &stub, &runtime); |
- |
- Bind(&stub); |
- Callable replace_callable = CodeFactory::RegExpReplace(isolate()); |
- Return(CallStub(replace_callable, context, receiver, string, replace_value)); |
- |
- Bind(&runtime); |
- Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string, |
- replace_value)); |
-} |
- |
-// Simple string matching functionality for internal use which does not modify |
-// the last match info. |
-TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) { |
- Node* const regexp = Parameter(1); |
- Node* const string = Parameter(2); |
- Node* const context = Parameter(5); |
- |
- Node* const null = NullConstant(); |
- Node* const smi_zero = SmiConstant(Smi::FromInt(0)); |
- |
- Node* const native_context = LoadNativeContext(context); |
- Node* const internal_match_info = LoadContextElement( |
- native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX); |
- |
- Node* const match_indices = |
- IrregexpExec(context, regexp, string, smi_zero, internal_match_info); |
- |
- Label if_matched(this), if_didnotmatch(this); |
- Branch(WordEqual(match_indices, null), &if_didnotmatch, &if_matched); |
- |
- Bind(&if_didnotmatch); |
- Return(null); |
- |
- Bind(&if_matched); |
- { |
- Node* result = |
- ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); |
- Return(result); |
- } |
-} |
- |
} // namespace internal |
} // namespace v8 |