1 // Copyright 2017 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "src/builtins/builtins-regexp-gen.h" 6 7 #include "src/builtins/builtins-constructor-gen.h" 8 #include "src/builtins/builtins-utils-gen.h" 9 #include "src/builtins/builtins.h" 10 #include "src/builtins/growable-fixed-array-gen.h" 11 #include "src/code-factory.h" 12 #include "src/code-stub-assembler.h" 13 #include "src/counters.h" 14 #include "src/heap/factory-inl.h" 15 #include "src/objects/js-regexp-string-iterator.h" 16 #include "src/objects/js-regexp.h" 17 #include "src/objects/regexp-match-info.h" 18 #include "src/regexp/regexp-macro-assembler.h" 19 20 namespace v8 { 21 namespace internal { 22 23 using compiler::Node; 24 template <class T> 25 using TNode = compiler::TNode<T>; 26 27 TNode<Smi> RegExpBuiltinsAssembler::SmiZero() { return SmiConstant(0); } 28 29 TNode<IntPtrT> RegExpBuiltinsAssembler::IntPtrZero() { 30 return IntPtrConstant(0); 31 } 32 33 // ----------------------------------------------------------------------------- 34 // ES6 section 21.2 RegExp Objects 35 36 TNode<JSRegExpResult> RegExpBuiltinsAssembler::AllocateRegExpResult( 37 TNode<Context> context, TNode<Smi> length, TNode<Smi> index, 38 TNode<String> input) { 39 #ifdef DEBUG 40 TNode<Smi> max_length = SmiConstant(JSArray::kInitialMaxFastElementArray); 41 CSA_ASSERT(this, SmiLessThanOrEqual(length, max_length)); 42 #endif // DEBUG 43 44 // Allocate the JSRegExpResult together with its elements fixed array. 45 // Initial preparations first. 46 47 TNode<IntPtrT> length_intptr = SmiUntag(length); 48 const ElementsKind elements_kind = PACKED_ELEMENTS; 49 50 TNode<IntPtrT> elements_size = GetFixedArrayAllocationSize( 51 length_intptr, elements_kind, INTPTR_PARAMETERS); 52 TNode<IntPtrT> total_size = 53 IntPtrAdd(elements_size, IntPtrConstant(JSRegExpResult::kSize)); 54 55 static const int kRegExpResultOffset = 0; 56 static const int kElementsOffset = 57 kRegExpResultOffset + JSRegExpResult::kSize; 58 59 // The folded allocation. 60 61 Node* result = Allocate(total_size); 62 Node* elements = InnerAllocate(result, kElementsOffset); 63 64 // Initialize the JSRegExpResult. 65 66 TNode<Context> native_context = LoadNativeContext(context); 67 TNode<Map> map = CAST( 68 LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX)); 69 StoreMapNoWriteBarrier(result, map); 70 71 StoreObjectFieldNoWriteBarrier(result, JSArray::kPropertiesOrHashOffset, 72 EmptyFixedArrayConstant()); 73 StoreObjectFieldNoWriteBarrier(result, JSArray::kElementsOffset, elements); 74 StoreObjectFieldNoWriteBarrier(result, JSArray::kLengthOffset, length); 75 76 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kIndexOffset, index); 77 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kInputOffset, input); 78 StoreObjectFieldNoWriteBarrier(result, JSRegExpResult::kGroupsOffset, 79 UndefinedConstant()); 80 81 // Initialize the elements. 82 83 DCHECK(!IsDoubleElementsKind(elements_kind)); 84 const Heap::RootListIndex map_index = Heap::kFixedArrayMapRootIndex; 85 DCHECK(Heap::RootIsImmortalImmovable(map_index)); 86 StoreMapNoWriteBarrier(elements, map_index); 87 StoreObjectFieldNoWriteBarrier(elements, FixedArray::kLengthOffset, length); 88 89 FillFixedArrayWithValue(elements_kind, elements, IntPtrZero(), length_intptr, 90 Heap::kUndefinedValueRootIndex); 91 92 return CAST(result); 93 } 94 95 TNode<Object> RegExpBuiltinsAssembler::RegExpCreate( 96 TNode<Context> context, TNode<Context> native_context, 97 TNode<Object> maybe_string, TNode<String> flags) { 98 TNode<JSFunction> regexp_function = 99 CAST(LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX)); 100 TNode<Map> initial_map = CAST(LoadObjectField( 101 regexp_function, JSFunction::kPrototypeOrInitialMapOffset)); 102 return RegExpCreate(context, initial_map, maybe_string, flags); 103 } 104 105 TNode<Object> RegExpBuiltinsAssembler::RegExpCreate(TNode<Context> context, 106 TNode<Map> initial_map, 107 TNode<Object> maybe_string, 108 TNode<String> flags) { 109 TNode<String> pattern = Select<String>( 110 IsUndefined(maybe_string), [=] { return EmptyStringConstant(); }, 111 [=] { return ToString_Inline(context, maybe_string); }); 112 TNode<Object> regexp = CAST(AllocateJSObjectFromMap(initial_map)); 113 return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, 114 pattern, flags); 115 } 116 117 TNode<Object> RegExpBuiltinsAssembler::FastLoadLastIndex( 118 TNode<JSRegExp> regexp) { 119 // Load the in-object field. 120 static const int field_offset = 121 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; 122 return LoadObjectField(regexp, field_offset); 123 } 124 125 TNode<Object> RegExpBuiltinsAssembler::SlowLoadLastIndex(TNode<Context> context, 126 TNode<Object> regexp) { 127 return GetProperty(context, regexp, isolate()->factory()->lastIndex_string()); 128 } 129 130 TNode<Object> RegExpBuiltinsAssembler::LoadLastIndex(TNode<Context> context, 131 TNode<Object> regexp, 132 bool is_fastpath) { 133 return is_fastpath ? FastLoadLastIndex(CAST(regexp)) 134 : SlowLoadLastIndex(context, regexp); 135 } 136 137 // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified 138 // JSRegExp instance. 139 void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) { 140 // Store the in-object field. 141 static const int field_offset = 142 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; 143 StoreObjectField(regexp, field_offset, value); 144 } 145 146 void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp, 147 Node* value) { 148 Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); 149 SetPropertyStrict(CAST(context), CAST(regexp), CAST(name), CAST(value)); 150 } 151 152 void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp, 153 Node* value, bool is_fastpath) { 154 if (is_fastpath) { 155 FastStoreLastIndex(regexp, value); 156 } else { 157 SlowStoreLastIndex(context, regexp, value); 158 } 159 } 160 161 TNode<JSRegExpResult> RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( 162 TNode<Context> context, TNode<JSReceiver> maybe_regexp, 163 TNode<RegExpMatchInfo> match_info, TNode<String> string) { 164 Label named_captures(this), out(this); 165 166 TNode<IntPtrT> num_indices = SmiUntag(CAST(LoadFixedArrayElement( 167 match_info, RegExpMatchInfo::kNumberOfCapturesIndex))); 168 TNode<Smi> num_results = SmiTag(WordShr(num_indices, 1)); 169 TNode<Smi> start = CAST( 170 LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex)); 171 TNode<Smi> end = CAST(LoadFixedArrayElement( 172 match_info, RegExpMatchInfo::kFirstCaptureIndex + 1)); 173 174 // Calculate the substring of the first match before creating the result array 175 // to avoid an unnecessary write barrier storing the first result. 176 177 TNode<String> first = 178 CAST(CallBuiltin(Builtins::kSubString, context, string, start, end)); 179 180 TNode<JSRegExpResult> result = 181 AllocateRegExpResult(context, num_results, start, string); 182 TNode<FixedArray> result_elements = CAST(LoadElements(result)); 183 184 StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER); 185 186 // If no captures exist we can skip named capture handling as well. 187 GotoIf(SmiEqual(num_results, SmiConstant(1)), &out); 188 189 // Store all remaining captures. 190 TNode<IntPtrT> limit = IntPtrAdd( 191 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices); 192 193 TVARIABLE(IntPtrT, var_from_cursor, 194 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2)); 195 TVARIABLE(IntPtrT, var_to_cursor, IntPtrConstant(1)); 196 197 Variable* vars[] = {&var_from_cursor, &var_to_cursor}; 198 Label loop(this, 2, vars); 199 200 Goto(&loop); 201 BIND(&loop); 202 { 203 TNode<IntPtrT> from_cursor = var_from_cursor.value(); 204 TNode<IntPtrT> to_cursor = var_to_cursor.value(); 205 TNode<Smi> start = CAST(LoadFixedArrayElement(match_info, from_cursor)); 206 207 Label next_iter(this); 208 GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter); 209 210 TNode<IntPtrT> from_cursor_plus1 = 211 IntPtrAdd(from_cursor, IntPtrConstant(1)); 212 TNode<Smi> end = CAST(LoadFixedArrayElement(match_info, from_cursor_plus1)); 213 214 TNode<String> capture = 215 CAST(CallBuiltin(Builtins::kSubString, context, string, start, end)); 216 StoreFixedArrayElement(result_elements, to_cursor, capture); 217 Goto(&next_iter); 218 219 BIND(&next_iter); 220 var_from_cursor = IntPtrAdd(from_cursor, IntPtrConstant(2)); 221 var_to_cursor = IntPtrAdd(to_cursor, IntPtrConstant(1)); 222 Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop, 223 &named_captures); 224 } 225 226 BIND(&named_captures); 227 { 228 CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1))); 229 230 // We reach this point only if captures exist, implying that this is an 231 // IRREGEXP JSRegExp. 232 233 TNode<JSRegExp> regexp = CAST(maybe_regexp); 234 235 // Preparations for named capture properties. Exit early if the result does 236 // not have any named captures to minimize performance impact. 237 238 TNode<FixedArray> data = 239 CAST(LoadObjectField(regexp, JSRegExp::kDataOffset)); 240 CSA_ASSERT(this, 241 SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)), 242 SmiConstant(JSRegExp::IRREGEXP))); 243 244 // The names fixed array associates names at even indices with a capture 245 // index at odd indices. 246 TNode<Object> maybe_names = 247 LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex); 248 GotoIf(WordEqual(maybe_names, SmiZero()), &out); 249 250 // Allocate a new object to store the named capture properties. 251 // TODO(jgruber): Could be optimized by adding the object map to the heap 252 // root list. 253 254 TNode<Context> native_context = LoadNativeContext(context); 255 TNode<Map> map = CAST(LoadContextElement( 256 native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP)); 257 TNode<NameDictionary> properties = 258 AllocateNameDictionary(NameDictionary::kInitialCapacity); 259 260 TNode<JSObject> group_object = 261 CAST(AllocateJSObjectFromMap(map, properties)); 262 StoreObjectField(result, JSRegExpResult::kGroupsOffset, group_object); 263 264 // One or more named captures exist, add a property for each one. 265 266 TNode<FixedArray> names = CAST(maybe_names); 267 TNode<IntPtrT> names_length = LoadAndUntagFixedArrayBaseLength(names); 268 CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrZero())); 269 270 TVARIABLE(IntPtrT, var_i, IntPtrZero()); 271 272 Variable* vars[] = {&var_i}; 273 const int vars_count = sizeof(vars) / sizeof(vars[0]); 274 Label loop(this, vars_count, vars); 275 276 Goto(&loop); 277 BIND(&loop); 278 { 279 TNode<IntPtrT> i = var_i.value(); 280 TNode<IntPtrT> i_plus_1 = IntPtrAdd(i, IntPtrConstant(1)); 281 TNode<IntPtrT> i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1)); 282 283 TNode<String> name = CAST(LoadFixedArrayElement(names, i)); 284 TNode<Smi> index = CAST(LoadFixedArrayElement(names, i_plus_1)); 285 TNode<HeapObject> capture = 286 CAST(LoadFixedArrayElement(result_elements, SmiUntag(index))); 287 288 // TODO(jgruber): Calling into runtime to create each property is slow. 289 // Either we should create properties entirely in CSA (should be doable), 290 // or only call runtime once and loop there. 291 CallRuntime(Runtime::kCreateDataProperty, context, group_object, name, 292 capture); 293 294 var_i = i_plus_2; 295 Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out, 296 &loop); 297 } 298 } 299 300 BIND(&out); 301 return result; 302 } 303 304 void RegExpBuiltinsAssembler::GetStringPointers( 305 Node* const string_data, Node* const offset, Node* const last_index, 306 Node* const string_length, String::Encoding encoding, 307 Variable* var_string_start, Variable* var_string_end) { 308 DCHECK_EQ(var_string_start->rep(), MachineType::PointerRepresentation()); 309 DCHECK_EQ(var_string_end->rep(), MachineType::PointerRepresentation()); 310 311 const ElementsKind kind = (encoding == String::ONE_BYTE_ENCODING) 312 ? UINT8_ELEMENTS 313 : UINT16_ELEMENTS; 314 315 Node* const from_offset = ElementOffsetFromIndex( 316 IntPtrAdd(offset, last_index), kind, INTPTR_PARAMETERS); 317 var_string_start->Bind(IntPtrAdd(string_data, from_offset)); 318 319 Node* const to_offset = ElementOffsetFromIndex( 320 IntPtrAdd(offset, string_length), kind, INTPTR_PARAMETERS); 321 var_string_end->Bind(IntPtrAdd(string_data, to_offset)); 322 } 323 324 TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal( 325 TNode<Context> context, TNode<JSRegExp> regexp, TNode<String> string, 326 TNode<Number> last_index, TNode<RegExpMatchInfo> match_info) { 327 // Just jump directly to runtime if native RegExp is not selected at compile 328 // time or if regexp entry in generated code is turned off runtime switch or 329 // at compilation. 330 #ifdef V8_INTERPRETED_REGEXP 331 return CAST(CallRuntime(Runtime::kRegExpExec, context, regexp, string, 332 last_index, match_info)); 333 #else // V8_INTERPRETED_REGEXP 334 ToDirectStringAssembler to_direct(state(), string); 335 336 TVARIABLE(HeapObject, var_result); 337 Label out(this), atom(this), runtime(this, Label::kDeferred); 338 339 // External constants. 340 TNode<ExternalReference> isolate_address = 341 ExternalConstant(ExternalReference::isolate_address(isolate())); 342 TNode<ExternalReference> regexp_stack_memory_address_address = 343 ExternalConstant( 344 ExternalReference::address_of_regexp_stack_memory_address(isolate())); 345 TNode<ExternalReference> regexp_stack_memory_size_address = ExternalConstant( 346 ExternalReference::address_of_regexp_stack_memory_size(isolate())); 347 TNode<ExternalReference> static_offsets_vector_address = ExternalConstant( 348 ExternalReference::address_of_static_offsets_vector(isolate())); 349 350 // At this point, last_index is definitely a canonicalized non-negative 351 // number, which implies that any non-Smi last_index is greater than 352 // the maximal string length. If lastIndex > string.length then the matcher 353 // must fail. 354 355 Label if_failure(this); 356 357 CSA_ASSERT(this, IsNumberNormalized(last_index)); 358 CSA_ASSERT(this, IsNumberPositive(last_index)); 359 GotoIf(TaggedIsNotSmi(last_index), &if_failure); 360 361 TNode<IntPtrT> int_string_length = LoadStringLengthAsWord(string); 362 TNode<IntPtrT> int_last_index = SmiUntag(CAST(last_index)); 363 364 GotoIf(UintPtrGreaterThan(int_last_index, int_string_length), &if_failure); 365 366 // Since the RegExp has been compiled, data contains a fixed array. 367 TNode<FixedArray> data = CAST(LoadObjectField(regexp, JSRegExp::kDataOffset)); 368 { 369 // Dispatch on the type of the RegExp. 370 { 371 Label next(this), unreachable(this, Label::kDeferred); 372 TNode<Int32T> tag = LoadAndUntagToWord32FixedArrayElement( 373 data, IntPtrConstant(JSRegExp::kTagIndex)); 374 375 int32_t values[] = { 376 JSRegExp::IRREGEXP, JSRegExp::ATOM, JSRegExp::NOT_COMPILED, 377 }; 378 Label* labels[] = {&next, &atom, &runtime}; 379 380 STATIC_ASSERT(arraysize(values) == arraysize(labels)); 381 Switch(tag, &unreachable, values, labels, arraysize(values)); 382 383 BIND(&unreachable); 384 Unreachable(); 385 386 BIND(&next); 387 } 388 389 // Check (number_of_captures + 1) * 2 <= offsets vector size 390 // Or number_of_captures <= offsets vector size / 2 - 1 391 TNode<Smi> capture_count = 392 CAST(LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex)); 393 394 const int kOffsetsSize = Isolate::kJSRegexpStaticOffsetsVectorSize; 395 STATIC_ASSERT(kOffsetsSize >= 2); 396 GotoIf(SmiAbove(capture_count, SmiConstant(kOffsetsSize / 2 - 1)), 397 &runtime); 398 } 399 400 // Ensure that a RegExp stack is allocated. This check is after branching off 401 // for ATOM regexps to avoid unnecessary trips to runtime. 402 { 403 TNode<IntPtrT> stack_size = UncheckedCast<IntPtrT>( 404 Load(MachineType::IntPtr(), regexp_stack_memory_size_address)); 405 GotoIf(IntPtrEqual(stack_size, IntPtrZero()), &runtime); 406 } 407 408 // Unpack the string if possible. 409 410 to_direct.TryToDirect(&runtime); 411 412 // Load the irregexp code object and offsets into the subject string. Both 413 // depend on whether the string is one- or two-byte. 414 415 TVARIABLE(RawPtrT, var_string_start); 416 TVARIABLE(RawPtrT, var_string_end); 417 TVARIABLE(Object, var_code); 418 419 { 420 TNode<RawPtrT> direct_string_data = to_direct.PointerToData(&runtime); 421 422 Label next(this), if_isonebyte(this), if_istwobyte(this, Label::kDeferred); 423 Branch(IsOneByteStringInstanceType(to_direct.instance_type()), 424 &if_isonebyte, &if_istwobyte); 425 426 BIND(&if_isonebyte); 427 { 428 GetStringPointers(direct_string_data, to_direct.offset(), int_last_index, 429 int_string_length, String::ONE_BYTE_ENCODING, 430 &var_string_start, &var_string_end); 431 var_code = 432 LoadFixedArrayElement(data, JSRegExp::kIrregexpLatin1CodeIndex); 433 Goto(&next); 434 } 435 436 BIND(&if_istwobyte); 437 { 438 GetStringPointers(direct_string_data, to_direct.offset(), int_last_index, 439 int_string_length, String::TWO_BYTE_ENCODING, 440 &var_string_start, &var_string_end); 441 var_code = LoadFixedArrayElement(data, JSRegExp::kIrregexpUC16CodeIndex); 442 Goto(&next); 443 } 444 445 BIND(&next); 446 } 447 448 // Check that the irregexp code has been generated for the actual string 449 // encoding. If it has, the field contains a code object; and otherwise it 450 // contains the uninitialized sentinel as a smi. 451 #ifdef DEBUG 452 { 453 Label next(this); 454 GotoIfNot(TaggedIsSmi(var_code.value()), &next); 455 CSA_ASSERT(this, SmiEqual(CAST(var_code.value()), 456 SmiConstant(JSRegExp::kUninitializedValue))); 457 Goto(&next); 458 BIND(&next); 459 } 460 #endif 461 462 GotoIf(TaggedIsSmi(var_code.value()), &runtime); 463 TNode<Code> code = CAST(var_code.value()); 464 465 Label if_success(this), if_exception(this, Label::kDeferred); 466 { 467 IncrementCounter(isolate()->counters()->regexp_entry_native(), 1); 468 469 // Set up args for the final call into generated Irregexp code. 470 471 MachineType type_int32 = MachineType::Int32(); 472 MachineType type_tagged = MachineType::AnyTagged(); 473 MachineType type_ptr = MachineType::Pointer(); 474 475 // Result: A NativeRegExpMacroAssembler::Result return code. 476 MachineType retval_type = type_int32; 477 478 // Argument 0: Original subject string. 479 MachineType arg0_type = type_tagged; 480 TNode<String> arg0 = string; 481 482 // Argument 1: Previous index. 483 MachineType arg1_type = type_int32; 484 TNode<Int32T> arg1 = TruncateIntPtrToInt32(int_last_index); 485 486 // Argument 2: Start of string data. 487 MachineType arg2_type = type_ptr; 488 TNode<RawPtrT> arg2 = var_string_start.value(); 489 490 // Argument 3: End of string data. 491 MachineType arg3_type = type_ptr; 492 TNode<RawPtrT> arg3 = var_string_end.value(); 493 494 // Argument 4: static offsets vector buffer. 495 MachineType arg4_type = type_ptr; 496 TNode<ExternalReference> arg4 = static_offsets_vector_address; 497 498 // Argument 5: Set the number of capture registers to zero to force global 499 // regexps to behave as non-global. This does not affect non-global 500 // regexps. 501 MachineType arg5_type = type_int32; 502 TNode<Int32T> arg5 = Int32Constant(0); 503 504 // Argument 6: Start (high end) of backtracking stack memory area. 505 TNode<RawPtrT> stack_start = UncheckedCast<RawPtrT>( 506 Load(MachineType::Pointer(), regexp_stack_memory_address_address)); 507 TNode<IntPtrT> stack_size = UncheckedCast<IntPtrT>( 508 Load(MachineType::IntPtr(), regexp_stack_memory_size_address)); 509 TNode<RawPtrT> stack_end = 510 ReinterpretCast<RawPtrT>(IntPtrAdd(stack_start, stack_size)); 511 512 MachineType arg6_type = type_ptr; 513 TNode<RawPtrT> arg6 = stack_end; 514 515 // Argument 7: Indicate that this is a direct call from JavaScript. 516 MachineType arg7_type = type_int32; 517 TNode<Int32T> arg7 = Int32Constant(1); 518 519 // Argument 8: Pass current isolate address. 520 MachineType arg8_type = type_ptr; 521 TNode<ExternalReference> arg8 = isolate_address; 522 523 TNode<RawPtrT> code_entry = ReinterpretCast<RawPtrT>( 524 IntPtrAdd(BitcastTaggedToWord(code), 525 IntPtrConstant(Code::kHeaderSize - kHeapObjectTag))); 526 527 TNode<Int32T> result = UncheckedCast<Int32T>(CallCFunction9( 528 retval_type, arg0_type, arg1_type, arg2_type, arg3_type, arg4_type, 529 arg5_type, arg6_type, arg7_type, arg8_type, code_entry, arg0, arg1, 530 arg2, arg3, arg4, arg5, arg6, arg7, arg8)); 531 532 // Check the result. 533 // We expect exactly one result since we force the called regexp to behave 534 // as non-global. 535 TNode<IntPtrT> int_result = ChangeInt32ToIntPtr(result); 536 GotoIf(IntPtrEqual(int_result, 537 IntPtrConstant(NativeRegExpMacroAssembler::SUCCESS)), 538 &if_success); 539 GotoIf(IntPtrEqual(int_result, 540 IntPtrConstant(NativeRegExpMacroAssembler::FAILURE)), 541 &if_failure); 542 GotoIf(IntPtrEqual(int_result, 543 IntPtrConstant(NativeRegExpMacroAssembler::EXCEPTION)), 544 &if_exception); 545 546 CSA_ASSERT(this, 547 IntPtrEqual(int_result, 548 IntPtrConstant(NativeRegExpMacroAssembler::RETRY))); 549 Goto(&runtime); 550 } 551 552 BIND(&if_success); 553 { 554 // Check that the last match info has space for the capture registers and 555 // the additional information. Ensure no overflow in add. 556 STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset); 557 TNode<Smi> available_slots = 558 SmiSub(LoadFixedArrayBaseLength(match_info), 559 SmiConstant(RegExpMatchInfo::kLastMatchOverhead)); 560 TNode<Smi> capture_count = 561 CAST(LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex)); 562 // Calculate number of register_count = (capture_count + 1) * 2. 563 TNode<Smi> register_count = 564 SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1); 565 GotoIf(SmiGreaterThan(register_count, available_slots), &runtime); 566 567 // Fill match_info. 568 569 StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex, 570 register_count, SKIP_WRITE_BARRIER); 571 StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex, 572 string); 573 StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex, 574 string); 575 576 // Fill match and capture offsets in match_info. 577 { 578 TNode<IntPtrT> limit_offset = ElementOffsetFromIndex( 579 register_count, INT32_ELEMENTS, SMI_PARAMETERS, 0); 580 581 TNode<IntPtrT> to_offset = ElementOffsetFromIndex( 582 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), PACKED_ELEMENTS, 583 INTPTR_PARAMETERS, RegExpMatchInfo::kHeaderSize - kHeapObjectTag); 584 TVARIABLE(IntPtrT, var_to_offset, to_offset); 585 586 VariableList vars({&var_to_offset}, zone()); 587 BuildFastLoop( 588 vars, IntPtrZero(), limit_offset, 589 [=, &var_to_offset](Node* offset) { 590 TNode<Int32T> value = UncheckedCast<Int32T>(Load( 591 MachineType::Int32(), static_offsets_vector_address, offset)); 592 TNode<Smi> smi_value = SmiFromInt32(value); 593 StoreNoWriteBarrier(MachineRepresentation::kTagged, match_info, 594 var_to_offset.value(), smi_value); 595 Increment(&var_to_offset, kPointerSize); 596 }, 597 kInt32Size, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); 598 } 599 600 var_result = match_info; 601 Goto(&out); 602 } 603 604 BIND(&if_failure); 605 { 606 var_result = NullConstant(); 607 Goto(&out); 608 } 609 610 BIND(&if_exception); 611 { 612 // A stack overflow was detected in RegExp code. 613 #ifdef DEBUG 614 TNode<ExternalReference> pending_exception_address = 615 ExternalConstant(ExternalReference::Create( 616 IsolateAddressId::kPendingExceptionAddress, isolate())); 617 CSA_ASSERT(this, IsTheHole(Load(MachineType::AnyTagged(), 618 pending_exception_address))); 619 #endif // DEBUG 620 CallRuntime(Runtime::kThrowStackOverflow, context); 621 Unreachable(); 622 } 623 624 BIND(&runtime); 625 { 626 var_result = CAST(CallRuntime(Runtime::kRegExpExec, context, regexp, string, 627 last_index, match_info)); 628 Goto(&out); 629 } 630 631 BIND(&atom); 632 { 633 // TODO(jgruber): A call with 4 args stresses register allocation, this 634 // should probably just be inlined. 635 var_result = CAST(CallBuiltin(Builtins::kRegExpExecAtom, context, regexp, 636 string, last_index, match_info)); 637 Goto(&out); 638 } 639 640 BIND(&out); 641 return var_result.value(); 642 #endif // V8_INTERPRETED_REGEXP 643 } 644 645 // ES#sec-regexp.prototype.exec 646 // RegExp.prototype.exec ( string ) 647 // Implements the core of RegExp.prototype.exec but without actually 648 // constructing the JSRegExpResult. Returns a fixed array containing match 649 // indices as returned by RegExpExecStub on successful match, and jumps to 650 // if_didnotmatch otherwise. 651 TNode<RegExpMatchInfo> 652 RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( 653 TNode<Context> context, TNode<JSReceiver> maybe_regexp, 654 TNode<String> string, Label* if_didnotmatch, const bool is_fastpath) { 655 if (!is_fastpath) { 656 ThrowIfNotInstanceType(context, maybe_regexp, JS_REGEXP_TYPE, 657 "RegExp.prototype.exec"); 658 } 659 660 TNode<JSRegExp> regexp = CAST(maybe_regexp); 661 662 TVARIABLE(HeapObject, var_result); 663 Label out(this); 664 665 // Load lastIndex. 666 TVARIABLE(Number, var_lastindex); 667 { 668 TNode<Object> regexp_lastindex = 669 LoadLastIndex(context, regexp, is_fastpath); 670 671 if (is_fastpath) { 672 // ToLength on a positive smi is a nop and can be skipped. 673 CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex)); 674 var_lastindex = CAST(regexp_lastindex); 675 } else { 676 // Omit ToLength if lastindex is a non-negative smi. 677 Label call_tolength(this, Label::kDeferred), is_smi(this), next(this); 678 Branch(TaggedIsPositiveSmi(regexp_lastindex), &is_smi, &call_tolength); 679 680 BIND(&call_tolength); 681 var_lastindex = ToLength_Inline(context, regexp_lastindex); 682 Goto(&next); 683 684 BIND(&is_smi); 685 var_lastindex = CAST(regexp_lastindex); 686 Goto(&next); 687 688 BIND(&next); 689 } 690 } 691 692 // Check whether the regexp is global or sticky, which determines whether we 693 // update last index later on. 694 TNode<Smi> flags = CAST(LoadObjectField(regexp, JSRegExp::kFlagsOffset)); 695 TNode<IntPtrT> is_global_or_sticky = WordAnd( 696 SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky)); 697 TNode<BoolT> should_update_last_index = 698 WordNotEqual(is_global_or_sticky, IntPtrZero()); 699 700 // Grab and possibly update last index. 701 Label run_exec(this); 702 { 703 Label if_doupdate(this), if_dontupdate(this); 704 Branch(should_update_last_index, &if_doupdate, &if_dontupdate); 705 706 BIND(&if_doupdate); 707 { 708 Label if_isoob(this, Label::kDeferred); 709 GotoIfNot(TaggedIsSmi(var_lastindex.value()), &if_isoob); 710 TNode<Smi> string_length = LoadStringLengthAsSmi(string); 711 GotoIfNot(SmiLessThanOrEqual(CAST(var_lastindex.value()), string_length), 712 &if_isoob); 713 Goto(&run_exec); 714 715 BIND(&if_isoob); 716 { 717 StoreLastIndex(context, regexp, SmiZero(), is_fastpath); 718 Goto(if_didnotmatch); 719 } 720 } 721 722 BIND(&if_dontupdate); 723 { 724 var_lastindex = SmiZero(); 725 Goto(&run_exec); 726 } 727 } 728 729 TNode<HeapObject> match_indices; 730 Label successful_match(this); 731 BIND(&run_exec); 732 { 733 // Get last match info from the context. 734 TNode<Context> native_context = LoadNativeContext(context); 735 TNode<RegExpMatchInfo> last_match_info = CAST(LoadContextElement( 736 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX)); 737 738 // Call the exec stub. 739 match_indices = RegExpExecInternal(context, regexp, string, 740 var_lastindex.value(), last_match_info); 741 var_result = match_indices; 742 743 // {match_indices} is either null or the RegExpMatchInfo array. 744 // Return early if exec failed, possibly updating last index. 745 GotoIfNot(IsNull(match_indices), &successful_match); 746 747 GotoIfNot(should_update_last_index, if_didnotmatch); 748 749 StoreLastIndex(context, regexp, SmiZero(), is_fastpath); 750 Goto(if_didnotmatch); 751 } 752 753 BIND(&successful_match); 754 { 755 GotoIfNot(should_update_last_index, &out); 756 757 // Update the new last index from {match_indices}. 758 TNode<Number> new_lastindex = CAST(LoadFixedArrayElement( 759 CAST(match_indices), RegExpMatchInfo::kFirstCaptureIndex + 1)); 760 761 StoreLastIndex(context, regexp, new_lastindex, is_fastpath); 762 Goto(&out); 763 } 764 765 BIND(&out); 766 return CAST(var_result.value()); 767 } 768 769 // ES#sec-regexp.prototype.exec 770 // RegExp.prototype.exec ( string ) 771 TNode<HeapObject> RegExpBuiltinsAssembler::RegExpPrototypeExecBody( 772 TNode<Context> context, TNode<JSReceiver> maybe_regexp, 773 TNode<String> string, const bool is_fastpath) { 774 TVARIABLE(HeapObject, var_result); 775 776 Label if_didnotmatch(this), out(this); 777 TNode<RegExpMatchInfo> match_indices = RegExpPrototypeExecBodyWithoutResult( 778 context, maybe_regexp, string, &if_didnotmatch, is_fastpath); 779 780 // Successful match. 781 { 782 var_result = ConstructNewResultFromMatchInfo(context, maybe_regexp, 783 match_indices, string); 784 Goto(&out); 785 } 786 787 BIND(&if_didnotmatch); 788 { 789 var_result = NullConstant(); 790 Goto(&out); 791 } 792 793 BIND(&out); 794 return var_result.value(); 795 } 796 797 Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver( 798 Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template, 799 char const* method_name) { 800 Label out(this), throw_exception(this, Label::kDeferred); 801 VARIABLE(var_value_map, MachineRepresentation::kTagged); 802 803 GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception); 804 805 // Load the instance type of the {value}. 806 var_value_map.Bind(LoadMap(maybe_receiver)); 807 Node* const value_instance_type = LoadMapInstanceType(var_value_map.value()); 808 809 Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception); 810 811 // The {value} is not a compatible receiver for this method. 812 BIND(&throw_exception); 813 { 814 Node* const value_str = 815 CallBuiltin(Builtins::kToString, context, maybe_receiver); 816 ThrowTypeError(context, msg_template, StringConstant(method_name), 817 value_str); 818 } 819 820 BIND(&out); 821 return var_value_map.value(); 822 } 823 824 Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context, 825 Node* const object, 826 Node* const map) { 827 Label out(this); 828 VARIABLE(var_result, MachineRepresentation::kWord32); 829 830 #ifdef V8_ENABLE_FORCE_SLOW_PATH 831 var_result.Bind(Int32Constant(0)); 832 GotoIfForceSlowPath(&out); 833 #endif 834 835 Node* const native_context = LoadNativeContext(context); 836 Node* const regexp_fun = 837 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 838 Node* const initial_map = 839 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 840 Node* const has_initialmap = WordEqual(map, initial_map); 841 842 var_result.Bind(has_initialmap); 843 GotoIfNot(has_initialmap, &out); 844 845 // The smi check is required to omit ToLength(lastIndex) calls with possible 846 // user-code execution on the fast path. 847 Node* const last_index = FastLoadLastIndex(CAST(object)); 848 var_result.Bind(TaggedIsPositiveSmi(last_index)); 849 Goto(&out); 850 851 BIND(&out); 852 return var_result.value(); 853 } 854 855 // We also return true if exec is undefined (and hence per spec) 856 // the original {exec} will be used. 857 TNode<BoolT> RegExpBuiltinsAssembler::IsFastRegExpWithOriginalExec( 858 TNode<Context> context, TNode<JSRegExp> object) { 859 CSA_ASSERT(this, TaggedIsNotSmi(object)); 860 Label out(this); 861 Label check_last_index(this); 862 TVARIABLE(BoolT, var_result); 863 864 #ifdef V8_ENABLE_FORCE_SLOW_PATH 865 var_result = BoolConstant(0); 866 GotoIfForceSlowPath(&out); 867 #endif 868 869 TNode<BoolT> is_regexp = HasInstanceType(object, JS_REGEXP_TYPE); 870 871 var_result = is_regexp; 872 GotoIfNot(is_regexp, &out); 873 874 TNode<Context> native_context = LoadNativeContext(context); 875 TNode<Object> original_exec = 876 LoadContextElement(native_context, Context::REGEXP_EXEC_FUNCTION_INDEX); 877 878 TNode<Object> regexp_exec = 879 GetProperty(context, object, isolate()->factory()->exec_string()); 880 881 TNode<BoolT> has_initialexec = WordEqual(regexp_exec, original_exec); 882 var_result = has_initialexec; 883 GotoIf(has_initialexec, &check_last_index); 884 TNode<BoolT> is_undefined = IsUndefined(regexp_exec); 885 var_result = is_undefined; 886 GotoIfNot(is_undefined, &out); 887 Goto(&check_last_index); 888 889 BIND(&check_last_index); 890 // The smi check is required to omit ToLength(lastIndex) calls with possible 891 // user-code execution on the fast path. 892 TNode<Object> last_index = FastLoadLastIndex(object); 893 var_result = TaggedIsPositiveSmi(last_index); 894 Goto(&out); 895 896 BIND(&out); 897 return var_result.value(); 898 } 899 900 Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context, 901 Node* const object) { 902 CSA_ASSERT(this, TaggedIsNotSmi(object)); 903 return IsFastRegExpNoPrototype(context, object, LoadMap(object)); 904 } 905 906 // RegExp fast path implementations rely on unmodified JSRegExp instances. 907 // We use a fairly coarse granularity for this and simply check whether both 908 // the regexp itself is unmodified (i.e. its map has not changed), its 909 // prototype is unmodified, and lastIndex is a non-negative smi. 910 void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, 911 Node* const object, 912 Node* const map, 913 Label* const if_isunmodified, 914 Label* const if_ismodified) { 915 CSA_ASSERT(this, WordEqual(LoadMap(object), map)); 916 917 GotoIfForceSlowPath(if_ismodified); 918 919 // TODO(ishell): Update this check once map changes for constant field 920 // tracking are landing. 921 922 Node* const native_context = LoadNativeContext(context); 923 Node* const regexp_fun = 924 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 925 Node* const initial_map = 926 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 927 Node* const has_initialmap = WordEqual(map, initial_map); 928 929 GotoIfNot(has_initialmap, if_ismodified); 930 931 Node* const initial_proto_initial_map = 932 LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX); 933 Node* const proto_map = LoadMap(LoadMapPrototype(map)); 934 Node* const proto_has_initialmap = 935 WordEqual(proto_map, initial_proto_initial_map); 936 937 GotoIfNot(proto_has_initialmap, if_ismodified); 938 939 // The smi check is required to omit ToLength(lastIndex) calls with possible 940 // user-code execution on the fast path. 941 Node* const last_index = FastLoadLastIndex(CAST(object)); 942 Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified); 943 } 944 945 void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, 946 Node* const object, 947 Label* const if_isunmodified, 948 Label* const if_ismodified) { 949 CSA_ASSERT(this, TaggedIsNotSmi(object)); 950 BranchIfFastRegExp(context, object, LoadMap(object), if_isunmodified, 951 if_ismodified); 952 } 953 954 TNode<BoolT> RegExpBuiltinsAssembler::IsFastRegExp(SloppyTNode<Context> context, 955 SloppyTNode<Object> object) { 956 Label yup(this), nope(this), out(this); 957 TVARIABLE(BoolT, var_result); 958 959 BranchIfFastRegExp(context, object, &yup, &nope); 960 961 BIND(&yup); 962 var_result = Int32TrueConstant(); 963 Goto(&out); 964 965 BIND(&nope); 966 var_result = Int32FalseConstant(); 967 Goto(&out); 968 969 BIND(&out); 970 return var_result.value(); 971 } 972 973 void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* const context, 974 Node* const object, 975 Label* if_isunmodified, 976 Label* if_ismodified) { 977 // Could be a Smi. 978 Node* const map = LoadReceiverMap(object); 979 980 Node* const native_context = LoadNativeContext(context); 981 Node* const initial_regexp_result_map = 982 LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); 983 984 Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified, 985 if_ismodified); 986 } 987 988 // Slow path stub for RegExpPrototypeExec to decrease code size. 989 TF_BUILTIN(RegExpPrototypeExecSlow, RegExpBuiltinsAssembler) { 990 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kReceiver)); 991 TNode<String> string = CAST(Parameter(Descriptor::kString)); 992 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 993 994 Return(RegExpPrototypeExecBody(context, regexp, string, false)); 995 } 996 997 // Fast path stub for ATOM regexps. String matching is done by StringIndexOf, 998 // and {match_info} is updated on success. 999 // The slow path is implemented in RegExpImpl::AtomExec. 1000 TF_BUILTIN(RegExpExecAtom, RegExpBuiltinsAssembler) { 1001 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kRegExp)); 1002 TNode<String> subject_string = CAST(Parameter(Descriptor::kString)); 1003 TNode<Smi> last_index = CAST(Parameter(Descriptor::kLastIndex)); 1004 TNode<FixedArray> match_info = CAST(Parameter(Descriptor::kMatchInfo)); 1005 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1006 1007 CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); 1008 1009 TNode<FixedArray> data = CAST(LoadObjectField(regexp, JSRegExp::kDataOffset)); 1010 CSA_ASSERT(this, 1011 SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)), 1012 SmiConstant(JSRegExp::ATOM))); 1013 1014 // Callers ensure that last_index is in-bounds. 1015 CSA_ASSERT(this, 1016 UintPtrLessThanOrEqual(SmiUntag(last_index), 1017 LoadStringLengthAsWord(subject_string))); 1018 1019 Node* const needle_string = 1020 LoadFixedArrayElement(data, JSRegExp::kAtomPatternIndex); 1021 CSA_ASSERT(this, IsString(needle_string)); 1022 1023 TNode<Smi> const match_from = 1024 CAST(CallBuiltin(Builtins::kStringIndexOf, context, subject_string, 1025 needle_string, last_index)); 1026 1027 Label if_failure(this), if_success(this); 1028 Branch(SmiEqual(match_from, SmiConstant(-1)), &if_failure, &if_success); 1029 1030 BIND(&if_success); 1031 { 1032 CSA_ASSERT(this, TaggedIsPositiveSmi(match_from)); 1033 CSA_ASSERT(this, UintPtrLessThan(SmiUntag(match_from), 1034 LoadStringLengthAsWord(subject_string))); 1035 1036 const int kNumRegisters = 2; 1037 STATIC_ASSERT(RegExpMatchInfo::kInitialCaptureIndices >= kNumRegisters); 1038 1039 TNode<Smi> const match_to = 1040 SmiAdd(match_from, LoadStringLengthAsSmi(needle_string)); 1041 1042 StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex, 1043 SmiConstant(kNumRegisters), SKIP_WRITE_BARRIER); 1044 StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex, 1045 subject_string); 1046 StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex, 1047 subject_string); 1048 StoreFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex, 1049 match_from, SKIP_WRITE_BARRIER); 1050 StoreFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex + 1, 1051 match_to, SKIP_WRITE_BARRIER); 1052 1053 Return(match_info); 1054 } 1055 1056 BIND(&if_failure); 1057 Return(NullConstant()); 1058 } 1059 1060 TF_BUILTIN(RegExpExecInternal, RegExpBuiltinsAssembler) { 1061 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kRegExp)); 1062 TNode<String> string = CAST(Parameter(Descriptor::kString)); 1063 TNode<Number> last_index = CAST(Parameter(Descriptor::kLastIndex)); 1064 TNode<RegExpMatchInfo> match_info = CAST(Parameter(Descriptor::kMatchInfo)); 1065 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1066 1067 CSA_ASSERT(this, IsNumberNormalized(last_index)); 1068 CSA_ASSERT(this, IsNumberPositive(last_index)); 1069 1070 Return(RegExpExecInternal(context, regexp, string, last_index, match_info)); 1071 } 1072 1073 // ES#sec-regexp.prototype.exec 1074 // RegExp.prototype.exec ( string ) 1075 TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { 1076 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 1077 TNode<Object> maybe_string = CAST(Parameter(Descriptor::kString)); 1078 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1079 1080 // Ensure {maybe_receiver} is a JSRegExp. 1081 ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, 1082 "RegExp.prototype.exec"); 1083 TNode<JSRegExp> receiver = CAST(maybe_receiver); 1084 1085 // Convert {maybe_string} to a String. 1086 TNode<String> string = ToString_Inline(context, maybe_string); 1087 1088 Label if_isfastpath(this), if_isslowpath(this); 1089 Branch(IsFastRegExpNoPrototype(context, receiver), &if_isfastpath, 1090 &if_isslowpath); 1091 1092 BIND(&if_isfastpath); 1093 Return(RegExpPrototypeExecBody(context, receiver, string, true)); 1094 1095 BIND(&if_isslowpath); 1096 Return(CallBuiltin(Builtins::kRegExpPrototypeExecSlow, context, receiver, 1097 string)); 1098 } 1099 1100 Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context, 1101 Node* const regexp, 1102 bool is_fastpath) { 1103 Isolate* isolate = this->isolate(); 1104 1105 TNode<IntPtrT> const int_one = IntPtrConstant(1); 1106 TVARIABLE(Smi, var_length, SmiZero()); 1107 TVARIABLE(IntPtrT, var_flags); 1108 1109 // First, count the number of characters we will need and check which flags 1110 // are set. 1111 1112 if (is_fastpath) { 1113 // Refer to JSRegExp's flag property on the fast-path. 1114 CSA_ASSERT(this, IsJSRegExp(regexp)); 1115 Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset); 1116 var_flags = SmiUntag(flags_smi); 1117 1118 #define CASE_FOR_FLAG(FLAG) \ 1119 do { \ 1120 Label next(this); \ 1121 GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \ 1122 var_length = SmiAdd(var_length.value(), SmiConstant(1)); \ 1123 Goto(&next); \ 1124 BIND(&next); \ 1125 } while (false) 1126 1127 CASE_FOR_FLAG(JSRegExp::kGlobal); 1128 CASE_FOR_FLAG(JSRegExp::kIgnoreCase); 1129 CASE_FOR_FLAG(JSRegExp::kMultiline); 1130 CASE_FOR_FLAG(JSRegExp::kDotAll); 1131 CASE_FOR_FLAG(JSRegExp::kUnicode); 1132 CASE_FOR_FLAG(JSRegExp::kSticky); 1133 #undef CASE_FOR_FLAG 1134 } else { 1135 DCHECK(!is_fastpath); 1136 1137 // Fall back to GetProperty stub on the slow-path. 1138 var_flags = IntPtrZero(); 1139 1140 #define CASE_FOR_FLAG(NAME, FLAG) \ 1141 do { \ 1142 Label next(this); \ 1143 Node* const flag = GetProperty( \ 1144 context, regexp, isolate->factory()->InternalizeUtf8String(NAME)); \ 1145 Label if_isflagset(this); \ 1146 BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \ 1147 BIND(&if_isflagset); \ 1148 var_length = SmiAdd(var_length.value(), SmiConstant(1)); \ 1149 var_flags = Signed(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \ 1150 Goto(&next); \ 1151 BIND(&next); \ 1152 } while (false) 1153 1154 CASE_FOR_FLAG("global", JSRegExp::kGlobal); 1155 CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase); 1156 CASE_FOR_FLAG("multiline", JSRegExp::kMultiline); 1157 CASE_FOR_FLAG("dotAll", JSRegExp::kDotAll); 1158 CASE_FOR_FLAG("unicode", JSRegExp::kUnicode); 1159 CASE_FOR_FLAG("sticky", JSRegExp::kSticky); 1160 #undef CASE_FOR_FLAG 1161 } 1162 1163 // Allocate a string of the required length and fill it with the corresponding 1164 // char for each set flag. 1165 1166 { 1167 Node* const result = AllocateSeqOneByteString(context, var_length.value()); 1168 1169 VARIABLE(var_offset, MachineType::PointerRepresentation(), 1170 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag)); 1171 1172 #define CASE_FOR_FLAG(FLAG, CHAR) \ 1173 do { \ 1174 Label next(this); \ 1175 GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \ 1176 Node* const value = Int32Constant(CHAR); \ 1177 StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \ 1178 var_offset.value(), value); \ 1179 var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \ 1180 Goto(&next); \ 1181 BIND(&next); \ 1182 } while (false) 1183 1184 CASE_FOR_FLAG(JSRegExp::kGlobal, 'g'); 1185 CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i'); 1186 CASE_FOR_FLAG(JSRegExp::kMultiline, 'm'); 1187 CASE_FOR_FLAG(JSRegExp::kDotAll, 's'); 1188 CASE_FOR_FLAG(JSRegExp::kUnicode, 'u'); 1189 CASE_FOR_FLAG(JSRegExp::kSticky, 'y'); 1190 #undef CASE_FOR_FLAG 1191 1192 return result; 1193 } 1194 } 1195 1196 // ES#sec-isregexp IsRegExp ( argument ) 1197 Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context, 1198 Node* const maybe_receiver) { 1199 Label out(this), if_isregexp(this); 1200 1201 VARIABLE(var_result, MachineRepresentation::kWord32, Int32Constant(0)); 1202 1203 GotoIf(TaggedIsSmi(maybe_receiver), &out); 1204 GotoIfNot(IsJSReceiver(maybe_receiver), &out); 1205 1206 Node* const receiver = maybe_receiver; 1207 1208 // Check @@match. 1209 { 1210 Node* const value = 1211 GetProperty(context, receiver, isolate()->factory()->match_symbol()); 1212 1213 Label match_isundefined(this), match_isnotundefined(this); 1214 Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined); 1215 1216 BIND(&match_isundefined); 1217 Branch(IsJSRegExp(receiver), &if_isregexp, &out); 1218 1219 BIND(&match_isnotundefined); 1220 BranchIfToBooleanIsTrue(value, &if_isregexp, &out); 1221 } 1222 1223 BIND(&if_isregexp); 1224 var_result.Bind(Int32Constant(1)); 1225 Goto(&out); 1226 1227 BIND(&out); 1228 return var_result.value(); 1229 } 1230 1231 // ES#sec-regexpinitialize 1232 // Runtime Semantics: RegExpInitialize ( obj, pattern, flags ) 1233 Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context, 1234 Node* const regexp, 1235 Node* const maybe_pattern, 1236 Node* const maybe_flags) { 1237 CSA_ASSERT(this, IsJSRegExp(regexp)); 1238 1239 // Normalize pattern. 1240 TNode<Object> const pattern = Select<Object>( 1241 IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); }, 1242 [=] { return ToString_Inline(context, maybe_pattern); }); 1243 1244 // Normalize flags. 1245 TNode<Object> const flags = Select<Object>( 1246 IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); }, 1247 [=] { return ToString_Inline(context, maybe_flags); }); 1248 1249 // Initialize. 1250 1251 return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, 1252 pattern, flags); 1253 } 1254 1255 // ES #sec-get-regexp.prototype.flags 1256 TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { 1257 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 1258 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1259 1260 TNode<Map> map = CAST(ThrowIfNotJSReceiver(context, maybe_receiver, 1261 MessageTemplate::kRegExpNonObject, 1262 "RegExp.prototype.flags")); 1263 TNode<JSReceiver> receiver = CAST(maybe_receiver); 1264 1265 Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); 1266 BranchIfFastRegExp(context, receiver, map, &if_isfastpath, &if_isslowpath); 1267 1268 BIND(&if_isfastpath); 1269 Return(FlagsGetter(context, receiver, true)); 1270 1271 BIND(&if_isslowpath); 1272 Return(FlagsGetter(context, receiver, false)); 1273 } 1274 1275 // ES#sec-regexp-pattern-flags 1276 // RegExp ( pattern, flags ) 1277 TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) { 1278 TNode<Object> pattern = CAST(Parameter(Descriptor::kPattern)); 1279 TNode<Object> flags = CAST(Parameter(Descriptor::kFlags)); 1280 TNode<Object> new_target = CAST(Parameter(Descriptor::kJSNewTarget)); 1281 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1282 1283 Isolate* isolate = this->isolate(); 1284 1285 VARIABLE(var_flags, MachineRepresentation::kTagged, flags); 1286 VARIABLE(var_pattern, MachineRepresentation::kTagged, pattern); 1287 VARIABLE(var_new_target, MachineRepresentation::kTagged, new_target); 1288 1289 Node* const native_context = LoadNativeContext(context); 1290 Node* const regexp_function = 1291 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 1292 1293 Node* const pattern_is_regexp = IsRegExp(context, pattern); 1294 1295 { 1296 Label next(this); 1297 1298 GotoIfNot(IsUndefined(new_target), &next); 1299 var_new_target.Bind(regexp_function); 1300 1301 GotoIfNot(pattern_is_regexp, &next); 1302 GotoIfNot(IsUndefined(flags), &next); 1303 1304 Node* const value = 1305 GetProperty(context, pattern, isolate->factory()->constructor_string()); 1306 1307 GotoIfNot(WordEqual(value, regexp_function), &next); 1308 Return(pattern); 1309 1310 BIND(&next); 1311 } 1312 1313 { 1314 Label next(this), if_patternisfastregexp(this), 1315 if_patternisslowregexp(this); 1316 GotoIf(TaggedIsSmi(pattern), &next); 1317 1318 GotoIf(IsJSRegExp(CAST(pattern)), &if_patternisfastregexp); 1319 1320 Branch(pattern_is_regexp, &if_patternisslowregexp, &next); 1321 1322 BIND(&if_patternisfastregexp); 1323 { 1324 Node* const source = 1325 LoadObjectField(CAST(pattern), JSRegExp::kSourceOffset); 1326 var_pattern.Bind(source); 1327 1328 { 1329 Label inner_next(this); 1330 GotoIfNot(IsUndefined(flags), &inner_next); 1331 1332 Node* const value = FlagsGetter(context, pattern, true); 1333 var_flags.Bind(value); 1334 Goto(&inner_next); 1335 1336 BIND(&inner_next); 1337 } 1338 1339 Goto(&next); 1340 } 1341 1342 BIND(&if_patternisslowregexp); 1343 { 1344 { 1345 Node* const value = 1346 GetProperty(context, pattern, isolate->factory()->source_string()); 1347 var_pattern.Bind(value); 1348 } 1349 1350 { 1351 Label inner_next(this); 1352 GotoIfNot(IsUndefined(flags), &inner_next); 1353 1354 Node* const value = 1355 GetProperty(context, pattern, isolate->factory()->flags_string()); 1356 var_flags.Bind(value); 1357 Goto(&inner_next); 1358 1359 BIND(&inner_next); 1360 } 1361 1362 Goto(&next); 1363 } 1364 1365 BIND(&next); 1366 } 1367 1368 // Allocate. 1369 1370 VARIABLE(var_regexp, MachineRepresentation::kTagged); 1371 { 1372 Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred), 1373 next(this); 1374 Branch(WordEqual(var_new_target.value(), regexp_function), 1375 &allocate_jsregexp, &allocate_generic); 1376 1377 BIND(&allocate_jsregexp); 1378 { 1379 Node* const initial_map = LoadObjectField( 1380 regexp_function, JSFunction::kPrototypeOrInitialMapOffset); 1381 Node* const regexp = AllocateJSObjectFromMap(initial_map); 1382 var_regexp.Bind(regexp); 1383 Goto(&next); 1384 } 1385 1386 BIND(&allocate_generic); 1387 { 1388 ConstructorBuiltinsAssembler constructor_assembler(this->state()); 1389 Node* const regexp = constructor_assembler.EmitFastNewObject( 1390 context, regexp_function, var_new_target.value()); 1391 var_regexp.Bind(regexp); 1392 Goto(&next); 1393 } 1394 1395 BIND(&next); 1396 } 1397 1398 Node* const result = RegExpInitialize(context, var_regexp.value(), 1399 var_pattern.value(), var_flags.value()); 1400 Return(result); 1401 } 1402 1403 // ES#sec-regexp.prototype.compile 1404 // RegExp.prototype.compile ( pattern, flags ) 1405 TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) { 1406 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 1407 TNode<Object> maybe_pattern = CAST(Parameter(Descriptor::kPattern)); 1408 TNode<Object> maybe_flags = CAST(Parameter(Descriptor::kFlags)); 1409 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1410 1411 ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, 1412 "RegExp.prototype.compile"); 1413 Node* const receiver = maybe_receiver; 1414 1415 VARIABLE(var_flags, MachineRepresentation::kTagged, maybe_flags); 1416 VARIABLE(var_pattern, MachineRepresentation::kTagged, maybe_pattern); 1417 1418 // Handle a JSRegExp pattern. 1419 { 1420 Label next(this); 1421 1422 GotoIf(TaggedIsSmi(maybe_pattern), &next); 1423 GotoIfNot(IsJSRegExp(CAST(maybe_pattern)), &next); 1424 1425 Node* const pattern = maybe_pattern; 1426 1427 // {maybe_flags} must be undefined in this case, otherwise throw. 1428 { 1429 Label next(this); 1430 GotoIf(IsUndefined(maybe_flags), &next); 1431 1432 ThrowTypeError(context, MessageTemplate::kRegExpFlags); 1433 1434 BIND(&next); 1435 } 1436 1437 Node* const new_flags = FlagsGetter(context, pattern, true); 1438 Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset); 1439 1440 var_flags.Bind(new_flags); 1441 var_pattern.Bind(new_pattern); 1442 1443 Goto(&next); 1444 BIND(&next); 1445 } 1446 1447 Node* const result = RegExpInitialize(context, receiver, var_pattern.value(), 1448 var_flags.value()); 1449 Return(result); 1450 } 1451 1452 // ES6 21.2.5.10. 1453 // ES #sec-get-regexp.prototype.source 1454 TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) { 1455 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1456 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1457 1458 // Check whether we have an unmodified regexp instance. 1459 Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred); 1460 1461 GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp); 1462 Branch(IsJSRegExp(CAST(receiver)), &if_isjsregexp, &if_isnotjsregexp); 1463 1464 BIND(&if_isjsregexp); 1465 Return(LoadObjectField(CAST(receiver), JSRegExp::kSourceOffset)); 1466 1467 BIND(&if_isnotjsregexp); 1468 { 1469 Isolate* isolate = this->isolate(); 1470 Node* const native_context = LoadNativeContext(context); 1471 Node* const regexp_fun = 1472 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 1473 Node* const initial_map = 1474 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 1475 Node* const initial_prototype = LoadMapPrototype(initial_map); 1476 1477 Label if_isprototype(this), if_isnotprototype(this); 1478 Branch(WordEqual(receiver, initial_prototype), &if_isprototype, 1479 &if_isnotprototype); 1480 1481 BIND(&if_isprototype); 1482 { 1483 const int counter = v8::Isolate::kRegExpPrototypeSourceGetter; 1484 Node* const counter_smi = SmiConstant(counter); 1485 CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); 1486 1487 Node* const result = 1488 HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)")); 1489 Return(result); 1490 } 1491 1492 BIND(&if_isnotprototype); 1493 { 1494 ThrowTypeError(context, MessageTemplate::kRegExpNonRegExp, 1495 "RegExp.prototype.source"); 1496 } 1497 } 1498 } 1499 1500 // Fast-path implementation for flag checks on an unmodified JSRegExp instance. 1501 Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp, 1502 JSRegExp::Flag flag) { 1503 TNode<Smi> const flags = 1504 CAST(LoadObjectField(regexp, JSRegExp::kFlagsOffset)); 1505 TNode<Smi> const mask = SmiConstant(flag); 1506 return SmiToInt32(SmiAnd(flags, mask)); 1507 } 1508 1509 // Load through the GetProperty stub. 1510 Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context, 1511 Node* const regexp, 1512 JSRegExp::Flag flag) { 1513 Factory* factory = isolate()->factory(); 1514 1515 Label out(this); 1516 VARIABLE(var_result, MachineRepresentation::kWord32); 1517 1518 Handle<String> name; 1519 switch (flag) { 1520 case JSRegExp::kGlobal: 1521 name = factory->global_string(); 1522 break; 1523 case JSRegExp::kIgnoreCase: 1524 name = factory->ignoreCase_string(); 1525 break; 1526 case JSRegExp::kMultiline: 1527 name = factory->multiline_string(); 1528 break; 1529 case JSRegExp::kDotAll: 1530 UNREACHABLE(); // Never called for dotAll. 1531 break; 1532 case JSRegExp::kSticky: 1533 name = factory->sticky_string(); 1534 break; 1535 case JSRegExp::kUnicode: 1536 name = factory->unicode_string(); 1537 break; 1538 default: 1539 UNREACHABLE(); 1540 } 1541 1542 Node* const value = GetProperty(context, regexp, name); 1543 1544 Label if_true(this), if_false(this); 1545 BranchIfToBooleanIsTrue(value, &if_true, &if_false); 1546 1547 BIND(&if_true); 1548 { 1549 var_result.Bind(Int32Constant(1)); 1550 Goto(&out); 1551 } 1552 1553 BIND(&if_false); 1554 { 1555 var_result.Bind(Int32Constant(0)); 1556 Goto(&out); 1557 } 1558 1559 BIND(&out); 1560 return var_result.value(); 1561 } 1562 1563 Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context, 1564 Node* const regexp, 1565 JSRegExp::Flag flag, 1566 bool is_fastpath) { 1567 return is_fastpath ? FastFlagGetter(regexp, flag) 1568 : SlowFlagGetter(context, regexp, flag); 1569 } 1570 1571 void RegExpBuiltinsAssembler::FlagGetter(Node* context, Node* receiver, 1572 JSRegExp::Flag flag, int counter, 1573 const char* method_name) { 1574 // Check whether we have an unmodified regexp instance. 1575 Label if_isunmodifiedjsregexp(this), 1576 if_isnotunmodifiedjsregexp(this, Label::kDeferred); 1577 1578 GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp); 1579 Branch(IsJSRegExp(receiver), &if_isunmodifiedjsregexp, 1580 &if_isnotunmodifiedjsregexp); 1581 1582 BIND(&if_isunmodifiedjsregexp); 1583 { 1584 // Refer to JSRegExp's flag property on the fast-path. 1585 Node* const is_flag_set = FastFlagGetter(receiver, flag); 1586 Return(SelectBooleanConstant(is_flag_set)); 1587 } 1588 1589 BIND(&if_isnotunmodifiedjsregexp); 1590 { 1591 Node* const native_context = LoadNativeContext(context); 1592 Node* const regexp_fun = 1593 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 1594 Node* const initial_map = 1595 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 1596 Node* const initial_prototype = LoadMapPrototype(initial_map); 1597 1598 Label if_isprototype(this), if_isnotprototype(this); 1599 Branch(WordEqual(receiver, initial_prototype), &if_isprototype, 1600 &if_isnotprototype); 1601 1602 BIND(&if_isprototype); 1603 { 1604 if (counter != -1) { 1605 Node* const counter_smi = SmiConstant(counter); 1606 CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); 1607 } 1608 Return(UndefinedConstant()); 1609 } 1610 1611 BIND(&if_isnotprototype); 1612 { ThrowTypeError(context, MessageTemplate::kRegExpNonRegExp, method_name); } 1613 } 1614 } 1615 1616 // ES6 21.2.5.4. 1617 // ES #sec-get-regexp.prototype.global 1618 TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) { 1619 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1620 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1621 FlagGetter(context, receiver, JSRegExp::kGlobal, 1622 v8::Isolate::kRegExpPrototypeOldFlagGetter, 1623 "RegExp.prototype.global"); 1624 } 1625 1626 // ES6 21.2.5.5. 1627 // ES #sec-get-regexp.prototype.ignorecase 1628 TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) { 1629 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1630 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1631 FlagGetter(context, receiver, JSRegExp::kIgnoreCase, 1632 v8::Isolate::kRegExpPrototypeOldFlagGetter, 1633 "RegExp.prototype.ignoreCase"); 1634 } 1635 1636 // ES6 21.2.5.7. 1637 // ES #sec-get-regexp.prototype.multiline 1638 TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) { 1639 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1640 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1641 FlagGetter(context, receiver, JSRegExp::kMultiline, 1642 v8::Isolate::kRegExpPrototypeOldFlagGetter, 1643 "RegExp.prototype.multiline"); 1644 } 1645 1646 // ES #sec-get-regexp.prototype.dotAll 1647 TF_BUILTIN(RegExpPrototypeDotAllGetter, RegExpBuiltinsAssembler) { 1648 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1649 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1650 static const int kNoCounter = -1; 1651 FlagGetter(context, receiver, JSRegExp::kDotAll, kNoCounter, 1652 "RegExp.prototype.dotAll"); 1653 } 1654 1655 // ES6 21.2.5.12. 1656 // ES #sec-get-regexp.prototype.sticky 1657 TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) { 1658 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1659 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1660 FlagGetter(context, receiver, JSRegExp::kSticky, 1661 v8::Isolate::kRegExpPrototypeStickyGetter, 1662 "RegExp.prototype.sticky"); 1663 } 1664 1665 // ES6 21.2.5.15. 1666 // ES #sec-get-regexp.prototype.unicode 1667 TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { 1668 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1669 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 1670 FlagGetter(context, receiver, JSRegExp::kUnicode, 1671 v8::Isolate::kRegExpPrototypeUnicodeGetter, 1672 "RegExp.prototype.unicode"); 1673 } 1674 1675 // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) 1676 Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, 1677 Node* string) { 1678 VARIABLE(var_result, MachineRepresentation::kTagged); 1679 Label out(this); 1680 1681 // Take the slow path of fetching the exec property, calling it, and 1682 // verifying its return value. 1683 1684 // Get the exec property. 1685 Node* const exec = 1686 GetProperty(context, regexp, isolate()->factory()->exec_string()); 1687 1688 // Is {exec} callable? 1689 Label if_iscallable(this), if_isnotcallable(this); 1690 1691 GotoIf(TaggedIsSmi(exec), &if_isnotcallable); 1692 1693 Node* const exec_map = LoadMap(exec); 1694 Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable); 1695 1696 BIND(&if_iscallable); 1697 { 1698 Callable call_callable = CodeFactory::Call(isolate()); 1699 Node* const result = CallJS(call_callable, context, exec, regexp, string); 1700 1701 var_result.Bind(result); 1702 GotoIf(IsNull(result), &out); 1703 1704 ThrowIfNotJSReceiver(context, result, 1705 MessageTemplate::kInvalidRegExpExecResult, ""); 1706 1707 Goto(&out); 1708 } 1709 1710 BIND(&if_isnotcallable); 1711 { 1712 ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, 1713 "RegExp.prototype.exec"); 1714 1715 Node* const result = CallBuiltin(Builtins::kRegExpPrototypeExecSlow, 1716 context, regexp, string); 1717 var_result.Bind(result); 1718 Goto(&out); 1719 } 1720 1721 BIND(&out); 1722 return var_result.value(); 1723 } 1724 1725 // ES#sec-regexp.prototype.test 1726 // RegExp.prototype.test ( S ) 1727 TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) { 1728 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 1729 TNode<Object> maybe_string = CAST(Parameter(Descriptor::kString)); 1730 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1731 1732 // Ensure {maybe_receiver} is a JSReceiver. 1733 ThrowIfNotJSReceiver(context, maybe_receiver, 1734 MessageTemplate::kIncompatibleMethodReceiver, 1735 "RegExp.prototype.test"); 1736 TNode<JSReceiver> receiver = CAST(maybe_receiver); 1737 1738 // Convert {maybe_string} to a String. 1739 TNode<String> string = ToString_Inline(context, maybe_string); 1740 1741 Label fast_path(this), slow_path(this); 1742 BranchIfFastRegExp(context, receiver, &fast_path, &slow_path); 1743 1744 BIND(&fast_path); 1745 { 1746 Label if_didnotmatch(this); 1747 RegExpPrototypeExecBodyWithoutResult(context, receiver, string, 1748 &if_didnotmatch, true); 1749 Return(TrueConstant()); 1750 1751 BIND(&if_didnotmatch); 1752 Return(FalseConstant()); 1753 } 1754 1755 BIND(&slow_path); 1756 { 1757 // Call exec. 1758 TNode<HeapObject> match_indices = 1759 CAST(RegExpExec(context, receiver, string)); 1760 1761 // Return true iff exec matched successfully. 1762 Return(SelectBooleanConstant(IsNotNull(match_indices))); 1763 } 1764 } 1765 1766 TF_BUILTIN(RegExpPrototypeTestFast, RegExpBuiltinsAssembler) { 1767 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kReceiver)); 1768 TNode<String> string = CAST(Parameter(Descriptor::kString)); 1769 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1770 1771 Label if_didnotmatch(this); 1772 CSA_ASSERT(this, IsFastRegExpWithOriginalExec(context, regexp)); 1773 RegExpPrototypeExecBodyWithoutResult(context, regexp, string, &if_didnotmatch, 1774 true); 1775 Return(TrueConstant()); 1776 1777 BIND(&if_didnotmatch); 1778 Return(FalseConstant()); 1779 } 1780 1781 Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string, 1782 Node* const index, 1783 Node* const is_unicode, 1784 bool is_fastpath) { 1785 CSA_ASSERT(this, IsString(string)); 1786 CSA_ASSERT(this, IsNumberNormalized(index)); 1787 if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index)); 1788 1789 // Default to last_index + 1. 1790 Node* const index_plus_one = NumberInc(index); 1791 VARIABLE(var_result, MachineRepresentation::kTagged, index_plus_one); 1792 1793 // Advancing the index has some subtle issues involving the distinction 1794 // between Smis and HeapNumbers. There's three cases: 1795 // * {index} is a Smi, {index_plus_one} is a Smi. The standard case. 1796 // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber. 1797 // In this case we can return the result early, because 1798 // {index_plus_one} > {string}.length. 1799 // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only 1800 // occur when {index} is outside the Smi range since we normalize 1801 // explicitly. Again we can return early. 1802 if (is_fastpath) { 1803 // Must be in Smi range on the fast path. We control the value of {index} 1804 // on all call-sites and can never exceed the length of the string. 1805 STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue); 1806 CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one)); 1807 } 1808 1809 Label if_isunicode(this), out(this); 1810 GotoIfNot(is_unicode, &out); 1811 1812 // Keep this unconditional (even on the fast path) just to be safe. 1813 Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out); 1814 1815 BIND(&if_isunicode); 1816 { 1817 TNode<IntPtrT> const string_length = LoadStringLengthAsWord(string); 1818 TNode<IntPtrT> untagged_plus_one = SmiUntag(index_plus_one); 1819 GotoIfNot(IntPtrLessThan(untagged_plus_one, string_length), &out); 1820 1821 Node* const lead = StringCharCodeAt(string, SmiUntag(index)); 1822 GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)), 1823 Int32Constant(0xD800)), 1824 &out); 1825 1826 Node* const trail = StringCharCodeAt(string, untagged_plus_one); 1827 GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)), 1828 Int32Constant(0xDC00)), 1829 &out); 1830 1831 // At a surrogate pair, return index + 2. 1832 Node* const index_plus_two = NumberInc(index_plus_one); 1833 var_result.Bind(index_plus_two); 1834 1835 Goto(&out); 1836 } 1837 1838 BIND(&out); 1839 return var_result.value(); 1840 } 1841 1842 void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, 1843 Node* const regexp, 1844 TNode<String> string, 1845 const bool is_fastpath) { 1846 if (is_fastpath) CSA_ASSERT(this, IsFastRegExp(context, regexp)); 1847 1848 Node* const is_global = 1849 FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath); 1850 1851 Label if_isglobal(this), if_isnotglobal(this); 1852 Branch(is_global, &if_isglobal, &if_isnotglobal); 1853 1854 BIND(&if_isnotglobal); 1855 { 1856 Node* const result = 1857 is_fastpath 1858 ? RegExpPrototypeExecBody(CAST(context), CAST(regexp), string, true) 1859 : RegExpExec(context, regexp, string); 1860 Return(result); 1861 } 1862 1863 BIND(&if_isglobal); 1864 { 1865 Node* const is_unicode = 1866 FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath); 1867 1868 StoreLastIndex(context, regexp, SmiZero(), is_fastpath); 1869 1870 // Allocate an array to store the resulting match strings. 1871 1872 GrowableFixedArray array(state()); 1873 1874 // Loop preparations. Within the loop, collect results from RegExpExec 1875 // and store match strings in the array. 1876 1877 Variable* vars[] = {array.var_array(), array.var_length(), 1878 array.var_capacity()}; 1879 Label loop(this, 3, vars), out(this); 1880 Goto(&loop); 1881 1882 BIND(&loop); 1883 { 1884 VARIABLE(var_match, MachineRepresentation::kTagged); 1885 1886 Label if_didmatch(this), if_didnotmatch(this); 1887 if (is_fastpath) { 1888 // On the fast path, grab the matching string from the raw match index 1889 // array. 1890 TNode<RegExpMatchInfo> match_indices = 1891 RegExpPrototypeExecBodyWithoutResult(CAST(context), CAST(regexp), 1892 string, &if_didnotmatch, true); 1893 1894 Node* const match_from = LoadFixedArrayElement( 1895 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 1896 Node* const match_to = LoadFixedArrayElement( 1897 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); 1898 1899 var_match.Bind(CallBuiltin(Builtins::kSubString, context, string, 1900 match_from, match_to)); 1901 Goto(&if_didmatch); 1902 } else { 1903 DCHECK(!is_fastpath); 1904 Node* const result = RegExpExec(context, regexp, string); 1905 1906 Label load_match(this); 1907 Branch(IsNull(result), &if_didnotmatch, &load_match); 1908 1909 BIND(&load_match); 1910 var_match.Bind( 1911 ToString_Inline(context, GetProperty(context, result, SmiZero()))); 1912 Goto(&if_didmatch); 1913 } 1914 1915 BIND(&if_didnotmatch); 1916 { 1917 // Return null if there were no matches, otherwise just exit the loop. 1918 GotoIfNot(IntPtrEqual(array.length(), IntPtrZero()), &out); 1919 Return(NullConstant()); 1920 } 1921 1922 BIND(&if_didmatch); 1923 { 1924 Node* match = var_match.value(); 1925 1926 // Store the match, growing the fixed array if needed. 1927 1928 array.Push(CAST(match)); 1929 1930 // Advance last index if the match is the empty string. 1931 1932 TNode<Smi> const match_length = LoadStringLengthAsSmi(match); 1933 GotoIfNot(SmiEqual(match_length, SmiZero()), &loop); 1934 1935 Node* last_index = 1936 LoadLastIndex(CAST(context), CAST(regexp), is_fastpath); 1937 if (is_fastpath) { 1938 CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); 1939 } else { 1940 last_index = ToLength_Inline(context, last_index); 1941 } 1942 1943 Node* const new_last_index = 1944 AdvanceStringIndex(string, last_index, is_unicode, is_fastpath); 1945 1946 if (is_fastpath) { 1947 // On the fast path, we can be certain that lastIndex can never be 1948 // incremented to overflow the Smi range since the maximal string 1949 // length is less than the maximal Smi value. 1950 STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue); 1951 CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index)); 1952 } 1953 1954 StoreLastIndex(context, regexp, new_last_index, is_fastpath); 1955 1956 Goto(&loop); 1957 } 1958 } 1959 1960 BIND(&out); 1961 { 1962 // Wrap the match in a JSArray. 1963 1964 Node* const result = array.ToJSArray(CAST(context)); 1965 Return(result); 1966 } 1967 } 1968 } 1969 1970 // ES#sec-regexp.prototype-@@match 1971 // RegExp.prototype [ @@match ] ( string ) 1972 TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { 1973 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 1974 TNode<Object> maybe_string = CAST(Parameter(Descriptor::kString)); 1975 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 1976 1977 // Ensure {maybe_receiver} is a JSReceiver. 1978 ThrowIfNotJSReceiver(context, maybe_receiver, 1979 MessageTemplate::kIncompatibleMethodReceiver, 1980 "RegExp.prototype.@@match"); 1981 Node* const receiver = maybe_receiver; 1982 1983 // Convert {maybe_string} to a String. 1984 TNode<String> const string = ToString_Inline(context, maybe_string); 1985 1986 Label fast_path(this), slow_path(this); 1987 BranchIfFastRegExp(context, receiver, &fast_path, &slow_path); 1988 1989 BIND(&fast_path); 1990 // TODO(pwong): Could be optimized to remove the overhead of calling the 1991 // builtin (at the cost of a larger builtin). 1992 Return(CallBuiltin(Builtins::kRegExpMatchFast, context, receiver, string)); 1993 1994 BIND(&slow_path); 1995 RegExpPrototypeMatchBody(context, receiver, string, false); 1996 } 1997 1998 TNode<Object> RegExpBuiltinsAssembler::MatchAllIterator( 1999 TNode<Context> context, TNode<Context> native_context, 2000 TNode<Object> maybe_regexp, TNode<String> string, 2001 TNode<BoolT> is_fast_regexp, char const* method_name) { 2002 Label create_iterator(this), if_fast_regexp(this), 2003 if_slow_regexp(this, Label::kDeferred), if_not_regexp(this); 2004 2005 // 1. Let S be ? ToString(O). 2006 // Handled by the caller of MatchAllIterator. 2007 CSA_ASSERT(this, IsString(string)); 2008 2009 TVARIABLE(Object, var_matcher); 2010 TVARIABLE(Int32T, var_global); 2011 TVARIABLE(Int32T, var_unicode); 2012 2013 // 2. If ? IsRegExp(R) is true, then 2014 GotoIf(is_fast_regexp, &if_fast_regexp); 2015 Branch(IsRegExp(context, maybe_regexp), &if_slow_regexp, &if_not_regexp); 2016 BIND(&if_fast_regexp); 2017 { 2018 CSA_ASSERT(this, IsFastRegExp(context, maybe_regexp)); 2019 TNode<JSRegExp> fast_regexp = CAST(maybe_regexp); 2020 TNode<Object> source = 2021 LoadObjectField(fast_regexp, JSRegExp::kSourceOffset); 2022 TNode<String> flags = CAST(FlagsGetter(context, fast_regexp, true)); 2023 2024 // c. Let matcher be ? Construct(C, R, flags ). 2025 var_matcher = RegExpCreate(context, native_context, source, flags); 2026 CSA_ASSERT(this, IsFastRegExp(context, var_matcher.value())); 2027 2028 // d. Let global be ? ToBoolean(? Get(matcher, "global")). 2029 var_global = UncheckedCast<Int32T>( 2030 FastFlagGetter(var_matcher.value(), JSRegExp::kGlobal)); 2031 2032 // e. Let fullUnicode be ? ToBoolean(? Get(matcher, "unicode"). 2033 var_unicode = UncheckedCast<Int32T>( 2034 FastFlagGetter(var_matcher.value(), JSRegExp::kUnicode)); 2035 2036 // f. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). 2037 // g. Perform ? Set(matcher, "lastIndex", lastIndex, true). 2038 FastStoreLastIndex(var_matcher.value(), FastLoadLastIndex(fast_regexp)); 2039 Goto(&create_iterator); 2040 } 2041 BIND(&if_slow_regexp); 2042 { 2043 // a. Let C be ? SpeciesConstructor(R, %RegExp%). 2044 TNode<Object> regexp_fun = 2045 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 2046 TNode<Object> species_constructor = 2047 SpeciesConstructor(native_context, maybe_regexp, regexp_fun); 2048 2049 // b. Let flags be ? ToString(? Get(R, "flags")). 2050 TNode<Object> flags = GetProperty(context, maybe_regexp, 2051 isolate()->factory()->flags_string()); 2052 TNode<String> flags_string = ToString_Inline(context, flags); 2053 2054 // c. Let matcher be ? Construct(C, R, flags ). 2055 var_matcher = 2056 CAST(ConstructJS(CodeFactory::Construct(isolate()), context, 2057 species_constructor, maybe_regexp, flags_string)); 2058 2059 // d. Let global be ? ToBoolean(? Get(matcher, "global")). 2060 var_global = UncheckedCast<Int32T>( 2061 SlowFlagGetter(context, var_matcher.value(), JSRegExp::kGlobal)); 2062 2063 // e. Let fullUnicode be ? ToBoolean(? Get(matcher, "unicode"). 2064 var_unicode = UncheckedCast<Int32T>( 2065 SlowFlagGetter(context, var_matcher.value(), JSRegExp::kUnicode)); 2066 2067 // f. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). 2068 TNode<Number> last_index = UncheckedCast<Number>( 2069 ToLength_Inline(context, SlowLoadLastIndex(context, maybe_regexp))); 2070 2071 // g. Perform ? Set(matcher, "lastIndex", lastIndex, true). 2072 SlowStoreLastIndex(context, var_matcher.value(), last_index); 2073 2074 Goto(&create_iterator); 2075 } 2076 // 3. Else, 2077 BIND(&if_not_regexp); 2078 { 2079 // a. Let flags be "g". 2080 // b. Let matcher be ? RegExpCreate(R, flags). 2081 var_matcher = RegExpCreate(context, native_context, maybe_regexp, 2082 StringConstant("g")); 2083 2084 // c. Let global be true. 2085 var_global = Int32Constant(1); 2086 2087 // d. Let fullUnicode be false. 2088 var_unicode = Int32Constant(0); 2089 2090 #ifdef DEBUG 2091 // Assert: ! Get(matcher, "lastIndex") is 0. 2092 TNode<Object> last_index = SlowLoadLastIndex(context, var_matcher.value()); 2093 CSA_ASSERT(this, WordEqual(SmiZero(), last_index)); 2094 #endif // DEBUG 2095 2096 Goto(&create_iterator); 2097 } 2098 // 4. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode). 2099 BIND(&create_iterator); 2100 { 2101 TNode<Map> map = CAST(LoadContextElement( 2102 native_context, 2103 Context::INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX)); 2104 2105 // 4. Let iterator be ObjectCreate(%RegExpStringIteratorPrototype%, 2106 // [[IteratingRegExp]], [[IteratedString]], [[Global]], [[Unicode]], 2107 // [[Done]] ). 2108 TNode<Object> iterator = CAST(Allocate(JSRegExpStringIterator::kSize)); 2109 StoreMapNoWriteBarrier(iterator, map); 2110 StoreObjectFieldRoot(iterator, 2111 JSRegExpStringIterator::kPropertiesOrHashOffset, 2112 Heap::kEmptyFixedArrayRootIndex); 2113 StoreObjectFieldRoot(iterator, JSRegExpStringIterator::kElementsOffset, 2114 Heap::kEmptyFixedArrayRootIndex); 2115 2116 // 5. Set iterator.[[IteratingRegExp]] to R. 2117 StoreObjectFieldNoWriteBarrier( 2118 iterator, JSRegExpStringIterator::kIteratingRegExpOffset, 2119 var_matcher.value()); 2120 2121 // 6. Set iterator.[[IteratedString]] to S. 2122 StoreObjectFieldNoWriteBarrier( 2123 iterator, JSRegExpStringIterator::kIteratedStringOffset, string); 2124 2125 #ifdef DEBUG 2126 // Verify global and unicode can be bitwise shifted without masking. 2127 TNode<Int32T> zero = Int32Constant(0); 2128 TNode<Int32T> one = Int32Constant(1); 2129 CSA_ASSERT(this, Word32Or(Word32Equal(var_global.value(), zero), 2130 Word32Equal(var_global.value(), one))); 2131 CSA_ASSERT(this, Word32Or(Word32Equal(var_unicode.value(), zero), 2132 Word32Equal(var_unicode.value(), one))); 2133 #endif // DEBUG 2134 2135 // 7. Set iterator.[[Global]] to global. 2136 // 8. Set iterator.[[Unicode]] to fullUnicode. 2137 // 9. Set iterator.[[Done]] to false. 2138 TNode<Word32T> global_flag = Word32Shl( 2139 var_global.value(), Int32Constant(JSRegExpStringIterator::kGlobalBit)); 2140 TNode<Word32T> unicode_flag = 2141 Word32Shl(var_unicode.value(), 2142 Int32Constant(JSRegExpStringIterator::kUnicodeBit)); 2143 TNode<Word32T> iterator_flags = Word32Or(global_flag, unicode_flag); 2144 StoreObjectFieldNoWriteBarrier(iterator, 2145 JSRegExpStringIterator::kFlagsOffset, 2146 SmiFromInt32(Signed(iterator_flags))); 2147 2148 return iterator; 2149 } 2150 } 2151 2152 // https://tc39.github.io/proposal-string-matchall/ 2153 // RegExp.prototype [ @@matchAll ] ( string ) 2154 TF_BUILTIN(RegExpPrototypeMatchAll, RegExpBuiltinsAssembler) { 2155 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2156 TNode<Context> native_context = LoadNativeContext(context); 2157 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 2158 TNode<Object> string = CAST(Parameter(Descriptor::kString)); 2159 2160 // 1. Let R be the this value. 2161 // 2. If Type(R) is not Object, throw a TypeError exception. 2162 ThrowIfNotJSReceiver(context, receiver, 2163 MessageTemplate::kIncompatibleMethodReceiver, 2164 "RegExp.prototype.@@matchAll"); 2165 2166 // 3. Return ? MatchAllIterator(R, string). 2167 Return(MatchAllIterator( 2168 context, native_context, receiver, ToString_Inline(context, string), 2169 IsFastRegExp(context, receiver), "RegExp.prototype.@@matchAll")); 2170 } 2171 2172 // Helper that skips a few initial checks. and assumes... 2173 // 1) receiver is a "fast" RegExp 2174 // 2) pattern is a string 2175 TF_BUILTIN(RegExpMatchFast, RegExpBuiltinsAssembler) { 2176 TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); 2177 TNode<String> string = CAST(Parameter(Descriptor::kPattern)); 2178 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2179 2180 RegExpPrototypeMatchBody(context, receiver, string, true); 2181 } 2182 2183 void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast( 2184 Node* const context, Node* const regexp, Node* const string) { 2185 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2186 CSA_ASSERT(this, IsString(string)); 2187 2188 // Grab the initial value of last index. 2189 Node* const previous_last_index = FastLoadLastIndex(CAST(regexp)); 2190 2191 // Ensure last index is 0. 2192 FastStoreLastIndex(regexp, SmiZero()); 2193 2194 // Call exec. 2195 Label if_didnotmatch(this); 2196 TNode<RegExpMatchInfo> match_indices = RegExpPrototypeExecBodyWithoutResult( 2197 CAST(context), CAST(regexp), CAST(string), &if_didnotmatch, true); 2198 2199 // Successful match. 2200 { 2201 // Reset last index. 2202 FastStoreLastIndex(regexp, previous_last_index); 2203 2204 // Return the index of the match. 2205 Node* const index = LoadFixedArrayElement( 2206 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 2207 Return(index); 2208 } 2209 2210 BIND(&if_didnotmatch); 2211 { 2212 // Reset last index and return -1. 2213 FastStoreLastIndex(regexp, previous_last_index); 2214 Return(SmiConstant(-1)); 2215 } 2216 } 2217 2218 void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( 2219 Node* const context, Node* const regexp, Node* const string) { 2220 CSA_ASSERT(this, IsJSReceiver(regexp)); 2221 CSA_ASSERT(this, IsString(string)); 2222 2223 Isolate* const isolate = this->isolate(); 2224 2225 Node* const smi_zero = SmiZero(); 2226 2227 // Grab the initial value of last index. 2228 Node* const previous_last_index = 2229 SlowLoadLastIndex(CAST(context), CAST(regexp)); 2230 2231 // Ensure last index is 0. 2232 { 2233 Label next(this), slow(this, Label::kDeferred); 2234 BranchIfSameValue(previous_last_index, smi_zero, &next, &slow); 2235 2236 BIND(&slow); 2237 SlowStoreLastIndex(context, regexp, smi_zero); 2238 Goto(&next); 2239 BIND(&next); 2240 } 2241 2242 // Call exec. 2243 Node* const exec_result = RegExpExec(context, regexp, string); 2244 2245 // Reset last index if necessary. 2246 { 2247 Label next(this), slow(this, Label::kDeferred); 2248 Node* const current_last_index = 2249 SlowLoadLastIndex(CAST(context), CAST(regexp)); 2250 2251 BranchIfSameValue(current_last_index, previous_last_index, &next, &slow); 2252 2253 BIND(&slow); 2254 SlowStoreLastIndex(context, regexp, previous_last_index); 2255 Goto(&next); 2256 BIND(&next); 2257 } 2258 2259 // Return -1 if no match was found. 2260 { 2261 Label next(this); 2262 GotoIfNot(IsNull(exec_result), &next); 2263 Return(SmiConstant(-1)); 2264 BIND(&next); 2265 } 2266 2267 // Return the index of the match. 2268 { 2269 Label fast_result(this), slow_result(this, Label::kDeferred); 2270 BranchIfFastRegExpResult(context, exec_result, &fast_result, &slow_result); 2271 2272 BIND(&fast_result); 2273 { 2274 Node* const index = 2275 LoadObjectField(exec_result, JSRegExpResult::kIndexOffset); 2276 Return(index); 2277 } 2278 2279 BIND(&slow_result); 2280 { 2281 Return(GetProperty(context, exec_result, 2282 isolate->factory()->index_string())); 2283 } 2284 } 2285 } 2286 2287 // ES#sec-regexp.prototype-@@search 2288 // RegExp.prototype [ @@search ] ( string ) 2289 TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) { 2290 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 2291 TNode<Object> maybe_string = CAST(Parameter(Descriptor::kString)); 2292 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2293 2294 // Ensure {maybe_receiver} is a JSReceiver. 2295 ThrowIfNotJSReceiver(context, maybe_receiver, 2296 MessageTemplate::kIncompatibleMethodReceiver, 2297 "RegExp.prototype.@@search"); 2298 Node* const receiver = maybe_receiver; 2299 2300 // Convert {maybe_string} to a String. 2301 TNode<String> const string = ToString_Inline(context, maybe_string); 2302 2303 Label fast_path(this), slow_path(this); 2304 BranchIfFastRegExp(context, receiver, &fast_path, &slow_path); 2305 2306 BIND(&fast_path); 2307 // TODO(pwong): Could be optimized to remove the overhead of calling the 2308 // builtin (at the cost of a larger builtin). 2309 Return(CallBuiltin(Builtins::kRegExpSearchFast, context, receiver, string)); 2310 2311 BIND(&slow_path); 2312 RegExpPrototypeSearchBodySlow(context, receiver, string); 2313 } 2314 2315 // Helper that skips a few initial checks. and assumes... 2316 // 1) receiver is a "fast" RegExp 2317 // 2) pattern is a string 2318 TF_BUILTIN(RegExpSearchFast, RegExpBuiltinsAssembler) { 2319 TNode<JSRegExp> receiver = CAST(Parameter(Descriptor::kReceiver)); 2320 TNode<String> string = CAST(Parameter(Descriptor::kPattern)); 2321 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2322 2323 RegExpPrototypeSearchBodyFast(context, receiver, string); 2324 } 2325 2326 // Generates the fast path for @@split. {regexp} is an unmodified, non-sticky 2327 // JSRegExp, {string} is a String, and {limit} is a Smi. 2328 void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context, 2329 Node* const regexp, 2330 TNode<String> string, 2331 TNode<Smi> const limit) { 2332 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2333 CSA_ASSERT(this, Word32BinaryNot(FastFlagGetter(regexp, JSRegExp::kSticky))); 2334 2335 TNode<IntPtrT> const int_limit = SmiUntag(limit); 2336 2337 const ElementsKind kind = PACKED_ELEMENTS; 2338 const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; 2339 2340 Node* const allocation_site = nullptr; 2341 Node* const native_context = LoadNativeContext(context); 2342 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 2343 2344 Label return_empty_array(this, Label::kDeferred); 2345 2346 // If limit is zero, return an empty array. 2347 { 2348 Label next(this), if_limitiszero(this, Label::kDeferred); 2349 Branch(SmiEqual(limit, SmiZero()), &return_empty_array, &next); 2350 BIND(&next); 2351 } 2352 2353 TNode<Smi> const string_length = LoadStringLengthAsSmi(string); 2354 2355 // If passed the empty {string}, return either an empty array or a singleton 2356 // array depending on whether the {regexp} matches. 2357 { 2358 Label next(this), if_stringisempty(this, Label::kDeferred); 2359 Branch(SmiEqual(string_length, SmiZero()), &if_stringisempty, &next); 2360 2361 BIND(&if_stringisempty); 2362 { 2363 Node* const last_match_info = LoadContextElement( 2364 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 2365 2366 Node* const match_indices = 2367 CallBuiltin(Builtins::kRegExpExecInternal, context, regexp, string, 2368 SmiZero(), last_match_info); 2369 2370 Label return_singleton_array(this); 2371 Branch(IsNull(match_indices), &return_singleton_array, 2372 &return_empty_array); 2373 2374 BIND(&return_singleton_array); 2375 { 2376 Node* const length = SmiConstant(1); 2377 Node* const capacity = IntPtrConstant(1); 2378 Node* const result = AllocateJSArray(kind, array_map, capacity, length, 2379 allocation_site, mode); 2380 2381 TNode<FixedArray> const fixed_array = CAST(LoadElements(result)); 2382 StoreFixedArrayElement(fixed_array, 0, string); 2383 2384 Return(result); 2385 } 2386 } 2387 2388 BIND(&next); 2389 } 2390 2391 // Loop preparations. 2392 2393 GrowableFixedArray array(state()); 2394 2395 TVARIABLE(Smi, var_last_matched_until, SmiZero()); 2396 TVARIABLE(Smi, var_next_search_from, SmiZero()); 2397 2398 Variable* vars[] = {array.var_array(), array.var_length(), 2399 array.var_capacity(), &var_last_matched_until, 2400 &var_next_search_from}; 2401 const int vars_count = sizeof(vars) / sizeof(vars[0]); 2402 Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this); 2403 Goto(&loop); 2404 2405 BIND(&loop); 2406 { 2407 TNode<Smi> const next_search_from = var_next_search_from.value(); 2408 TNode<Smi> const last_matched_until = var_last_matched_until.value(); 2409 2410 // We're done if we've reached the end of the string. 2411 { 2412 Label next(this); 2413 Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out, 2414 &next); 2415 BIND(&next); 2416 } 2417 2418 // Search for the given {regexp}. 2419 2420 Node* const last_match_info = LoadContextElement( 2421 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 2422 2423 TNode<HeapObject> const match_indices_ho = 2424 CAST(CallBuiltin(Builtins::kRegExpExecInternal, context, regexp, string, 2425 next_search_from, last_match_info)); 2426 2427 // We're done if no match was found. 2428 { 2429 Label next(this); 2430 Branch(IsNull(match_indices_ho), &push_suffix_and_out, &next); 2431 BIND(&next); 2432 } 2433 2434 TNode<FixedArray> match_indices = CAST(match_indices_ho); 2435 TNode<Smi> const match_from = CAST(LoadFixedArrayElement( 2436 match_indices, RegExpMatchInfo::kFirstCaptureIndex)); 2437 2438 // We're done if the match starts beyond the string. 2439 { 2440 Label next(this); 2441 Branch(SmiEqual(match_from, string_length), &push_suffix_and_out, &next); 2442 BIND(&next); 2443 } 2444 2445 TNode<Smi> const match_to = CAST(LoadFixedArrayElement( 2446 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1)); 2447 2448 // Advance index and continue if the match is empty. 2449 { 2450 Label next(this); 2451 2452 GotoIfNot(SmiEqual(match_to, next_search_from), &next); 2453 GotoIfNot(SmiEqual(match_to, last_matched_until), &next); 2454 2455 Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode); 2456 Node* const new_next_search_from = 2457 AdvanceStringIndex(string, next_search_from, is_unicode, true); 2458 var_next_search_from = CAST(new_next_search_from); 2459 Goto(&loop); 2460 2461 BIND(&next); 2462 } 2463 2464 // A valid match was found, add the new substring to the array. 2465 { 2466 TNode<Smi> const from = last_matched_until; 2467 TNode<Smi> const to = match_from; 2468 array.Push(CallBuiltin(Builtins::kSubString, context, string, from, to)); 2469 GotoIf(WordEqual(array.length(), int_limit), &out); 2470 } 2471 2472 // Add all captures to the array. 2473 { 2474 Node* const num_registers = LoadFixedArrayElement( 2475 match_indices, RegExpMatchInfo::kNumberOfCapturesIndex); 2476 Node* const int_num_registers = SmiUntag(num_registers); 2477 2478 VARIABLE(var_reg, MachineType::PointerRepresentation()); 2479 var_reg.Bind(IntPtrConstant(2)); 2480 2481 Variable* vars[] = {array.var_array(), array.var_length(), 2482 array.var_capacity(), &var_reg}; 2483 const int vars_count = sizeof(vars) / sizeof(vars[0]); 2484 Label nested_loop(this, vars_count, vars), nested_loop_out(this); 2485 Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop, 2486 &nested_loop_out); 2487 2488 BIND(&nested_loop); 2489 { 2490 Node* const reg = var_reg.value(); 2491 Node* const from = LoadFixedArrayElement( 2492 match_indices, reg, 2493 RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode); 2494 TNode<Smi> const to = CAST(LoadFixedArrayElement( 2495 match_indices, reg, 2496 (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode)); 2497 2498 Label select_capture(this), select_undefined(this), store_value(this); 2499 VARIABLE(var_value, MachineRepresentation::kTagged); 2500 Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined, 2501 &select_capture); 2502 2503 BIND(&select_capture); 2504 { 2505 var_value.Bind( 2506 CallBuiltin(Builtins::kSubString, context, string, from, to)); 2507 Goto(&store_value); 2508 } 2509 2510 BIND(&select_undefined); 2511 { 2512 var_value.Bind(UndefinedConstant()); 2513 Goto(&store_value); 2514 } 2515 2516 BIND(&store_value); 2517 { 2518 array.Push(CAST(var_value.value())); 2519 GotoIf(WordEqual(array.length(), int_limit), &out); 2520 2521 Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2)); 2522 var_reg.Bind(new_reg); 2523 2524 Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop, 2525 &nested_loop_out); 2526 } 2527 } 2528 2529 BIND(&nested_loop_out); 2530 } 2531 2532 var_last_matched_until = match_to; 2533 var_next_search_from = match_to; 2534 Goto(&loop); 2535 } 2536 2537 BIND(&push_suffix_and_out); 2538 { 2539 Node* const from = var_last_matched_until.value(); 2540 Node* const to = string_length; 2541 array.Push(CallBuiltin(Builtins::kSubString, context, string, from, to)); 2542 Goto(&out); 2543 } 2544 2545 BIND(&out); 2546 { 2547 Node* const result = array.ToJSArray(CAST(context)); 2548 Return(result); 2549 } 2550 2551 BIND(&return_empty_array); 2552 { 2553 Node* const length = SmiZero(); 2554 Node* const capacity = IntPtrZero(); 2555 Node* const result = AllocateJSArray(kind, array_map, capacity, length, 2556 allocation_site, mode); 2557 Return(result); 2558 } 2559 } 2560 2561 // Helper that skips a few initial checks. 2562 TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) { 2563 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kRegExp)); 2564 TNode<String> string = CAST(Parameter(Descriptor::kString)); 2565 TNode<Object> maybe_limit = CAST(Parameter(Descriptor::kLimit)); 2566 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2567 2568 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2569 2570 // TODO(jgruber): Even if map checks send us to the fast path, we still need 2571 // to verify the constructor property and jump to the slow path if it has 2572 // been changed. 2573 2574 // Verify {maybe_limit}. 2575 2576 VARIABLE(var_limit, MachineRepresentation::kTagged, maybe_limit); 2577 Label if_limitissmimax(this), runtime(this, Label::kDeferred); 2578 2579 { 2580 Label next(this); 2581 2582 GotoIf(IsUndefined(maybe_limit), &if_limitissmimax); 2583 Branch(TaggedIsPositiveSmi(maybe_limit), &next, &runtime); 2584 2585 // We need to be extra-strict and require the given limit to be either 2586 // undefined or a positive smi. We can't call ToUint32(maybe_limit) since 2587 // that might move us onto the slow path, resulting in ordering spec 2588 // violations (see https://crbug.com/801171). 2589 2590 BIND(&if_limitissmimax); 2591 { 2592 // TODO(jgruber): In this case, we can probably avoid generation of limit 2593 // checks in Generate_RegExpPrototypeSplitBody. 2594 var_limit.Bind(SmiConstant(Smi::kMaxValue)); 2595 Goto(&next); 2596 } 2597 2598 BIND(&next); 2599 } 2600 2601 // Due to specific shortcuts we take on the fast path (specifically, we don't 2602 // allocate a new regexp instance as specced), we need to ensure that the 2603 // given regexp is non-sticky to avoid invalid results. See crbug.com/v8/6706. 2604 2605 GotoIf(FastFlagGetter(regexp, JSRegExp::kSticky), &runtime); 2606 2607 // We're good to go on the fast path, which is inlined here. 2608 2609 RegExpPrototypeSplitBody(context, regexp, string, CAST(var_limit.value())); 2610 2611 BIND(&runtime); 2612 Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, 2613 var_limit.value())); 2614 } 2615 2616 // ES#sec-regexp.prototype-@@split 2617 // RegExp.prototype [ @@split ] ( string, limit ) 2618 TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { 2619 const int kStringArg = 0; 2620 const int kLimitArg = 1; 2621 2622 TNode<IntPtrT> argc = 2623 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 2624 CodeStubArguments args(this, argc); 2625 2626 TNode<Object> maybe_receiver = args.GetReceiver(); 2627 TNode<Object> maybe_string = args.GetOptionalArgumentValue(kStringArg); 2628 TNode<Object> maybe_limit = args.GetOptionalArgumentValue(kLimitArg); 2629 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2630 2631 // Ensure {maybe_receiver} is a JSReceiver. 2632 ThrowIfNotJSReceiver(context, maybe_receiver, 2633 MessageTemplate::kIncompatibleMethodReceiver, 2634 "RegExp.prototype.@@split"); 2635 Node* const receiver = maybe_receiver; 2636 2637 // Convert {maybe_string} to a String. 2638 TNode<String> const string = ToString_Inline(context, maybe_string); 2639 2640 Label stub(this), runtime(this, Label::kDeferred); 2641 BranchIfFastRegExp(context, receiver, &stub, &runtime); 2642 2643 BIND(&stub); 2644 args.PopAndReturn(CallBuiltin(Builtins::kRegExpSplit, context, receiver, 2645 string, maybe_limit)); 2646 2647 BIND(&runtime); 2648 args.PopAndReturn(CallRuntime(Runtime::kRegExpSplit, context, receiver, 2649 string, maybe_limit)); 2650 } 2651 2652 Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath( 2653 Node* context, Node* regexp, Node* string, Node* replace_callable) { 2654 // The fast path is reached only if {receiver} is a global unmodified 2655 // JSRegExp instance and {replace_callable} is callable. 2656 2657 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2658 CSA_ASSERT(this, IsCallable(replace_callable)); 2659 CSA_ASSERT(this, IsString(string)); 2660 2661 Isolate* const isolate = this->isolate(); 2662 2663 Node* const undefined = UndefinedConstant(); 2664 TNode<IntPtrT> int_one = IntPtrConstant(1); 2665 2666 Node* const native_context = LoadNativeContext(context); 2667 2668 Label out(this); 2669 VARIABLE(var_result, MachineRepresentation::kTagged); 2670 2671 // Set last index to 0. 2672 FastStoreLastIndex(regexp, SmiZero()); 2673 2674 // Allocate {result_array}. 2675 Node* result_array; 2676 { 2677 ElementsKind kind = PACKED_ELEMENTS; 2678 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 2679 TNode<IntPtrT> capacity = IntPtrConstant(16); 2680 TNode<Smi> length = SmiZero(); 2681 Node* const allocation_site = nullptr; 2682 ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS; 2683 2684 result_array = AllocateJSArray(kind, array_map, capacity, length, 2685 allocation_site, capacity_mode); 2686 } 2687 2688 // Call into runtime for RegExpExecMultiple. 2689 TNode<FixedArray> last_match_info = CAST(LoadContextElement( 2690 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX)); 2691 Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp, 2692 string, last_match_info, result_array); 2693 2694 // Reset last index to 0. 2695 FastStoreLastIndex(regexp, SmiZero()); 2696 2697 // If no matches, return the subject string. 2698 var_result.Bind(string); 2699 GotoIf(IsNull(res), &out); 2700 2701 // Reload last match info since it might have changed. 2702 last_match_info = CAST(LoadContextElement( 2703 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX)); 2704 2705 Node* const res_length = LoadJSArrayLength(res); 2706 TNode<FixedArray> const res_elems = CAST(LoadElements(res)); 2707 2708 TNode<Smi> const num_capture_registers = CAST(LoadFixedArrayElement( 2709 last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex)); 2710 2711 Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this), 2712 create_result(this); 2713 Branch(SmiEqual(num_capture_registers, SmiConstant(2)), 2714 &if_noexplicitcaptures, &if_hasexplicitcaptures); 2715 2716 BIND(&if_noexplicitcaptures); 2717 { 2718 // If the number of captures is two then there are no explicit captures in 2719 // the regexp, just the implicit capture that captures the whole match. In 2720 // this case we can simplify quite a bit and end up with something faster. 2721 // The builder will consist of some integers that indicate slices of the 2722 // input string and some replacements that were returned from the replace 2723 // function. 2724 2725 TVARIABLE(Smi, var_match_start, SmiZero()); 2726 2727 TNode<IntPtrT> const end = SmiUntag(res_length); 2728 TVARIABLE(IntPtrT, var_i, IntPtrZero()); 2729 2730 Variable* vars[] = {&var_i, &var_match_start}; 2731 Label loop(this, 2, vars); 2732 Goto(&loop); 2733 BIND(&loop); 2734 { 2735 GotoIfNot(IntPtrLessThan(var_i.value(), end), &create_result); 2736 2737 Node* const elem = LoadFixedArrayElement(res_elems, var_i.value()); 2738 2739 Label if_issmi(this), if_isstring(this), loop_epilogue(this); 2740 Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring); 2741 2742 BIND(&if_issmi); 2743 { 2744 TNode<Smi> smi_elem = CAST(elem); 2745 // Integers represent slices of the original string. 2746 Label if_isnegativeorzero(this), if_ispositive(this); 2747 BranchIfSmiLessThanOrEqual(smi_elem, SmiZero(), &if_isnegativeorzero, 2748 &if_ispositive); 2749 2750 BIND(&if_ispositive); 2751 { 2752 TNode<IntPtrT> int_elem = SmiUntag(smi_elem); 2753 TNode<IntPtrT> new_match_start = 2754 Signed(IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)), 2755 WordAnd(int_elem, IntPtrConstant(0x7FF)))); 2756 var_match_start = SmiTag(new_match_start); 2757 Goto(&loop_epilogue); 2758 } 2759 2760 BIND(&if_isnegativeorzero); 2761 { 2762 var_i = IntPtrAdd(var_i.value(), int_one); 2763 2764 TNode<Smi> const next_elem = 2765 CAST(LoadFixedArrayElement(res_elems, var_i.value())); 2766 2767 var_match_start = SmiSub(next_elem, smi_elem); 2768 Goto(&loop_epilogue); 2769 } 2770 } 2771 2772 BIND(&if_isstring); 2773 { 2774 CSA_ASSERT(this, IsString(elem)); 2775 2776 Callable call_callable = CodeFactory::Call(isolate); 2777 TNode<Smi> match_start = var_match_start.value(); 2778 Node* const replacement_obj = 2779 CallJS(call_callable, context, replace_callable, undefined, elem, 2780 match_start, string); 2781 2782 TNode<String> const replacement_str = 2783 ToString_Inline(context, replacement_obj); 2784 StoreFixedArrayElement(res_elems, var_i.value(), replacement_str); 2785 2786 TNode<Smi> const elem_length = LoadStringLengthAsSmi(elem); 2787 var_match_start = SmiAdd(match_start, elem_length); 2788 2789 Goto(&loop_epilogue); 2790 } 2791 2792 BIND(&loop_epilogue); 2793 { 2794 var_i = IntPtrAdd(var_i.value(), int_one); 2795 Goto(&loop); 2796 } 2797 } 2798 } 2799 2800 BIND(&if_hasexplicitcaptures); 2801 { 2802 Node* const from = IntPtrZero(); 2803 Node* const to = SmiUntag(res_length); 2804 const int increment = 1; 2805 2806 BuildFastLoop(from, to, 2807 [this, res_elems, isolate, native_context, context, undefined, 2808 replace_callable](Node* index) { 2809 Node* const elem = LoadFixedArrayElement(res_elems, index); 2810 2811 Label do_continue(this); 2812 GotoIf(TaggedIsSmi(elem), &do_continue); 2813 2814 // elem must be an Array. 2815 // Use the apply argument as backing for global RegExp 2816 // properties. 2817 2818 CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE)); 2819 2820 // TODO(jgruber): Remove indirection through 2821 // Call->ReflectApply. 2822 Callable call_callable = CodeFactory::Call(isolate); 2823 Node* const reflect_apply = LoadContextElement( 2824 native_context, Context::REFLECT_APPLY_INDEX); 2825 2826 Node* const replacement_obj = 2827 CallJS(call_callable, context, reflect_apply, undefined, 2828 replace_callable, undefined, elem); 2829 2830 // Overwrite the i'th element in the results with the string 2831 // we got back from the callback function. 2832 2833 TNode<String> const replacement_str = 2834 ToString_Inline(context, replacement_obj); 2835 StoreFixedArrayElement(res_elems, index, replacement_str); 2836 2837 Goto(&do_continue); 2838 BIND(&do_continue); 2839 }, 2840 increment, CodeStubAssembler::INTPTR_PARAMETERS, 2841 CodeStubAssembler::IndexAdvanceMode::kPost); 2842 2843 Goto(&create_result); 2844 } 2845 2846 BIND(&create_result); 2847 { 2848 Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context, 2849 res, res_length, string); 2850 var_result.Bind(result); 2851 Goto(&out); 2852 } 2853 2854 BIND(&out); 2855 return var_result.value(); 2856 } 2857 2858 Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( 2859 Node* context, Node* regexp, TNode<String> string, 2860 TNode<String> replace_string) { 2861 // The fast path is reached only if {receiver} is an unmodified 2862 // JSRegExp instance, {replace_value} is non-callable, and 2863 // ToString({replace_value}) does not contain '$', i.e. we're doing a simple 2864 // string replacement. 2865 2866 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2867 2868 const bool kIsFastPath = true; 2869 2870 TVARIABLE(String, var_result, EmptyStringConstant()); 2871 VARIABLE(var_last_match_end, MachineRepresentation::kTagged, SmiZero()); 2872 VARIABLE(var_is_unicode, MachineRepresentation::kWord32, Int32Constant(0)); 2873 Variable* vars[] = {&var_result, &var_last_match_end}; 2874 Label out(this), loop(this, 2, vars), loop_end(this), 2875 if_nofurthermatches(this); 2876 2877 // Is {regexp} global? 2878 Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); 2879 GotoIfNot(is_global, &loop); 2880 2881 var_is_unicode.Bind(FastFlagGetter(regexp, JSRegExp::kUnicode)); 2882 FastStoreLastIndex(regexp, SmiZero()); 2883 Goto(&loop); 2884 2885 BIND(&loop); 2886 { 2887 TNode<RegExpMatchInfo> var_match_indices = 2888 RegExpPrototypeExecBodyWithoutResult(CAST(context), CAST(regexp), 2889 string, &if_nofurthermatches, 2890 kIsFastPath); 2891 2892 // Successful match. 2893 { 2894 TNode<Smi> const match_start = CAST(LoadFixedArrayElement( 2895 var_match_indices, RegExpMatchInfo::kFirstCaptureIndex)); 2896 TNode<Smi> const match_end = CAST(LoadFixedArrayElement( 2897 var_match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1)); 2898 2899 TNode<Smi> const replace_length = LoadStringLengthAsSmi(replace_string); 2900 2901 // TODO(jgruber): We could skip many of the checks that using SubString 2902 // here entails. 2903 TNode<String> first_part = 2904 CAST(CallBuiltin(Builtins::kSubString, context, string, 2905 var_last_match_end.value(), match_start)); 2906 var_result = CAST(CallBuiltin(Builtins::kStringAdd_CheckNone_NotTenured, 2907 context, var_result.value(), first_part)); 2908 2909 GotoIf(SmiEqual(replace_length, SmiZero()), &loop_end); 2910 2911 var_result = 2912 CAST(CallBuiltin(Builtins::kStringAdd_CheckNone_NotTenured, context, 2913 var_result.value(), replace_string)); 2914 Goto(&loop_end); 2915 2916 BIND(&loop_end); 2917 { 2918 var_last_match_end.Bind(match_end); 2919 // Non-global case ends here after the first replacement. 2920 GotoIfNot(is_global, &if_nofurthermatches); 2921 2922 GotoIf(SmiNotEqual(match_end, match_start), &loop); 2923 // If match is the empty string, we have to increment lastIndex. 2924 Node* const this_index = FastLoadLastIndex(CAST(regexp)); 2925 Node* const next_index = AdvanceStringIndex( 2926 string, this_index, var_is_unicode.value(), kIsFastPath); 2927 FastStoreLastIndex(regexp, next_index); 2928 Goto(&loop); 2929 } 2930 } 2931 } 2932 2933 BIND(&if_nofurthermatches); 2934 { 2935 TNode<Smi> const string_length = LoadStringLengthAsSmi(string); 2936 TNode<String> last_part = 2937 CAST(CallBuiltin(Builtins::kSubString, context, string, 2938 var_last_match_end.value(), string_length)); 2939 var_result = CAST(CallBuiltin(Builtins::kStringAdd_CheckNone_NotTenured, 2940 context, var_result.value(), last_part)); 2941 Goto(&out); 2942 } 2943 2944 BIND(&out); 2945 return var_result.value(); 2946 } 2947 2948 // Helper that skips a few initial checks. 2949 TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) { 2950 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kRegExp)); 2951 TNode<String> string = CAST(Parameter(Descriptor::kString)); 2952 TNode<Object> replace_value = CAST(Parameter(Descriptor::kReplaceValue)); 2953 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 2954 2955 CSA_ASSERT(this, IsFastRegExp(context, regexp)); 2956 2957 Label checkreplacestring(this), if_iscallable(this), 2958 runtime(this, Label::kDeferred); 2959 2960 // 2. Is {replace_value} callable? 2961 GotoIf(TaggedIsSmi(replace_value), &checkreplacestring); 2962 Branch(IsCallableMap(LoadMap(CAST(replace_value))), &if_iscallable, 2963 &checkreplacestring); 2964 2965 // 3. Does ToString({replace_value}) contain '$'? 2966 BIND(&checkreplacestring); 2967 { 2968 TNode<String> const replace_string = 2969 ToString_Inline(context, replace_value); 2970 2971 // ToString(replaceValue) could potentially change the shape of the RegExp 2972 // object. Recheck that we are still on the fast path and bail to runtime 2973 // otherwise. 2974 { 2975 Label next(this); 2976 BranchIfFastRegExp(context, regexp, &next, &runtime); 2977 BIND(&next); 2978 } 2979 2980 TNode<String> const dollar_string = HeapConstant( 2981 isolate()->factory()->LookupSingleCharacterStringFromCode('$')); 2982 TNode<Smi> const dollar_ix = 2983 CAST(CallBuiltin(Builtins::kStringIndexOf, context, replace_string, 2984 dollar_string, SmiZero())); 2985 GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime); 2986 2987 Return( 2988 ReplaceSimpleStringFastPath(context, regexp, string, replace_string)); 2989 } 2990 2991 // {regexp} is unmodified and {replace_value} is callable. 2992 BIND(&if_iscallable); 2993 { 2994 Node* const replace_fn = replace_value; 2995 2996 // Check if the {regexp} is global. 2997 Label if_isglobal(this), if_isnotglobal(this); 2998 2999 Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); 3000 Branch(is_global, &if_isglobal, &if_isnotglobal); 3001 3002 BIND(&if_isglobal); 3003 Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn)); 3004 3005 BIND(&if_isnotglobal); 3006 Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction, 3007 context, string, regexp, replace_fn)); 3008 } 3009 3010 BIND(&runtime); 3011 Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string, 3012 replace_value)); 3013 } 3014 3015 // ES#sec-regexp.prototype-@@replace 3016 // RegExp.prototype [ @@replace ] ( string, replaceValue ) 3017 TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { 3018 const int kStringArg = 0; 3019 const int kReplaceValueArg = 1; 3020 3021 TNode<IntPtrT> argc = 3022 ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); 3023 CodeStubArguments args(this, argc); 3024 3025 TNode<Object> maybe_receiver = args.GetReceiver(); 3026 TNode<Object> maybe_string = args.GetOptionalArgumentValue(kStringArg); 3027 TNode<Object> replace_value = args.GetOptionalArgumentValue(kReplaceValueArg); 3028 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 3029 3030 // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic: 3031 // 3032 // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace) 3033 // if (IsCallable(replace)) { 3034 // if (IsGlobal(receiver)) { 3035 // // Called 'fast-path' but contains several runtime calls. 3036 // ReplaceGlobalCallableFastPath() 3037 // } else { 3038 // CallRuntime(StringReplaceNonGlobalRegExpWithFunction) 3039 // } 3040 // } else { 3041 // if (replace.contains("$")) { 3042 // CallRuntime(RegExpReplace) 3043 // } else { 3044 // ReplaceSimpleStringFastPath() 3045 // } 3046 // } 3047 3048 // Ensure {maybe_receiver} is a JSReceiver. 3049 ThrowIfNotJSReceiver(context, maybe_receiver, 3050 MessageTemplate::kIncompatibleMethodReceiver, 3051 "RegExp.prototype.@@replace"); 3052 Node* const receiver = maybe_receiver; 3053 3054 // Convert {maybe_string} to a String. 3055 TNode<String> const string = ToString_Inline(context, maybe_string); 3056 3057 // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? 3058 Label stub(this), runtime(this, Label::kDeferred); 3059 BranchIfFastRegExp(context, receiver, &stub, &runtime); 3060 3061 BIND(&stub); 3062 args.PopAndReturn(CallBuiltin(Builtins::kRegExpReplace, context, receiver, 3063 string, replace_value)); 3064 3065 BIND(&runtime); 3066 args.PopAndReturn(CallRuntime(Runtime::kRegExpReplace, context, receiver, 3067 string, replace_value)); 3068 } 3069 3070 // Simple string matching functionality for internal use which does not modify 3071 // the last match info. 3072 TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) { 3073 TNode<JSRegExp> regexp = CAST(Parameter(Descriptor::kRegExp)); 3074 TNode<String> string = CAST(Parameter(Descriptor::kString)); 3075 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 3076 3077 TNode<Context> native_context = LoadNativeContext(context); 3078 TNode<RegExpMatchInfo> internal_match_info = CAST(LoadContextElement( 3079 native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX)); 3080 TNode<HeapObject> maybe_match_indices = 3081 CAST(CallBuiltin(Builtins::kRegExpExecInternal, context, regexp, string, 3082 SmiZero(), internal_match_info)); 3083 TNode<Oddball> null = NullConstant(); 3084 Label if_matched(this); 3085 GotoIfNot(WordEqual(maybe_match_indices, null), &if_matched); 3086 Return(null); 3087 3088 BIND(&if_matched); 3089 TNode<RegExpMatchInfo> match_indices = CAST(maybe_match_indices); 3090 Return( 3091 ConstructNewResultFromMatchInfo(context, regexp, match_indices, string)); 3092 } 3093 3094 class RegExpStringIteratorAssembler : public RegExpBuiltinsAssembler { 3095 public: 3096 explicit RegExpStringIteratorAssembler(compiler::CodeAssemblerState* state) 3097 : RegExpBuiltinsAssembler(state) {} 3098 3099 protected: 3100 TNode<Smi> LoadFlags(TNode<HeapObject> iterator) { 3101 return LoadObjectField<Smi>(iterator, JSRegExpStringIterator::kFlagsOffset); 3102 } 3103 3104 TNode<BoolT> HasDoneFlag(TNode<Smi> flags) { 3105 return UncheckedCast<BoolT>( 3106 IsSetSmi(flags, 1 << JSRegExpStringIterator::kDoneBit)); 3107 } 3108 3109 TNode<BoolT> HasGlobalFlag(TNode<Smi> flags) { 3110 return UncheckedCast<BoolT>( 3111 IsSetSmi(flags, 1 << JSRegExpStringIterator::kGlobalBit)); 3112 } 3113 3114 TNode<BoolT> HasUnicodeFlag(TNode<Smi> flags) { 3115 return UncheckedCast<BoolT>( 3116 IsSetSmi(flags, 1 << JSRegExpStringIterator::kUnicodeBit)); 3117 } 3118 3119 void SetDoneFlag(TNode<HeapObject> iterator, TNode<Smi> flags) { 3120 TNode<Smi> new_flags = 3121 SmiOr(flags, SmiConstant(1 << JSRegExpStringIterator::kDoneBit)); 3122 StoreObjectFieldNoWriteBarrier( 3123 iterator, JSRegExpStringIterator::kFlagsOffset, new_flags); 3124 } 3125 }; 3126 3127 // https://tc39.github.io/proposal-string-matchall/ 3128 // %RegExpStringIteratorPrototype%.next ( ) 3129 TF_BUILTIN(RegExpStringIteratorPrototypeNext, RegExpStringIteratorAssembler) { 3130 const char* method_name = "%RegExpStringIterator%.prototype.next"; 3131 TNode<Context> context = CAST(Parameter(Descriptor::kContext)); 3132 TNode<Object> maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); 3133 3134 Label if_match(this), if_no_match(this, Label::kDeferred), 3135 return_empty_done_result(this, Label::kDeferred); 3136 3137 // 1. Let O be the this value. 3138 // 2. If Type(O) is not Object, throw a TypeError exception. 3139 // 3. If O does not have all of the internal slots of a RegExp String Iterator 3140 // Object Instance (see 5.3), throw a TypeError exception. 3141 ThrowIfNotInstanceType(context, maybe_receiver, 3142 JS_REGEXP_STRING_ITERATOR_TYPE, method_name); 3143 TNode<HeapObject> receiver = CAST(maybe_receiver); 3144 3145 // 4. If O.[[Done]] is true, then 3146 // a. Return ! CreateIterResultObject(undefined, true). 3147 TNode<Smi> flags = LoadFlags(receiver); 3148 GotoIf(HasDoneFlag(flags), &return_empty_done_result); 3149 3150 // 5. Let R be O.[[IteratingRegExp]]. 3151 TNode<Object> iterating_regexp = 3152 LoadObjectField(receiver, JSRegExpStringIterator::kIteratingRegExpOffset); 3153 3154 // TODO(jgruber): Verify that this is guaranteed. 3155 CSA_CHECK(this, TaggedIsNotSmi(iterating_regexp)); 3156 CSA_CHECK(this, IsJSReceiver(CAST(iterating_regexp))); 3157 3158 // 6. Let S be O.[[IteratedString]]. 3159 TNode<String> iterating_string = CAST( 3160 LoadObjectField(receiver, JSRegExpStringIterator::kIteratedStringOffset)); 3161 3162 // 7. Let global be O.[[Global]]. 3163 // See if_match. 3164 3165 // 8. Let fullUnicode be O.[[Unicode]]. 3166 // See if_global. 3167 3168 // 9. Let match be ? RegExpExec(R, S). 3169 TVARIABLE(Object, var_match); 3170 TVARIABLE(BoolT, var_is_fast_regexp); 3171 { 3172 Label if_fast(this), if_slow(this, Label::kDeferred); 3173 BranchIfFastRegExp(context, iterating_regexp, &if_fast, &if_slow); 3174 3175 BIND(&if_fast); 3176 { 3177 TNode<RegExpMatchInfo> match_indices = 3178 RegExpPrototypeExecBodyWithoutResult(context, CAST(iterating_regexp), 3179 iterating_string, &if_no_match, 3180 true); 3181 var_match = ConstructNewResultFromMatchInfo( 3182 context, CAST(iterating_regexp), match_indices, iterating_string); 3183 var_is_fast_regexp = Int32TrueConstant(); 3184 Goto(&if_match); 3185 } 3186 3187 BIND(&if_slow); 3188 { 3189 var_match = CAST(RegExpExec(context, iterating_regexp, iterating_string)); 3190 var_is_fast_regexp = Int32FalseConstant(); 3191 Branch(IsNull(var_match.value()), &if_no_match, &if_match); 3192 } 3193 } 3194 3195 // 10. If match is null, then 3196 BIND(&if_no_match); 3197 { 3198 // a. Set O.[[Done]] to true. 3199 SetDoneFlag(receiver, flags); 3200 3201 // b. Return ! CreateIterResultObject(undefined, true). 3202 Goto(&return_empty_done_result); 3203 } 3204 // 11. Else, 3205 BIND(&if_match); 3206 { 3207 Label if_global(this), if_not_global(this, Label::kDeferred), 3208 return_result(this); 3209 3210 // a. If global is true, 3211 Branch(HasGlobalFlag(flags), &if_global, &if_not_global); 3212 BIND(&if_global); 3213 { 3214 Label if_fast(this), if_slow(this, Label::kDeferred); 3215 3216 // ii. If matchStr is the empty string, 3217 Branch(var_is_fast_regexp.value(), &if_fast, &if_slow); 3218 BIND(&if_fast); 3219 { 3220 // i. Let matchStr be ? ToString(? Get(match, "0")). 3221 CSA_ASSERT_BRANCH(this, [&](Label* ok, Label* not_ok) { 3222 BranchIfFastRegExpResult(context, var_match.value(), ok, not_ok); 3223 }); 3224 CSA_ASSERT(this, 3225 SmiNotEqual(LoadFastJSArrayLength(CAST(var_match.value())), 3226 SmiZero())); 3227 TNode<FixedArray> result_fixed_array = 3228 CAST(LoadElements(CAST(var_match.value()))); 3229 TNode<String> match_str = 3230 CAST(LoadFixedArrayElement(result_fixed_array, 0)); 3231 3232 // When iterating_regexp is fast, we assume it stays fast even after 3233 // accessing the first match from the RegExp result. 3234 CSA_ASSERT(this, IsFastRegExp(context, iterating_regexp)); 3235 GotoIfNot(IsEmptyString(match_str), &return_result); 3236 3237 // 1. Let thisIndex be ? ToLength(? Get(R, "lastIndex")). 3238 TNode<Smi> this_index = CAST(FastLoadLastIndex(CAST(iterating_regexp))); 3239 CSA_ASSERT(this, TaggedIsSmi(this_index)); 3240 3241 // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). 3242 TNode<Smi> next_index = CAST(AdvanceStringIndex( 3243 iterating_string, this_index, HasUnicodeFlag(flags), true)); 3244 CSA_ASSERT(this, TaggedIsSmi(next_index)); 3245 3246 // 3. Perform ? Set(R, "lastIndex", nextIndex, true). 3247 FastStoreLastIndex(iterating_regexp, next_index); 3248 3249 // iii. Return ! CreateIterResultObject(match, false). 3250 Goto(&return_result); 3251 } 3252 BIND(&if_slow); 3253 { 3254 // i. Let matchStr be ? ToString(? Get(match, "0")). 3255 TNode<String> match_str = ToString_Inline( 3256 context, GetProperty(context, var_match.value(), SmiZero())); 3257 3258 GotoIfNot(IsEmptyString(match_str), &return_result); 3259 3260 // 1. Let thisIndex be ? ToLength(? Get(R, "lastIndex")). 3261 TNode<Object> last_index = SlowLoadLastIndex(context, iterating_regexp); 3262 TNode<Number> this_index = ToLength_Inline(context, last_index); 3263 3264 // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). 3265 TNode<Object> next_index = CAST(AdvanceStringIndex( 3266 iterating_string, this_index, HasUnicodeFlag(flags), false)); 3267 3268 // 3. Perform ? Set(R, "lastIndex", nextIndex, true). 3269 SlowStoreLastIndex(context, iterating_regexp, next_index); 3270 3271 // iii. Return ! CreateIterResultObject(match, false). 3272 Goto(&return_result); 3273 } 3274 } 3275 // b. Else, 3276 BIND(&if_not_global); 3277 { 3278 // i. Set O.[[Done]] to true. 3279 SetDoneFlag(receiver, flags); 3280 3281 // ii. Return ! CreateIterResultObject(match, false). 3282 Goto(&return_result); 3283 } 3284 BIND(&return_result); 3285 { 3286 Return(AllocateJSIteratorResult(context, var_match.value(), 3287 FalseConstant())); 3288 } 3289 } 3290 BIND(&return_empty_done_result); 3291 Return( 3292 AllocateJSIteratorResult(context, UndefinedConstant(), TrueConstant())); 3293 } 3294 3295 } // namespace internal 3296 } // namespace v8 3297