1 // Copyright 2016 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.h" 6 7 #include "src/builtins/builtins-constructor.h" 8 #include "src/builtins/builtins-utils.h" 9 #include "src/builtins/builtins.h" 10 #include "src/code-factory.h" 11 #include "src/code-stub-assembler.h" 12 #include "src/counters.h" 13 #include "src/objects-inl.h" 14 #include "src/objects/regexp-match-info.h" 15 #include "src/regexp/jsregexp.h" 16 #include "src/regexp/regexp-utils.h" 17 #include "src/string-builder.h" 18 19 namespace v8 { 20 namespace internal { 21 22 typedef CodeStubAssembler::ParameterMode ParameterMode; 23 24 25 // ----------------------------------------------------------------------------- 26 // ES6 section 21.2 RegExp Objects 27 28 Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) { 29 // Load the in-object field. 30 static const int field_offset = 31 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; 32 return LoadObjectField(regexp, field_offset); 33 } 34 35 Node* RegExpBuiltinsAssembler::SlowLoadLastIndex(Node* context, Node* regexp) { 36 // Load through the GetProperty stub. 37 Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); 38 Callable getproperty_callable = CodeFactory::GetProperty(isolate()); 39 return CallStub(getproperty_callable, context, regexp, name); 40 } 41 42 Node* RegExpBuiltinsAssembler::LoadLastIndex(Node* context, Node* regexp, 43 bool is_fastpath) { 44 return is_fastpath ? FastLoadLastIndex(regexp) 45 : SlowLoadLastIndex(context, regexp); 46 } 47 48 // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified 49 // JSRegExp instance. 50 void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) { 51 // Store the in-object field. 52 static const int field_offset = 53 JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; 54 StoreObjectField(regexp, field_offset, value); 55 } 56 57 void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp, 58 Node* value) { 59 // Store through runtime. 60 // TODO(ishell): Use SetPropertyStub here once available. 61 Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); 62 Node* const language_mode = SmiConstant(Smi::FromInt(STRICT)); 63 CallRuntime(Runtime::kSetProperty, context, regexp, name, value, 64 language_mode); 65 } 66 67 void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp, 68 Node* value, bool is_fastpath) { 69 if (is_fastpath) { 70 FastStoreLastIndex(regexp, value); 71 } else { 72 SlowStoreLastIndex(context, regexp, value); 73 } 74 } 75 76 Node* RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( 77 Node* const context, Node* const regexp, Node* const match_info, 78 Node* const string) { 79 Label named_captures(this), out(this); 80 81 Node* const num_indices = SmiUntag(LoadFixedArrayElement( 82 match_info, RegExpMatchInfo::kNumberOfCapturesIndex)); 83 Node* const num_results = SmiTag(WordShr(num_indices, 1)); 84 Node* const start = 85 LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex); 86 Node* const end = LoadFixedArrayElement( 87 match_info, RegExpMatchInfo::kFirstCaptureIndex + 1); 88 89 // Calculate the substring of the first match before creating the result array 90 // to avoid an unnecessary write barrier storing the first result. 91 Node* const first = SubString(context, string, start, end); 92 93 Node* const result = 94 AllocateRegExpResult(context, num_results, start, string); 95 Node* const result_elements = LoadElements(result); 96 97 StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER); 98 99 // If no captures exist we can skip named capture handling as well. 100 GotoIf(SmiEqual(num_results, SmiConstant(1)), &out); 101 102 // Store all remaining captures. 103 Node* const limit = IntPtrAdd( 104 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices); 105 106 Variable var_from_cursor( 107 this, MachineType::PointerRepresentation(), 108 IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2)); 109 Variable var_to_cursor(this, MachineType::PointerRepresentation(), 110 IntPtrConstant(1)); 111 112 Variable* vars[] = {&var_from_cursor, &var_to_cursor}; 113 Label loop(this, 2, vars); 114 115 Goto(&loop); 116 Bind(&loop); 117 { 118 Node* const from_cursor = var_from_cursor.value(); 119 Node* const to_cursor = var_to_cursor.value(); 120 Node* const start = LoadFixedArrayElement(match_info, from_cursor); 121 122 Label next_iter(this); 123 GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter); 124 125 Node* const from_cursor_plus1 = IntPtrAdd(from_cursor, IntPtrConstant(1)); 126 Node* const end = LoadFixedArrayElement(match_info, from_cursor_plus1); 127 128 Node* const capture = SubString(context, string, start, end); 129 StoreFixedArrayElement(result_elements, to_cursor, capture); 130 Goto(&next_iter); 131 132 Bind(&next_iter); 133 var_from_cursor.Bind(IntPtrAdd(from_cursor, IntPtrConstant(2))); 134 var_to_cursor.Bind(IntPtrAdd(to_cursor, IntPtrConstant(1))); 135 Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop, 136 &named_captures); 137 } 138 139 Bind(&named_captures); 140 { 141 // We reach this point only if captures exist, implying that this is an 142 // IRREGEXP JSRegExp. 143 144 CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); 145 CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1))); 146 147 // Preparations for named capture properties. Exit early if the result does 148 // not have any named captures to minimize performance impact. 149 150 Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset); 151 CSA_ASSERT(this, SmiEqual(LoadFixedArrayElement(data, JSRegExp::kTagIndex), 152 SmiConstant(JSRegExp::IRREGEXP))); 153 154 // The names fixed array associates names at even indices with a capture 155 // index at odd indices. 156 Node* const names = 157 LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex); 158 GotoIf(SmiEqual(names, SmiConstant(0)), &out); 159 160 // Allocate a new object to store the named capture properties. 161 // TODO(jgruber): Could be optimized by adding the object map to the heap 162 // root list. 163 164 Node* const native_context = LoadNativeContext(context); 165 Node* const map = LoadContextElement( 166 native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP); 167 Node* const properties = 168 AllocateNameDictionary(NameDictionary::kInitialCapacity); 169 170 Node* const group_object = AllocateJSObjectFromMap(map, properties); 171 172 // Store it on the result as a 'group' property. 173 174 { 175 Node* const name = HeapConstant(isolate()->factory()->group_string()); 176 CallRuntime(Runtime::kCreateDataProperty, context, result, name, 177 group_object); 178 } 179 180 // One or more named captures exist, add a property for each one. 181 182 CSA_ASSERT(this, HasInstanceType(names, FIXED_ARRAY_TYPE)); 183 Node* const names_length = LoadAndUntagFixedArrayBaseLength(names); 184 CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrConstant(0))); 185 186 Variable var_i(this, MachineType::PointerRepresentation()); 187 var_i.Bind(IntPtrConstant(0)); 188 189 Variable* vars[] = {&var_i}; 190 const int vars_count = sizeof(vars) / sizeof(vars[0]); 191 Label loop(this, vars_count, vars); 192 193 Goto(&loop); 194 Bind(&loop); 195 { 196 Node* const i = var_i.value(); 197 Node* const i_plus_1 = IntPtrAdd(i, IntPtrConstant(1)); 198 Node* const i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1)); 199 200 Node* const name = LoadFixedArrayElement(names, i); 201 Node* const index = LoadFixedArrayElement(names, i_plus_1); 202 Node* const capture = 203 LoadFixedArrayElement(result_elements, SmiUntag(index)); 204 205 CallRuntime(Runtime::kCreateDataProperty, context, group_object, name, 206 capture); 207 208 var_i.Bind(i_plus_2); 209 Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out, 210 &loop); 211 } 212 } 213 214 Bind(&out); 215 return result; 216 } 217 218 // ES#sec-regexp.prototype.exec 219 // RegExp.prototype.exec ( string ) 220 // Implements the core of RegExp.prototype.exec but without actually 221 // constructing the JSRegExpResult. Returns either null (if the RegExp did not 222 // match) or a fixed array containing match indices as returned by 223 // RegExpExecStub. 224 Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( 225 Node* const context, Node* const regexp, Node* const string, 226 Label* if_didnotmatch, const bool is_fastpath) { 227 Isolate* const isolate = this->isolate(); 228 229 Node* const null = NullConstant(); 230 Node* const int_zero = IntPtrConstant(0); 231 Node* const smi_zero = SmiConstant(Smi::kZero); 232 233 if (!is_fastpath) { 234 ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, 235 "RegExp.prototype.exec"); 236 } 237 238 CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(string))); 239 CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); 240 241 Variable var_result(this, MachineRepresentation::kTagged); 242 Label out(this); 243 244 // Load lastIndex. 245 Variable var_lastindex(this, MachineRepresentation::kTagged); 246 { 247 Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath); 248 var_lastindex.Bind(regexp_lastindex); 249 250 if (is_fastpath) { 251 // ToLength on a positive smi is a nop and can be skipped. 252 CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex)); 253 } else { 254 // Omit ToLength if lastindex is a non-negative smi. 255 Label call_tolength(this, Label::kDeferred), next(this); 256 Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength); 257 258 Bind(&call_tolength); 259 { 260 Callable tolength_callable = CodeFactory::ToLength(isolate); 261 var_lastindex.Bind( 262 CallStub(tolength_callable, context, regexp_lastindex)); 263 Goto(&next); 264 } 265 266 Bind(&next); 267 } 268 } 269 270 // Check whether the regexp is global or sticky, which determines whether we 271 // update last index later on. 272 Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); 273 Node* const is_global_or_sticky = WordAnd( 274 SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky)); 275 Node* const should_update_last_index = 276 WordNotEqual(is_global_or_sticky, int_zero); 277 278 // Grab and possibly update last index. 279 Label run_exec(this); 280 { 281 Label if_doupdate(this), if_dontupdate(this); 282 Branch(should_update_last_index, &if_doupdate, &if_dontupdate); 283 284 Bind(&if_doupdate); 285 { 286 Node* const lastindex = var_lastindex.value(); 287 288 Label if_isoob(this, Label::kDeferred); 289 GotoIfNot(TaggedIsSmi(lastindex), &if_isoob); 290 Node* const string_length = LoadStringLength(string); 291 GotoIfNot(SmiLessThanOrEqual(lastindex, string_length), &if_isoob); 292 Goto(&run_exec); 293 294 Bind(&if_isoob); 295 { 296 StoreLastIndex(context, regexp, smi_zero, is_fastpath); 297 var_result.Bind(null); 298 Goto(if_didnotmatch); 299 } 300 } 301 302 Bind(&if_dontupdate); 303 { 304 var_lastindex.Bind(smi_zero); 305 Goto(&run_exec); 306 } 307 } 308 309 Node* match_indices; 310 Label successful_match(this); 311 Bind(&run_exec); 312 { 313 // Get last match info from the context. 314 Node* const native_context = LoadNativeContext(context); 315 Node* const last_match_info = LoadContextElement( 316 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 317 318 // Call the exec stub. 319 Callable exec_callable = CodeFactory::RegExpExec(isolate); 320 match_indices = CallStub(exec_callable, context, regexp, string, 321 var_lastindex.value(), last_match_info); 322 var_result.Bind(match_indices); 323 324 // {match_indices} is either null or the RegExpMatchInfo array. 325 // Return early if exec failed, possibly updating last index. 326 GotoIfNot(WordEqual(match_indices, null), &successful_match); 327 328 GotoIfNot(should_update_last_index, if_didnotmatch); 329 330 StoreLastIndex(context, regexp, smi_zero, is_fastpath); 331 Goto(if_didnotmatch); 332 } 333 334 Bind(&successful_match); 335 { 336 GotoIfNot(should_update_last_index, &out); 337 338 // Update the new last index from {match_indices}. 339 Node* const new_lastindex = LoadFixedArrayElement( 340 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); 341 342 StoreLastIndex(context, regexp, new_lastindex, is_fastpath); 343 Goto(&out); 344 } 345 346 Bind(&out); 347 return var_result.value(); 348 } 349 350 // ES#sec-regexp.prototype.exec 351 // RegExp.prototype.exec ( string ) 352 Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBody(Node* const context, 353 Node* const regexp, 354 Node* const string, 355 const bool is_fastpath) { 356 Node* const null = NullConstant(); 357 358 Variable var_result(this, MachineRepresentation::kTagged); 359 360 Label if_didnotmatch(this), out(this); 361 Node* const indices_or_null = RegExpPrototypeExecBodyWithoutResult( 362 context, regexp, string, &if_didnotmatch, is_fastpath); 363 364 // Successful match. 365 { 366 Node* const match_indices = indices_or_null; 367 Node* const result = 368 ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); 369 var_result.Bind(result); 370 Goto(&out); 371 } 372 373 Bind(&if_didnotmatch); 374 { 375 var_result.Bind(null); 376 Goto(&out); 377 } 378 379 Bind(&out); 380 return var_result.value(); 381 } 382 383 Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver( 384 Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template, 385 char const* method_name) { 386 Label out(this), throw_exception(this, Label::kDeferred); 387 Variable var_value_map(this, MachineRepresentation::kTagged); 388 389 GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception); 390 391 // Load the instance type of the {value}. 392 var_value_map.Bind(LoadMap(maybe_receiver)); 393 Node* const value_instance_type = LoadMapInstanceType(var_value_map.value()); 394 395 Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception); 396 397 // The {value} is not a compatible receiver for this method. 398 Bind(&throw_exception); 399 { 400 Node* const message_id = SmiConstant(Smi::FromInt(msg_template)); 401 Node* const method_name_str = HeapConstant( 402 isolate()->factory()->NewStringFromAsciiChecked(method_name, TENURED)); 403 404 Callable callable = CodeFactory::ToString(isolate()); 405 Node* const value_str = CallStub(callable, context, maybe_receiver); 406 407 CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str, 408 value_str); 409 Unreachable(); 410 } 411 412 Bind(&out); 413 return var_value_map.value(); 414 } 415 416 Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* object, 417 Node* map) { 418 Label out(this); 419 Variable var_result(this, MachineRepresentation::kWord32); 420 421 Node* const native_context = LoadNativeContext(context); 422 Node* const regexp_fun = 423 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 424 Node* const initial_map = 425 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 426 Node* const has_initialmap = WordEqual(map, initial_map); 427 428 var_result.Bind(has_initialmap); 429 GotoIfNot(has_initialmap, &out); 430 431 // The smi check is required to omit ToLength(lastIndex) calls with possible 432 // user-code execution on the fast path. 433 Node* const last_index = FastLoadLastIndex(object); 434 var_result.Bind(TaggedIsPositiveSmi(last_index)); 435 Goto(&out); 436 437 Bind(&out); 438 return var_result.value(); 439 } 440 441 // RegExp fast path implementations rely on unmodified JSRegExp instances. 442 // We use a fairly coarse granularity for this and simply check whether both 443 // the regexp itself is unmodified (i.e. its map has not changed), its 444 // prototype is unmodified, and lastIndex is a non-negative smi. 445 void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, 446 Node* const object, 447 Node* const map, 448 Label* const if_isunmodified, 449 Label* const if_ismodified) { 450 CSA_ASSERT(this, WordEqual(LoadMap(object), map)); 451 452 // TODO(ishell): Update this check once map changes for constant field 453 // tracking are landing. 454 455 Node* const native_context = LoadNativeContext(context); 456 Node* const regexp_fun = 457 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 458 Node* const initial_map = 459 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 460 Node* const has_initialmap = WordEqual(map, initial_map); 461 462 GotoIfNot(has_initialmap, if_ismodified); 463 464 Node* const initial_proto_initial_map = 465 LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX); 466 Node* const proto_map = LoadMap(LoadMapPrototype(map)); 467 Node* const proto_has_initialmap = 468 WordEqual(proto_map, initial_proto_initial_map); 469 470 GotoIfNot(proto_has_initialmap, if_ismodified); 471 472 // The smi check is required to omit ToLength(lastIndex) calls with possible 473 // user-code execution on the fast path. 474 Node* const last_index = FastLoadLastIndex(object); 475 Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified); 476 } 477 478 Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context, 479 Node* const object, 480 Node* const map) { 481 Label yup(this), nope(this), out(this); 482 Variable var_result(this, MachineRepresentation::kWord32); 483 484 BranchIfFastRegExp(context, object, map, &yup, &nope); 485 486 Bind(&yup); 487 var_result.Bind(Int32Constant(1)); 488 Goto(&out); 489 490 Bind(&nope); 491 var_result.Bind(Int32Constant(0)); 492 Goto(&out); 493 494 Bind(&out); 495 return var_result.value(); 496 } 497 498 void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map, 499 Label* if_isunmodified, 500 Label* if_ismodified) { 501 Node* const native_context = LoadNativeContext(context); 502 Node* const initial_regexp_result_map = 503 LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); 504 505 Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified, 506 if_ismodified); 507 } 508 509 // ES#sec-regexp.prototype.exec 510 // RegExp.prototype.exec ( string ) 511 TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { 512 Node* const maybe_receiver = Parameter(0); 513 Node* const maybe_string = Parameter(1); 514 Node* const context = Parameter(4); 515 516 // Ensure {maybe_receiver} is a JSRegExp. 517 ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, 518 "RegExp.prototype.exec"); 519 Node* const receiver = maybe_receiver; 520 521 // Convert {maybe_string} to a String. 522 Node* const string = ToString(context, maybe_string); 523 524 Label if_isfastpath(this), if_isslowpath(this); 525 Branch(IsInitialRegExpMap(context, receiver, LoadMap(receiver)), 526 &if_isfastpath, &if_isslowpath); 527 528 Bind(&if_isfastpath); 529 { 530 Node* const result = 531 RegExpPrototypeExecBody(context, receiver, string, true); 532 Return(result); 533 } 534 535 Bind(&if_isslowpath); 536 { 537 Node* const result = 538 RegExpPrototypeExecBody(context, receiver, string, false); 539 Return(result); 540 } 541 } 542 543 Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context, 544 Node* const regexp, 545 bool is_fastpath) { 546 Isolate* isolate = this->isolate(); 547 548 Node* const int_zero = IntPtrConstant(0); 549 Node* const int_one = IntPtrConstant(1); 550 Variable var_length(this, MachineType::PointerRepresentation(), int_zero); 551 Variable var_flags(this, MachineType::PointerRepresentation()); 552 553 // First, count the number of characters we will need and check which flags 554 // are set. 555 556 if (is_fastpath) { 557 // Refer to JSRegExp's flag property on the fast-path. 558 Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset); 559 Node* const flags_intptr = SmiUntag(flags_smi); 560 var_flags.Bind(flags_intptr); 561 562 #define CASE_FOR_FLAG(FLAG) \ 563 do { \ 564 Label next(this); \ 565 GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \ 566 var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ 567 Goto(&next); \ 568 Bind(&next); \ 569 } while (false) 570 571 CASE_FOR_FLAG(JSRegExp::kGlobal); 572 CASE_FOR_FLAG(JSRegExp::kIgnoreCase); 573 CASE_FOR_FLAG(JSRegExp::kMultiline); 574 CASE_FOR_FLAG(JSRegExp::kUnicode); 575 CASE_FOR_FLAG(JSRegExp::kSticky); 576 #undef CASE_FOR_FLAG 577 } else { 578 DCHECK(!is_fastpath); 579 580 // Fall back to GetProperty stub on the slow-path. 581 var_flags.Bind(int_zero); 582 583 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 584 585 #define CASE_FOR_FLAG(NAME, FLAG) \ 586 do { \ 587 Label next(this); \ 588 Node* const name = \ 589 HeapConstant(isolate->factory()->InternalizeUtf8String(NAME)); \ 590 Node* const flag = CallStub(getproperty_callable, context, regexp, name); \ 591 Label if_isflagset(this); \ 592 BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \ 593 Bind(&if_isflagset); \ 594 var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ 595 var_flags.Bind(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \ 596 Goto(&next); \ 597 Bind(&next); \ 598 } while (false) 599 600 CASE_FOR_FLAG("global", JSRegExp::kGlobal); 601 CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase); 602 CASE_FOR_FLAG("multiline", JSRegExp::kMultiline); 603 CASE_FOR_FLAG("unicode", JSRegExp::kUnicode); 604 CASE_FOR_FLAG("sticky", JSRegExp::kSticky); 605 #undef CASE_FOR_FLAG 606 } 607 608 // Allocate a string of the required length and fill it with the corresponding 609 // char for each set flag. 610 611 { 612 Node* const result = AllocateSeqOneByteString(context, var_length.value()); 613 Node* const flags_intptr = var_flags.value(); 614 615 Variable var_offset( 616 this, MachineType::PointerRepresentation(), 617 IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag)); 618 619 #define CASE_FOR_FLAG(FLAG, CHAR) \ 620 do { \ 621 Label next(this); \ 622 GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \ 623 Node* const value = Int32Constant(CHAR); \ 624 StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \ 625 var_offset.value(), value); \ 626 var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \ 627 Goto(&next); \ 628 Bind(&next); \ 629 } while (false) 630 631 CASE_FOR_FLAG(JSRegExp::kGlobal, 'g'); 632 CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i'); 633 CASE_FOR_FLAG(JSRegExp::kMultiline, 'm'); 634 CASE_FOR_FLAG(JSRegExp::kUnicode, 'u'); 635 CASE_FOR_FLAG(JSRegExp::kSticky, 'y'); 636 #undef CASE_FOR_FLAG 637 638 return result; 639 } 640 } 641 642 // ES#sec-isregexp IsRegExp ( argument ) 643 Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context, 644 Node* const maybe_receiver) { 645 Label out(this), if_isregexp(this); 646 647 Variable var_result(this, MachineRepresentation::kWord32, Int32Constant(0)); 648 649 GotoIf(TaggedIsSmi(maybe_receiver), &out); 650 GotoIfNot(IsJSReceiver(maybe_receiver), &out); 651 652 Node* const receiver = maybe_receiver; 653 654 // Check @@match. 655 { 656 Callable getproperty_callable = CodeFactory::GetProperty(isolate()); 657 Node* const name = HeapConstant(isolate()->factory()->match_symbol()); 658 Node* const value = CallStub(getproperty_callable, context, receiver, name); 659 660 Label match_isundefined(this), match_isnotundefined(this); 661 Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined); 662 663 Bind(&match_isundefined); 664 Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isregexp, &out); 665 666 Bind(&match_isnotundefined); 667 BranchIfToBooleanIsTrue(value, &if_isregexp, &out); 668 } 669 670 Bind(&if_isregexp); 671 var_result.Bind(Int32Constant(1)); 672 Goto(&out); 673 674 Bind(&out); 675 return var_result.value(); 676 } 677 678 // ES#sec-regexpinitialize 679 // Runtime Semantics: RegExpInitialize ( obj, pattern, flags ) 680 Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context, 681 Node* const regexp, 682 Node* const maybe_pattern, 683 Node* const maybe_flags) { 684 // Normalize pattern. 685 Node* const pattern = 686 Select(IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); }, 687 [=] { return ToString(context, maybe_pattern); }, 688 MachineRepresentation::kTagged); 689 690 // Normalize flags. 691 Node* const flags = 692 Select(IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); }, 693 [=] { return ToString(context, maybe_flags); }, 694 MachineRepresentation::kTagged); 695 696 // Initialize. 697 698 return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, 699 pattern, flags); 700 } 701 702 TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { 703 Node* const maybe_receiver = Parameter(0); 704 Node* const context = Parameter(3); 705 706 Node* const map = ThrowIfNotJSReceiver(context, maybe_receiver, 707 MessageTemplate::kRegExpNonObject, 708 "RegExp.prototype.flags"); 709 Node* const receiver = maybe_receiver; 710 711 Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); 712 Branch(IsInitialRegExpMap(context, receiver, map), &if_isfastpath, 713 &if_isslowpath); 714 715 Bind(&if_isfastpath); 716 Return(FlagsGetter(context, receiver, true)); 717 718 Bind(&if_isslowpath); 719 Return(FlagsGetter(context, receiver, false)); 720 } 721 722 // ES#sec-regexp-pattern-flags 723 // RegExp ( pattern, flags ) 724 TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) { 725 Node* const pattern = Parameter(1); 726 Node* const flags = Parameter(2); 727 Node* const new_target = Parameter(3); 728 Node* const context = Parameter(5); 729 730 Isolate* isolate = this->isolate(); 731 732 Variable var_flags(this, MachineRepresentation::kTagged, flags); 733 Variable var_pattern(this, MachineRepresentation::kTagged, pattern); 734 Variable var_new_target(this, MachineRepresentation::kTagged, new_target); 735 736 Node* const native_context = LoadNativeContext(context); 737 Node* const regexp_function = 738 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 739 740 Node* const pattern_is_regexp = IsRegExp(context, pattern); 741 742 { 743 Label next(this); 744 745 GotoIfNot(IsUndefined(new_target), &next); 746 var_new_target.Bind(regexp_function); 747 748 GotoIfNot(pattern_is_regexp, &next); 749 GotoIfNot(IsUndefined(flags), &next); 750 751 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 752 Node* const name = HeapConstant(isolate->factory()->constructor_string()); 753 Node* const value = CallStub(getproperty_callable, context, pattern, name); 754 755 GotoIfNot(WordEqual(value, regexp_function), &next); 756 Return(pattern); 757 758 Bind(&next); 759 } 760 761 { 762 Label next(this), if_patternisfastregexp(this), 763 if_patternisslowregexp(this); 764 GotoIf(TaggedIsSmi(pattern), &next); 765 766 GotoIf(HasInstanceType(pattern, JS_REGEXP_TYPE), &if_patternisfastregexp); 767 768 Branch(pattern_is_regexp, &if_patternisslowregexp, &next); 769 770 Bind(&if_patternisfastregexp); 771 { 772 Node* const source = LoadObjectField(pattern, JSRegExp::kSourceOffset); 773 var_pattern.Bind(source); 774 775 { 776 Label inner_next(this); 777 GotoIfNot(IsUndefined(flags), &inner_next); 778 779 Node* const value = FlagsGetter(context, pattern, true); 780 var_flags.Bind(value); 781 Goto(&inner_next); 782 783 Bind(&inner_next); 784 } 785 786 Goto(&next); 787 } 788 789 Bind(&if_patternisslowregexp); 790 { 791 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 792 793 { 794 Node* const name = HeapConstant(isolate->factory()->source_string()); 795 Node* const value = 796 CallStub(getproperty_callable, context, pattern, name); 797 var_pattern.Bind(value); 798 } 799 800 { 801 Label inner_next(this); 802 GotoIfNot(IsUndefined(flags), &inner_next); 803 804 Node* const name = HeapConstant(isolate->factory()->flags_string()); 805 Node* const value = 806 CallStub(getproperty_callable, context, pattern, name); 807 var_flags.Bind(value); 808 Goto(&inner_next); 809 810 Bind(&inner_next); 811 } 812 813 Goto(&next); 814 } 815 816 Bind(&next); 817 } 818 819 // Allocate. 820 821 Variable var_regexp(this, MachineRepresentation::kTagged); 822 { 823 Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred), 824 next(this); 825 Branch(WordEqual(var_new_target.value(), regexp_function), 826 &allocate_jsregexp, &allocate_generic); 827 828 Bind(&allocate_jsregexp); 829 { 830 Node* const initial_map = LoadObjectField( 831 regexp_function, JSFunction::kPrototypeOrInitialMapOffset); 832 Node* const regexp = AllocateJSObjectFromMap(initial_map); 833 var_regexp.Bind(regexp); 834 Goto(&next); 835 } 836 837 Bind(&allocate_generic); 838 { 839 ConstructorBuiltinsAssembler constructor_assembler(this->state()); 840 Node* const regexp = constructor_assembler.EmitFastNewObject( 841 context, regexp_function, var_new_target.value()); 842 var_regexp.Bind(regexp); 843 Goto(&next); 844 } 845 846 Bind(&next); 847 } 848 849 Node* const result = RegExpInitialize(context, var_regexp.value(), 850 var_pattern.value(), var_flags.value()); 851 Return(result); 852 } 853 854 // ES#sec-regexp.prototype.compile 855 // RegExp.prototype.compile ( pattern, flags ) 856 TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) { 857 Node* const maybe_receiver = Parameter(0); 858 Node* const maybe_pattern = Parameter(1); 859 Node* const maybe_flags = Parameter(2); 860 Node* const context = Parameter(5); 861 862 ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, 863 "RegExp.prototype.compile"); 864 Node* const receiver = maybe_receiver; 865 866 Variable var_flags(this, MachineRepresentation::kTagged, maybe_flags); 867 Variable var_pattern(this, MachineRepresentation::kTagged, maybe_pattern); 868 869 // Handle a JSRegExp pattern. 870 { 871 Label next(this); 872 873 GotoIf(TaggedIsSmi(maybe_pattern), &next); 874 GotoIfNot(HasInstanceType(maybe_pattern, JS_REGEXP_TYPE), &next); 875 876 Node* const pattern = maybe_pattern; 877 878 // {maybe_flags} must be undefined in this case, otherwise throw. 879 { 880 Label next(this); 881 GotoIf(IsUndefined(maybe_flags), &next); 882 883 Node* const message_id = SmiConstant(MessageTemplate::kRegExpFlags); 884 TailCallRuntime(Runtime::kThrowTypeError, context, message_id); 885 886 Bind(&next); 887 } 888 889 Node* const new_flags = FlagsGetter(context, pattern, true); 890 Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset); 891 892 var_flags.Bind(new_flags); 893 var_pattern.Bind(new_pattern); 894 895 Goto(&next); 896 Bind(&next); 897 } 898 899 Node* const result = RegExpInitialize(context, receiver, var_pattern.value(), 900 var_flags.value()); 901 Return(result); 902 } 903 904 // ES6 21.2.5.10. 905 TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) { 906 Node* const receiver = Parameter(0); 907 Node* const context = Parameter(3); 908 909 // Check whether we have an unmodified regexp instance. 910 Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred); 911 912 GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp); 913 Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isjsregexp, 914 &if_isnotjsregexp); 915 916 Bind(&if_isjsregexp); 917 { 918 Node* const source = LoadObjectField(receiver, JSRegExp::kSourceOffset); 919 Return(source); 920 } 921 922 Bind(&if_isnotjsregexp); 923 { 924 Isolate* isolate = this->isolate(); 925 Node* const native_context = LoadNativeContext(context); 926 Node* const regexp_fun = 927 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 928 Node* const initial_map = 929 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 930 Node* const initial_prototype = LoadMapPrototype(initial_map); 931 932 Label if_isprototype(this), if_isnotprototype(this); 933 Branch(WordEqual(receiver, initial_prototype), &if_isprototype, 934 &if_isnotprototype); 935 936 Bind(&if_isprototype); 937 { 938 const int counter = v8::Isolate::kRegExpPrototypeSourceGetter; 939 Node* const counter_smi = SmiConstant(counter); 940 CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); 941 942 Node* const result = 943 HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)")); 944 Return(result); 945 } 946 947 Bind(&if_isnotprototype); 948 { 949 Node* const message_id = 950 SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); 951 Node* const method_name_str = 952 HeapConstant(isolate->factory()->NewStringFromAsciiChecked( 953 "RegExp.prototype.source")); 954 TailCallRuntime(Runtime::kThrowTypeError, context, message_id, 955 method_name_str); 956 } 957 } 958 } 959 960 BUILTIN(RegExpPrototypeToString) { 961 HandleScope scope(isolate); 962 CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.toString"); 963 964 if (*recv == isolate->regexp_function()->prototype()) { 965 isolate->CountUsage(v8::Isolate::kRegExpPrototypeToString); 966 } 967 968 IncrementalStringBuilder builder(isolate); 969 970 builder.AppendCharacter('/'); 971 { 972 Handle<Object> source; 973 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 974 isolate, source, 975 JSReceiver::GetProperty(recv, isolate->factory()->source_string())); 976 Handle<String> source_str; 977 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, source_str, 978 Object::ToString(isolate, source)); 979 builder.AppendString(source_str); 980 } 981 982 builder.AppendCharacter('/'); 983 { 984 Handle<Object> flags; 985 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 986 isolate, flags, 987 JSReceiver::GetProperty(recv, isolate->factory()->flags_string())); 988 Handle<String> flags_str; 989 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags_str, 990 Object::ToString(isolate, flags)); 991 builder.AppendString(flags_str); 992 } 993 994 RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); 995 } 996 997 // Fast-path implementation for flag checks on an unmodified JSRegExp instance. 998 Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp, 999 JSRegExp::Flag flag) { 1000 Node* const smi_zero = SmiConstant(Smi::kZero); 1001 Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); 1002 Node* const mask = SmiConstant(Smi::FromInt(flag)); 1003 Node* const is_flag_set = WordNotEqual(SmiAnd(flags, mask), smi_zero); 1004 1005 return is_flag_set; 1006 } 1007 1008 // Load through the GetProperty stub. 1009 Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context, 1010 Node* const regexp, 1011 JSRegExp::Flag flag) { 1012 Factory* factory = isolate()->factory(); 1013 1014 Label out(this); 1015 Variable var_result(this, MachineRepresentation::kWord32); 1016 1017 Node* name; 1018 1019 switch (flag) { 1020 case JSRegExp::kGlobal: 1021 name = HeapConstant(factory->global_string()); 1022 break; 1023 case JSRegExp::kIgnoreCase: 1024 name = HeapConstant(factory->ignoreCase_string()); 1025 break; 1026 case JSRegExp::kMultiline: 1027 name = HeapConstant(factory->multiline_string()); 1028 break; 1029 case JSRegExp::kSticky: 1030 name = HeapConstant(factory->sticky_string()); 1031 break; 1032 case JSRegExp::kUnicode: 1033 name = HeapConstant(factory->unicode_string()); 1034 break; 1035 default: 1036 UNREACHABLE(); 1037 } 1038 1039 Callable getproperty_callable = CodeFactory::GetProperty(isolate()); 1040 Node* const value = CallStub(getproperty_callable, context, regexp, name); 1041 1042 Label if_true(this), if_false(this); 1043 BranchIfToBooleanIsTrue(value, &if_true, &if_false); 1044 1045 Bind(&if_true); 1046 { 1047 var_result.Bind(Int32Constant(1)); 1048 Goto(&out); 1049 } 1050 1051 Bind(&if_false); 1052 { 1053 var_result.Bind(Int32Constant(0)); 1054 Goto(&out); 1055 } 1056 1057 Bind(&out); 1058 return var_result.value(); 1059 } 1060 1061 Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context, 1062 Node* const regexp, 1063 JSRegExp::Flag flag, 1064 bool is_fastpath) { 1065 return is_fastpath ? FastFlagGetter(regexp, flag) 1066 : SlowFlagGetter(context, regexp, flag); 1067 } 1068 1069 void RegExpBuiltinsAssembler::FlagGetter(JSRegExp::Flag flag, 1070 v8::Isolate::UseCounterFeature counter, 1071 const char* method_name) { 1072 Node* const receiver = Parameter(0); 1073 Node* const context = Parameter(3); 1074 1075 Isolate* isolate = this->isolate(); 1076 1077 // Check whether we have an unmodified regexp instance. 1078 Label if_isunmodifiedjsregexp(this), 1079 if_isnotunmodifiedjsregexp(this, Label::kDeferred); 1080 1081 GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp); 1082 1083 Node* const receiver_map = LoadMap(receiver); 1084 Node* const instance_type = LoadMapInstanceType(receiver_map); 1085 1086 Branch(Word32Equal(instance_type, Int32Constant(JS_REGEXP_TYPE)), 1087 &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp); 1088 1089 Bind(&if_isunmodifiedjsregexp); 1090 { 1091 // Refer to JSRegExp's flag property on the fast-path. 1092 Node* const is_flag_set = FastFlagGetter(receiver, flag); 1093 Return(SelectBooleanConstant(is_flag_set)); 1094 } 1095 1096 Bind(&if_isnotunmodifiedjsregexp); 1097 { 1098 Node* const native_context = LoadNativeContext(context); 1099 Node* const regexp_fun = 1100 LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); 1101 Node* const initial_map = 1102 LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); 1103 Node* const initial_prototype = LoadMapPrototype(initial_map); 1104 1105 Label if_isprototype(this), if_isnotprototype(this); 1106 Branch(WordEqual(receiver, initial_prototype), &if_isprototype, 1107 &if_isnotprototype); 1108 1109 Bind(&if_isprototype); 1110 { 1111 Node* const counter_smi = SmiConstant(Smi::FromInt(counter)); 1112 CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); 1113 Return(UndefinedConstant()); 1114 } 1115 1116 Bind(&if_isnotprototype); 1117 { 1118 Node* const message_id = 1119 SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); 1120 Node* const method_name_str = HeapConstant( 1121 isolate->factory()->NewStringFromAsciiChecked(method_name)); 1122 CallRuntime(Runtime::kThrowTypeError, context, message_id, 1123 method_name_str); 1124 Unreachable(); 1125 } 1126 } 1127 } 1128 1129 // ES6 21.2.5.4. 1130 TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) { 1131 FlagGetter(JSRegExp::kGlobal, v8::Isolate::kRegExpPrototypeOldFlagGetter, 1132 "RegExp.prototype.global"); 1133 } 1134 1135 // ES6 21.2.5.5. 1136 TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) { 1137 FlagGetter(JSRegExp::kIgnoreCase, v8::Isolate::kRegExpPrototypeOldFlagGetter, 1138 "RegExp.prototype.ignoreCase"); 1139 } 1140 1141 // ES6 21.2.5.7. 1142 TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) { 1143 FlagGetter(JSRegExp::kMultiline, v8::Isolate::kRegExpPrototypeOldFlagGetter, 1144 "RegExp.prototype.multiline"); 1145 } 1146 1147 // ES6 21.2.5.12. 1148 TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) { 1149 FlagGetter(JSRegExp::kSticky, v8::Isolate::kRegExpPrototypeStickyGetter, 1150 "RegExp.prototype.sticky"); 1151 } 1152 1153 // ES6 21.2.5.15. 1154 TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { 1155 FlagGetter(JSRegExp::kUnicode, v8::Isolate::kRegExpPrototypeUnicodeGetter, 1156 "RegExp.prototype.unicode"); 1157 } 1158 1159 // The properties $1..$9 are the first nine capturing substrings of the last 1160 // successful match, or ''. The function RegExpMakeCaptureGetter will be 1161 // called with indices from 1 to 9. 1162 #define DEFINE_CAPTURE_GETTER(i) \ 1163 BUILTIN(RegExpCapture##i##Getter) { \ 1164 HandleScope scope(isolate); \ 1165 return *RegExpUtils::GenericCaptureGetter( \ 1166 isolate, isolate->regexp_last_match_info(), i); \ 1167 } 1168 DEFINE_CAPTURE_GETTER(1) 1169 DEFINE_CAPTURE_GETTER(2) 1170 DEFINE_CAPTURE_GETTER(3) 1171 DEFINE_CAPTURE_GETTER(4) 1172 DEFINE_CAPTURE_GETTER(5) 1173 DEFINE_CAPTURE_GETTER(6) 1174 DEFINE_CAPTURE_GETTER(7) 1175 DEFINE_CAPTURE_GETTER(8) 1176 DEFINE_CAPTURE_GETTER(9) 1177 #undef DEFINE_CAPTURE_GETTER 1178 1179 // The properties `input` and `$_` are aliases for each other. When this 1180 // value is set, the value it is set to is coerced to a string. 1181 // Getter and setter for the input. 1182 1183 BUILTIN(RegExpInputGetter) { 1184 HandleScope scope(isolate); 1185 Handle<Object> obj(isolate->regexp_last_match_info()->LastInput(), isolate); 1186 return obj->IsUndefined(isolate) ? isolate->heap()->empty_string() 1187 : String::cast(*obj); 1188 } 1189 1190 BUILTIN(RegExpInputSetter) { 1191 HandleScope scope(isolate); 1192 Handle<Object> value = args.atOrUndefined(isolate, 1); 1193 Handle<String> str; 1194 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str, 1195 Object::ToString(isolate, value)); 1196 isolate->regexp_last_match_info()->SetLastInput(*str); 1197 return isolate->heap()->undefined_value(); 1198 } 1199 1200 // Getters for the static properties lastMatch, lastParen, leftContext, and 1201 // rightContext of the RegExp constructor. The properties are computed based 1202 // on the captures array of the last successful match and the subject string 1203 // of the last successful match. 1204 BUILTIN(RegExpLastMatchGetter) { 1205 HandleScope scope(isolate); 1206 return *RegExpUtils::GenericCaptureGetter( 1207 isolate, isolate->regexp_last_match_info(), 0); 1208 } 1209 1210 BUILTIN(RegExpLastParenGetter) { 1211 HandleScope scope(isolate); 1212 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); 1213 const int length = match_info->NumberOfCaptureRegisters(); 1214 if (length <= 2) return isolate->heap()->empty_string(); // No captures. 1215 1216 DCHECK_EQ(0, length % 2); 1217 const int last_capture = (length / 2) - 1; 1218 1219 // We match the SpiderMonkey behavior: return the substring defined by the 1220 // last pair (after the first pair) of elements of the capture array even if 1221 // it is empty. 1222 return *RegExpUtils::GenericCaptureGetter(isolate, match_info, last_capture); 1223 } 1224 1225 BUILTIN(RegExpLeftContextGetter) { 1226 HandleScope scope(isolate); 1227 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); 1228 const int start_index = match_info->Capture(0); 1229 Handle<String> last_subject(match_info->LastSubject()); 1230 return *isolate->factory()->NewSubString(last_subject, 0, start_index); 1231 } 1232 1233 BUILTIN(RegExpRightContextGetter) { 1234 HandleScope scope(isolate); 1235 Handle<RegExpMatchInfo> match_info = isolate->regexp_last_match_info(); 1236 const int start_index = match_info->Capture(1); 1237 Handle<String> last_subject(match_info->LastSubject()); 1238 const int len = last_subject->length(); 1239 return *isolate->factory()->NewSubString(last_subject, start_index, len); 1240 } 1241 1242 // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) 1243 Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, 1244 Node* string) { 1245 Isolate* isolate = this->isolate(); 1246 1247 Node* const null = NullConstant(); 1248 1249 Variable var_result(this, MachineRepresentation::kTagged); 1250 Label out(this), if_isfastpath(this), if_isslowpath(this); 1251 1252 Node* const map = LoadMap(regexp); 1253 BranchIfFastRegExp(context, regexp, map, &if_isfastpath, &if_isslowpath); 1254 1255 Bind(&if_isfastpath); 1256 { 1257 Node* const result = RegExpPrototypeExecBody(context, regexp, string, true); 1258 var_result.Bind(result); 1259 Goto(&out); 1260 } 1261 1262 Bind(&if_isslowpath); 1263 { 1264 // Take the slow path of fetching the exec property, calling it, and 1265 // verifying its return value. 1266 1267 // Get the exec property. 1268 Node* const name = HeapConstant(isolate->factory()->exec_string()); 1269 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 1270 Node* const exec = CallStub(getproperty_callable, context, regexp, name); 1271 1272 // Is {exec} callable? 1273 Label if_iscallable(this), if_isnotcallable(this); 1274 1275 GotoIf(TaggedIsSmi(exec), &if_isnotcallable); 1276 1277 Node* const exec_map = LoadMap(exec); 1278 Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable); 1279 1280 Bind(&if_iscallable); 1281 { 1282 Callable call_callable = CodeFactory::Call(isolate); 1283 Node* const result = CallJS(call_callable, context, exec, regexp, string); 1284 1285 var_result.Bind(result); 1286 GotoIf(WordEqual(result, null), &out); 1287 1288 ThrowIfNotJSReceiver(context, result, 1289 MessageTemplate::kInvalidRegExpExecResult, "unused"); 1290 1291 Goto(&out); 1292 } 1293 1294 Bind(&if_isnotcallable); 1295 { 1296 ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, 1297 "RegExp.prototype.exec"); 1298 1299 Node* const result = 1300 RegExpPrototypeExecBody(context, regexp, string, false); 1301 var_result.Bind(result); 1302 Goto(&out); 1303 } 1304 } 1305 1306 Bind(&out); 1307 return var_result.value(); 1308 } 1309 1310 // ES#sec-regexp.prototype.test 1311 // RegExp.prototype.test ( S ) 1312 TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) { 1313 Node* const maybe_receiver = Parameter(0); 1314 Node* const maybe_string = Parameter(1); 1315 Node* const context = Parameter(4); 1316 1317 // Ensure {maybe_receiver} is a JSReceiver. 1318 ThrowIfNotJSReceiver(context, maybe_receiver, 1319 MessageTemplate::kIncompatibleMethodReceiver, 1320 "RegExp.prototype.test"); 1321 Node* const receiver = maybe_receiver; 1322 1323 // Convert {maybe_string} to a String. 1324 Node* const string = ToString(context, maybe_string); 1325 1326 Label fast_path(this), slow_path(this); 1327 BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path, 1328 &slow_path); 1329 1330 Bind(&fast_path); 1331 { 1332 Label if_didnotmatch(this); 1333 RegExpPrototypeExecBodyWithoutResult(context, receiver, string, 1334 &if_didnotmatch, true); 1335 Return(TrueConstant()); 1336 1337 Bind(&if_didnotmatch); 1338 Return(FalseConstant()); 1339 } 1340 1341 Bind(&slow_path); 1342 { 1343 // Call exec. 1344 Node* const match_indices = RegExpExec(context, receiver, string); 1345 1346 // Return true iff exec matched successfully. 1347 Node* const result = 1348 SelectBooleanConstant(WordNotEqual(match_indices, NullConstant())); 1349 Return(result); 1350 } 1351 } 1352 1353 Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string, 1354 Node* const index, 1355 Node* const is_unicode, 1356 bool is_fastpath) { 1357 CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(index))); 1358 if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index)); 1359 1360 // Default to last_index + 1. 1361 Node* const index_plus_one = NumberInc(index); 1362 Variable var_result(this, MachineRepresentation::kTagged, index_plus_one); 1363 1364 // Advancing the index has some subtle issues involving the distinction 1365 // between Smis and HeapNumbers. There's three cases: 1366 // * {index} is a Smi, {index_plus_one} is a Smi. The standard case. 1367 // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber. 1368 // In this case we can return the result early, because 1369 // {index_plus_one} > {string}.length. 1370 // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only 1371 // occur when {index} is outside the Smi range since we normalize 1372 // explicitly. Again we can return early. 1373 if (is_fastpath) { 1374 // Must be in Smi range on the fast path. We control the value of {index} 1375 // on all call-sites and can never exceed the length of the string. 1376 STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue); 1377 CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one)); 1378 } 1379 1380 Label if_isunicode(this), out(this); 1381 GotoIfNot(is_unicode, &out); 1382 1383 // Keep this unconditional (even on the fast path) just to be safe. 1384 Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out); 1385 1386 Bind(&if_isunicode); 1387 { 1388 Node* const string_length = LoadStringLength(string); 1389 GotoIfNot(SmiLessThan(index_plus_one, string_length), &out); 1390 1391 Node* const lead = StringCharCodeAt(string, index); 1392 GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)), 1393 Int32Constant(0xD800)), 1394 &out); 1395 1396 Node* const trail = StringCharCodeAt(string, index_plus_one); 1397 GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)), 1398 Int32Constant(0xDC00)), 1399 &out); 1400 1401 // At a surrogate pair, return index + 2. 1402 Node* const index_plus_two = NumberInc(index_plus_one); 1403 var_result.Bind(index_plus_two); 1404 1405 Goto(&out); 1406 } 1407 1408 Bind(&out); 1409 return var_result.value(); 1410 } 1411 1412 namespace { 1413 1414 // Utility class implementing a growable fixed array through CSA. 1415 class GrowableFixedArray { 1416 typedef CodeStubAssembler::Label Label; 1417 typedef CodeStubAssembler::Variable Variable; 1418 1419 public: 1420 explicit GrowableFixedArray(CodeStubAssembler* a) 1421 : assembler_(a), 1422 var_array_(a, MachineRepresentation::kTagged), 1423 var_length_(a, MachineType::PointerRepresentation()), 1424 var_capacity_(a, MachineType::PointerRepresentation()) { 1425 Initialize(); 1426 } 1427 1428 Node* length() const { return var_length_.value(); } 1429 1430 Variable* var_array() { return &var_array_; } 1431 Variable* var_length() { return &var_length_; } 1432 Variable* var_capacity() { return &var_capacity_; } 1433 1434 void Push(Node* const value) { 1435 CodeStubAssembler* a = assembler_; 1436 1437 Node* const length = var_length_.value(); 1438 Node* const capacity = var_capacity_.value(); 1439 1440 Label grow(a), store(a); 1441 a->Branch(a->IntPtrEqual(capacity, length), &grow, &store); 1442 1443 a->Bind(&grow); 1444 { 1445 Node* const new_capacity = NewCapacity(a, capacity); 1446 Node* const new_array = ResizeFixedArray(length, new_capacity); 1447 1448 var_capacity_.Bind(new_capacity); 1449 var_array_.Bind(new_array); 1450 a->Goto(&store); 1451 } 1452 1453 a->Bind(&store); 1454 { 1455 Node* const array = var_array_.value(); 1456 a->StoreFixedArrayElement(array, length, value); 1457 1458 Node* const new_length = a->IntPtrAdd(length, a->IntPtrConstant(1)); 1459 var_length_.Bind(new_length); 1460 } 1461 } 1462 1463 Node* ToJSArray(Node* const context) { 1464 CodeStubAssembler* a = assembler_; 1465 1466 const ElementsKind kind = FAST_ELEMENTS; 1467 1468 Node* const native_context = a->LoadNativeContext(context); 1469 Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context); 1470 1471 // Shrink to fit if necessary. 1472 { 1473 Label next(a); 1474 1475 Node* const length = var_length_.value(); 1476 Node* const capacity = var_capacity_.value(); 1477 1478 a->GotoIf(a->WordEqual(length, capacity), &next); 1479 1480 Node* const array = ResizeFixedArray(length, length); 1481 var_array_.Bind(array); 1482 var_capacity_.Bind(length); 1483 a->Goto(&next); 1484 1485 a->Bind(&next); 1486 } 1487 1488 Node* const result_length = a->SmiTag(length()); 1489 Node* const result = a->AllocateUninitializedJSArrayWithoutElements( 1490 kind, array_map, result_length, nullptr); 1491 1492 // Note: We do not currently shrink the fixed array. 1493 1494 a->StoreObjectField(result, JSObject::kElementsOffset, var_array_.value()); 1495 1496 return result; 1497 } 1498 1499 private: 1500 void Initialize() { 1501 CodeStubAssembler* a = assembler_; 1502 1503 const ElementsKind kind = FAST_ELEMENTS; 1504 1505 static const int kInitialArraySize = 8; 1506 Node* const capacity = a->IntPtrConstant(kInitialArraySize); 1507 Node* const array = a->AllocateFixedArray(kind, capacity); 1508 1509 a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity, 1510 Heap::kTheHoleValueRootIndex); 1511 1512 var_array_.Bind(array); 1513 var_capacity_.Bind(capacity); 1514 var_length_.Bind(a->IntPtrConstant(0)); 1515 } 1516 1517 Node* NewCapacity(CodeStubAssembler* a, Node* const current_capacity) { 1518 CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0))); 1519 1520 // Growth rate is analog to JSObject::NewElementsCapacity: 1521 // new_capacity = (current_capacity + (current_capacity >> 1)) + 16. 1522 1523 Node* const new_capacity = a->IntPtrAdd( 1524 a->IntPtrAdd(current_capacity, a->WordShr(current_capacity, 1)), 1525 a->IntPtrConstant(16)); 1526 1527 return new_capacity; 1528 } 1529 1530 // Creates a new array with {new_capacity} and copies the first 1531 // {element_count} elements from the current array. 1532 Node* ResizeFixedArray(Node* const element_count, Node* const new_capacity) { 1533 CodeStubAssembler* a = assembler_; 1534 1535 CSA_ASSERT(a, a->IntPtrGreaterThan(element_count, a->IntPtrConstant(0))); 1536 CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, a->IntPtrConstant(0))); 1537 CSA_ASSERT(a, a->IntPtrGreaterThanOrEqual(new_capacity, element_count)); 1538 1539 const ElementsKind kind = FAST_ELEMENTS; 1540 const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; 1541 const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; 1542 const CodeStubAssembler::AllocationFlags flags = 1543 CodeStubAssembler::kAllowLargeObjectAllocation; 1544 1545 Node* const from_array = var_array_.value(); 1546 Node* const to_array = 1547 a->AllocateFixedArray(kind, new_capacity, mode, flags); 1548 a->CopyFixedArrayElements(kind, from_array, kind, to_array, element_count, 1549 new_capacity, barrier_mode, mode); 1550 1551 return to_array; 1552 } 1553 1554 private: 1555 CodeStubAssembler* const assembler_; 1556 Variable var_array_; 1557 Variable var_length_; 1558 Variable var_capacity_; 1559 }; 1560 1561 } // namespace 1562 1563 void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, 1564 Node* const regexp, 1565 Node* const string, 1566 const bool is_fastpath) { 1567 Isolate* const isolate = this->isolate(); 1568 1569 Node* const null = NullConstant(); 1570 Node* const int_zero = IntPtrConstant(0); 1571 Node* const smi_zero = SmiConstant(Smi::kZero); 1572 1573 Node* const is_global = 1574 FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath); 1575 1576 Label if_isglobal(this), if_isnotglobal(this); 1577 Branch(is_global, &if_isglobal, &if_isnotglobal); 1578 1579 Bind(&if_isnotglobal); 1580 { 1581 Node* const result = 1582 is_fastpath ? RegExpPrototypeExecBody(context, regexp, string, true) 1583 : RegExpExec(context, regexp, string); 1584 Return(result); 1585 } 1586 1587 Bind(&if_isglobal); 1588 { 1589 Node* const is_unicode = 1590 FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath); 1591 1592 StoreLastIndex(context, regexp, smi_zero, is_fastpath); 1593 1594 // Allocate an array to store the resulting match strings. 1595 1596 GrowableFixedArray array(this); 1597 1598 // Loop preparations. Within the loop, collect results from RegExpExec 1599 // and store match strings in the array. 1600 1601 Variable* vars[] = {array.var_array(), array.var_length(), 1602 array.var_capacity()}; 1603 Label loop(this, 3, vars), out(this); 1604 Goto(&loop); 1605 1606 Bind(&loop); 1607 { 1608 Variable var_match(this, MachineRepresentation::kTagged); 1609 1610 Label if_didmatch(this), if_didnotmatch(this); 1611 if (is_fastpath) { 1612 // On the fast path, grab the matching string from the raw match index 1613 // array. 1614 Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( 1615 context, regexp, string, &if_didnotmatch, true); 1616 1617 Node* const match_from = LoadFixedArrayElement( 1618 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 1619 Node* const match_to = LoadFixedArrayElement( 1620 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); 1621 1622 Node* match = SubString(context, string, match_from, match_to); 1623 var_match.Bind(match); 1624 1625 Goto(&if_didmatch); 1626 } else { 1627 DCHECK(!is_fastpath); 1628 Node* const result = RegExpExec(context, regexp, string); 1629 1630 Label load_match(this); 1631 Branch(WordEqual(result, null), &if_didnotmatch, &load_match); 1632 1633 Bind(&load_match); 1634 { 1635 Label fast_result(this), slow_result(this); 1636 BranchIfFastRegExpResult(context, LoadMap(result), &fast_result, 1637 &slow_result); 1638 1639 Bind(&fast_result); 1640 { 1641 Node* const result_fixed_array = LoadElements(result); 1642 Node* const match = LoadFixedArrayElement(result_fixed_array, 0); 1643 1644 // The match is guaranteed to be a string on the fast path. 1645 CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(match))); 1646 1647 var_match.Bind(match); 1648 Goto(&if_didmatch); 1649 } 1650 1651 Bind(&slow_result); 1652 { 1653 // TODO(ishell): Use GetElement stub once it's available. 1654 Node* const name = smi_zero; 1655 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 1656 Node* const match = 1657 CallStub(getproperty_callable, context, result, name); 1658 1659 var_match.Bind(ToString(context, match)); 1660 Goto(&if_didmatch); 1661 } 1662 } 1663 } 1664 1665 Bind(&if_didnotmatch); 1666 { 1667 // Return null if there were no matches, otherwise just exit the loop. 1668 GotoIfNot(IntPtrEqual(array.length(), int_zero), &out); 1669 Return(null); 1670 } 1671 1672 Bind(&if_didmatch); 1673 { 1674 Node* match = var_match.value(); 1675 1676 // Store the match, growing the fixed array if needed. 1677 1678 array.Push(match); 1679 1680 // Advance last index if the match is the empty string. 1681 1682 Node* const match_length = LoadStringLength(match); 1683 GotoIfNot(SmiEqual(match_length, smi_zero), &loop); 1684 1685 Node* last_index = LoadLastIndex(context, regexp, is_fastpath); 1686 if (is_fastpath) { 1687 CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); 1688 } else { 1689 Callable tolength_callable = CodeFactory::ToLength(isolate); 1690 last_index = CallStub(tolength_callable, context, last_index); 1691 } 1692 1693 Node* const new_last_index = 1694 AdvanceStringIndex(string, last_index, is_unicode, is_fastpath); 1695 1696 if (is_fastpath) { 1697 // On the fast path, we can be certain that lastIndex can never be 1698 // incremented to overflow the Smi range since the maximal string 1699 // length is less than the maximal Smi value. 1700 STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue); 1701 CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index)); 1702 } 1703 1704 StoreLastIndex(context, regexp, new_last_index, is_fastpath); 1705 1706 Goto(&loop); 1707 } 1708 } 1709 1710 Bind(&out); 1711 { 1712 // Wrap the match in a JSArray. 1713 1714 Node* const result = array.ToJSArray(context); 1715 Return(result); 1716 } 1717 } 1718 } 1719 1720 // ES#sec-regexp.prototype-@@match 1721 // RegExp.prototype [ @@match ] ( string ) 1722 TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { 1723 Node* const maybe_receiver = Parameter(0); 1724 Node* const maybe_string = Parameter(1); 1725 Node* const context = Parameter(4); 1726 1727 // Ensure {maybe_receiver} is a JSReceiver. 1728 ThrowIfNotJSReceiver(context, maybe_receiver, 1729 MessageTemplate::kIncompatibleMethodReceiver, 1730 "RegExp.prototype.@@match"); 1731 Node* const receiver = maybe_receiver; 1732 1733 // Convert {maybe_string} to a String. 1734 Node* const string = ToString(context, maybe_string); 1735 1736 Label fast_path(this), slow_path(this); 1737 BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path, 1738 &slow_path); 1739 1740 Bind(&fast_path); 1741 RegExpPrototypeMatchBody(context, receiver, string, true); 1742 1743 Bind(&slow_path); 1744 RegExpPrototypeMatchBody(context, receiver, string, false); 1745 } 1746 1747 void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast( 1748 Node* const context, Node* const regexp, Node* const string) { 1749 // Grab the initial value of last index. 1750 Node* const previous_last_index = FastLoadLastIndex(regexp); 1751 1752 // Ensure last index is 0. 1753 FastStoreLastIndex(regexp, SmiConstant(Smi::kZero)); 1754 1755 // Call exec. 1756 Label if_didnotmatch(this); 1757 Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( 1758 context, regexp, string, &if_didnotmatch, true); 1759 1760 // Successful match. 1761 { 1762 // Reset last index. 1763 FastStoreLastIndex(regexp, previous_last_index); 1764 1765 // Return the index of the match. 1766 Node* const index = LoadFixedArrayElement( 1767 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 1768 Return(index); 1769 } 1770 1771 Bind(&if_didnotmatch); 1772 { 1773 // Reset last index and return -1. 1774 FastStoreLastIndex(regexp, previous_last_index); 1775 Return(SmiConstant(-1)); 1776 } 1777 } 1778 1779 void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( 1780 Node* const context, Node* const regexp, Node* const string) { 1781 Isolate* const isolate = this->isolate(); 1782 1783 Node* const smi_zero = SmiConstant(Smi::kZero); 1784 1785 // Grab the initial value of last index. 1786 Node* const previous_last_index = SlowLoadLastIndex(context, regexp); 1787 1788 // Ensure last index is 0. 1789 { 1790 Label next(this); 1791 GotoIf(SameValue(previous_last_index, smi_zero, context), &next); 1792 1793 SlowStoreLastIndex(context, regexp, smi_zero); 1794 Goto(&next); 1795 Bind(&next); 1796 } 1797 1798 // Call exec. 1799 Node* const exec_result = RegExpExec(context, regexp, string); 1800 1801 // Reset last index if necessary. 1802 { 1803 Label next(this); 1804 Node* const current_last_index = SlowLoadLastIndex(context, regexp); 1805 1806 GotoIf(SameValue(current_last_index, previous_last_index, context), &next); 1807 1808 SlowStoreLastIndex(context, regexp, previous_last_index); 1809 Goto(&next); 1810 1811 Bind(&next); 1812 } 1813 1814 // Return -1 if no match was found. 1815 { 1816 Label next(this); 1817 GotoIfNot(WordEqual(exec_result, NullConstant()), &next); 1818 Return(SmiConstant(-1)); 1819 Bind(&next); 1820 } 1821 1822 // Return the index of the match. 1823 { 1824 Label fast_result(this), slow_result(this, Label::kDeferred); 1825 BranchIfFastRegExpResult(context, LoadMap(exec_result), &fast_result, 1826 &slow_result); 1827 1828 Bind(&fast_result); 1829 { 1830 Node* const index = 1831 LoadObjectField(exec_result, JSRegExpResult::kIndexOffset); 1832 Return(index); 1833 } 1834 1835 Bind(&slow_result); 1836 { 1837 Node* const name = HeapConstant(isolate->factory()->index_string()); 1838 Callable getproperty_callable = CodeFactory::GetProperty(isolate); 1839 Node* const index = 1840 CallStub(getproperty_callable, context, exec_result, name); 1841 Return(index); 1842 } 1843 } 1844 } 1845 1846 // ES#sec-regexp.prototype-@@search 1847 // RegExp.prototype [ @@search ] ( string ) 1848 TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) { 1849 Node* const maybe_receiver = Parameter(0); 1850 Node* const maybe_string = Parameter(1); 1851 Node* const context = Parameter(4); 1852 1853 // Ensure {maybe_receiver} is a JSReceiver. 1854 ThrowIfNotJSReceiver(context, maybe_receiver, 1855 MessageTemplate::kIncompatibleMethodReceiver, 1856 "RegExp.prototype.@@search"); 1857 Node* const receiver = maybe_receiver; 1858 1859 // Convert {maybe_string} to a String. 1860 Node* const string = ToString(context, maybe_string); 1861 1862 Label fast_path(this), slow_path(this); 1863 BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path, 1864 &slow_path); 1865 1866 Bind(&fast_path); 1867 RegExpPrototypeSearchBodyFast(context, receiver, string); 1868 1869 Bind(&slow_path); 1870 RegExpPrototypeSearchBodySlow(context, receiver, string); 1871 } 1872 1873 // Generates the fast path for @@split. {regexp} is an unmodified JSRegExp, 1874 // {string} is a String, and {limit} is a Smi. 1875 void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context, 1876 Node* const regexp, 1877 Node* const string, 1878 Node* const limit) { 1879 Isolate* isolate = this->isolate(); 1880 1881 Node* const null = NullConstant(); 1882 Node* const smi_zero = SmiConstant(0); 1883 Node* const int_zero = IntPtrConstant(0); 1884 Node* const int_limit = SmiUntag(limit); 1885 1886 const ElementsKind kind = FAST_ELEMENTS; 1887 const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; 1888 1889 Node* const allocation_site = nullptr; 1890 Node* const native_context = LoadNativeContext(context); 1891 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 1892 1893 Label return_empty_array(this, Label::kDeferred); 1894 1895 // If limit is zero, return an empty array. 1896 { 1897 Label next(this), if_limitiszero(this, Label::kDeferred); 1898 Branch(SmiEqual(limit, smi_zero), &return_empty_array, &next); 1899 Bind(&next); 1900 } 1901 1902 Node* const string_length = LoadStringLength(string); 1903 1904 // If passed the empty {string}, return either an empty array or a singleton 1905 // array depending on whether the {regexp} matches. 1906 { 1907 Label next(this), if_stringisempty(this, Label::kDeferred); 1908 Branch(SmiEqual(string_length, smi_zero), &if_stringisempty, &next); 1909 1910 Bind(&if_stringisempty); 1911 { 1912 Node* const last_match_info = LoadContextElement( 1913 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 1914 1915 Callable exec_callable = CodeFactory::RegExpExec(isolate); 1916 Node* const match_indices = CallStub(exec_callable, context, regexp, 1917 string, smi_zero, last_match_info); 1918 1919 Label return_singleton_array(this); 1920 Branch(WordEqual(match_indices, null), &return_singleton_array, 1921 &return_empty_array); 1922 1923 Bind(&return_singleton_array); 1924 { 1925 Node* const length = SmiConstant(1); 1926 Node* const capacity = IntPtrConstant(1); 1927 Node* const result = AllocateJSArray(kind, array_map, capacity, length, 1928 allocation_site, mode); 1929 1930 Node* const fixed_array = LoadElements(result); 1931 StoreFixedArrayElement(fixed_array, 0, string); 1932 1933 Return(result); 1934 } 1935 } 1936 1937 Bind(&next); 1938 } 1939 1940 // Loop preparations. 1941 1942 GrowableFixedArray array(this); 1943 1944 Variable var_last_matched_until(this, MachineRepresentation::kTagged); 1945 Variable var_next_search_from(this, MachineRepresentation::kTagged); 1946 1947 var_last_matched_until.Bind(smi_zero); 1948 var_next_search_from.Bind(smi_zero); 1949 1950 Variable* vars[] = {array.var_array(), array.var_length(), 1951 array.var_capacity(), &var_last_matched_until, 1952 &var_next_search_from}; 1953 const int vars_count = sizeof(vars) / sizeof(vars[0]); 1954 Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this); 1955 Goto(&loop); 1956 1957 Bind(&loop); 1958 { 1959 Node* const next_search_from = var_next_search_from.value(); 1960 Node* const last_matched_until = var_last_matched_until.value(); 1961 1962 // We're done if we've reached the end of the string. 1963 { 1964 Label next(this); 1965 Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out, 1966 &next); 1967 Bind(&next); 1968 } 1969 1970 // Search for the given {regexp}. 1971 1972 Node* const last_match_info = LoadContextElement( 1973 native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 1974 1975 Callable exec_callable = CodeFactory::RegExpExec(isolate); 1976 Node* const match_indices = CallStub(exec_callable, context, regexp, string, 1977 next_search_from, last_match_info); 1978 1979 // We're done if no match was found. 1980 { 1981 Label next(this); 1982 Branch(WordEqual(match_indices, null), &push_suffix_and_out, &next); 1983 Bind(&next); 1984 } 1985 1986 Node* const match_from = LoadFixedArrayElement( 1987 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 1988 1989 // We're done if the match starts beyond the string. 1990 { 1991 Label next(this); 1992 Branch(WordEqual(match_from, string_length), &push_suffix_and_out, &next); 1993 Bind(&next); 1994 } 1995 1996 Node* const match_to = LoadFixedArrayElement( 1997 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); 1998 1999 // Advance index and continue if the match is empty. 2000 { 2001 Label next(this); 2002 2003 GotoIfNot(SmiEqual(match_to, next_search_from), &next); 2004 GotoIfNot(SmiEqual(match_to, last_matched_until), &next); 2005 2006 Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode); 2007 Node* const new_next_search_from = 2008 AdvanceStringIndex(string, next_search_from, is_unicode, true); 2009 var_next_search_from.Bind(new_next_search_from); 2010 Goto(&loop); 2011 2012 Bind(&next); 2013 } 2014 2015 // A valid match was found, add the new substring to the array. 2016 { 2017 Node* const from = last_matched_until; 2018 Node* const to = match_from; 2019 2020 Node* const substr = SubString(context, string, from, to); 2021 array.Push(substr); 2022 2023 GotoIf(WordEqual(array.length(), int_limit), &out); 2024 } 2025 2026 // Add all captures to the array. 2027 { 2028 Node* const num_registers = LoadFixedArrayElement( 2029 match_indices, RegExpMatchInfo::kNumberOfCapturesIndex); 2030 Node* const int_num_registers = SmiUntag(num_registers); 2031 2032 Variable var_reg(this, MachineType::PointerRepresentation()); 2033 var_reg.Bind(IntPtrConstant(2)); 2034 2035 Variable* vars[] = {array.var_array(), array.var_length(), 2036 array.var_capacity(), &var_reg}; 2037 const int vars_count = sizeof(vars) / sizeof(vars[0]); 2038 Label nested_loop(this, vars_count, vars), nested_loop_out(this); 2039 Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop, 2040 &nested_loop_out); 2041 2042 Bind(&nested_loop); 2043 { 2044 Node* const reg = var_reg.value(); 2045 Node* const from = LoadFixedArrayElement( 2046 match_indices, reg, 2047 RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode); 2048 Node* const to = LoadFixedArrayElement( 2049 match_indices, reg, 2050 (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode); 2051 2052 Label select_capture(this), select_undefined(this), store_value(this); 2053 Variable var_value(this, MachineRepresentation::kTagged); 2054 Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined, 2055 &select_capture); 2056 2057 Bind(&select_capture); 2058 { 2059 Node* const substr = SubString(context, string, from, to); 2060 var_value.Bind(substr); 2061 Goto(&store_value); 2062 } 2063 2064 Bind(&select_undefined); 2065 { 2066 Node* const undefined = UndefinedConstant(); 2067 var_value.Bind(undefined); 2068 Goto(&store_value); 2069 } 2070 2071 Bind(&store_value); 2072 { 2073 array.Push(var_value.value()); 2074 GotoIf(WordEqual(array.length(), int_limit), &out); 2075 2076 Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2)); 2077 var_reg.Bind(new_reg); 2078 2079 Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop, 2080 &nested_loop_out); 2081 } 2082 } 2083 2084 Bind(&nested_loop_out); 2085 } 2086 2087 var_last_matched_until.Bind(match_to); 2088 var_next_search_from.Bind(match_to); 2089 Goto(&loop); 2090 } 2091 2092 Bind(&push_suffix_and_out); 2093 { 2094 Node* const from = var_last_matched_until.value(); 2095 Node* const to = string_length; 2096 2097 Node* const substr = SubString(context, string, from, to); 2098 array.Push(substr); 2099 2100 Goto(&out); 2101 } 2102 2103 Bind(&out); 2104 { 2105 Node* const result = array.ToJSArray(context); 2106 Return(result); 2107 } 2108 2109 Bind(&return_empty_array); 2110 { 2111 Node* const length = smi_zero; 2112 Node* const capacity = int_zero; 2113 Node* const result = AllocateJSArray(kind, array_map, capacity, length, 2114 allocation_site, mode); 2115 Return(result); 2116 } 2117 } 2118 2119 // Helper that skips a few initial checks. 2120 TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) { 2121 typedef RegExpSplitDescriptor Descriptor; 2122 2123 Node* const regexp = Parameter(Descriptor::kReceiver); 2124 Node* const string = Parameter(Descriptor::kString); 2125 Node* const maybe_limit = Parameter(Descriptor::kLimit); 2126 Node* const context = Parameter(Descriptor::kContext); 2127 2128 CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp))); 2129 CSA_ASSERT(this, IsString(string)); 2130 2131 // TODO(jgruber): Even if map checks send us to the fast path, we still need 2132 // to verify the constructor property and jump to the slow path if it has 2133 // been changed. 2134 2135 // Convert {maybe_limit} to a uint32, capping at the maximal smi value. 2136 Variable var_limit(this, MachineRepresentation::kTagged, maybe_limit); 2137 Label if_limitissmimax(this), limit_done(this), runtime(this); 2138 2139 GotoIf(IsUndefined(maybe_limit), &if_limitissmimax); 2140 GotoIf(TaggedIsPositiveSmi(maybe_limit), &limit_done); 2141 2142 Node* const limit = ToUint32(context, maybe_limit); 2143 { 2144 // ToUint32(limit) could potentially change the shape of the RegExp 2145 // object. Recheck that we are still on the fast path and bail to runtime 2146 // otherwise. 2147 { 2148 Label next(this); 2149 BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime); 2150 Bind(&next); 2151 } 2152 2153 GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax); 2154 2155 var_limit.Bind(limit); 2156 Goto(&limit_done); 2157 } 2158 2159 Bind(&if_limitissmimax); 2160 { 2161 // TODO(jgruber): In this case, we can probably avoid generation of limit 2162 // checks in Generate_RegExpPrototypeSplitBody. 2163 var_limit.Bind(SmiConstant(Smi::kMaxValue)); 2164 Goto(&limit_done); 2165 } 2166 2167 Bind(&limit_done); 2168 { 2169 Node* const limit = var_limit.value(); 2170 RegExpPrototypeSplitBody(context, regexp, string, limit); 2171 } 2172 2173 Bind(&runtime); 2174 { 2175 // The runtime call passes in limit to ensure the second ToUint32(limit) 2176 // call is not observable. 2177 CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(limit))); 2178 Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, limit)); 2179 } 2180 } 2181 2182 // ES#sec-regexp.prototype-@@split 2183 // RegExp.prototype [ @@split ] ( string, limit ) 2184 TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { 2185 Node* const maybe_receiver = Parameter(0); 2186 Node* const maybe_string = Parameter(1); 2187 Node* const maybe_limit = Parameter(2); 2188 Node* const context = Parameter(5); 2189 2190 // Ensure {maybe_receiver} is a JSReceiver. 2191 ThrowIfNotJSReceiver(context, maybe_receiver, 2192 MessageTemplate::kIncompatibleMethodReceiver, 2193 "RegExp.prototype.@@split"); 2194 Node* const receiver = maybe_receiver; 2195 2196 // Convert {maybe_string} to a String. 2197 Node* const string = ToString(context, maybe_string); 2198 2199 Label stub(this), runtime(this, Label::kDeferred); 2200 BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime); 2201 2202 Bind(&stub); 2203 Callable split_callable = CodeFactory::RegExpSplit(isolate()); 2204 Return(CallStub(split_callable, context, receiver, string, maybe_limit)); 2205 2206 Bind(&runtime); 2207 Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string, 2208 maybe_limit)); 2209 } 2210 2211 Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath( 2212 Node* context, Node* regexp, Node* string, Node* replace_callable) { 2213 // The fast path is reached only if {receiver} is a global unmodified 2214 // JSRegExp instance and {replace_callable} is callable. 2215 2216 Isolate* const isolate = this->isolate(); 2217 2218 Node* const null = NullConstant(); 2219 Node* const undefined = UndefinedConstant(); 2220 Node* const int_zero = IntPtrConstant(0); 2221 Node* const int_one = IntPtrConstant(1); 2222 Node* const smi_zero = SmiConstant(Smi::kZero); 2223 2224 Node* const native_context = LoadNativeContext(context); 2225 2226 Label out(this); 2227 Variable var_result(this, MachineRepresentation::kTagged); 2228 2229 // Set last index to 0. 2230 FastStoreLastIndex(regexp, smi_zero); 2231 2232 // Allocate {result_array}. 2233 Node* result_array; 2234 { 2235 ElementsKind kind = FAST_ELEMENTS; 2236 Node* const array_map = LoadJSArrayElementsMap(kind, native_context); 2237 Node* const capacity = IntPtrConstant(16); 2238 Node* const length = smi_zero; 2239 Node* const allocation_site = nullptr; 2240 ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS; 2241 2242 result_array = AllocateJSArray(kind, array_map, capacity, length, 2243 allocation_site, capacity_mode); 2244 } 2245 2246 // Call into runtime for RegExpExecMultiple. 2247 Node* last_match_info = 2248 LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 2249 Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp, 2250 string, last_match_info, result_array); 2251 2252 // Reset last index to 0. 2253 FastStoreLastIndex(regexp, smi_zero); 2254 2255 // If no matches, return the subject string. 2256 var_result.Bind(string); 2257 GotoIf(WordEqual(res, null), &out); 2258 2259 // Reload last match info since it might have changed. 2260 last_match_info = 2261 LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 2262 2263 Node* const res_length = LoadJSArrayLength(res); 2264 Node* const res_elems = LoadElements(res); 2265 CSA_ASSERT(this, HasInstanceType(res_elems, FIXED_ARRAY_TYPE)); 2266 2267 Node* const num_capture_registers = LoadFixedArrayElement( 2268 last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex); 2269 2270 Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this), 2271 create_result(this); 2272 Branch(SmiEqual(num_capture_registers, SmiConstant(Smi::FromInt(2))), 2273 &if_noexplicitcaptures, &if_hasexplicitcaptures); 2274 2275 Bind(&if_noexplicitcaptures); 2276 { 2277 // If the number of captures is two then there are no explicit captures in 2278 // the regexp, just the implicit capture that captures the whole match. In 2279 // this case we can simplify quite a bit and end up with something faster. 2280 // The builder will consist of some integers that indicate slices of the 2281 // input string and some replacements that were returned from the replace 2282 // function. 2283 2284 Variable var_match_start(this, MachineRepresentation::kTagged); 2285 var_match_start.Bind(smi_zero); 2286 2287 Node* const end = SmiUntag(res_length); 2288 Variable var_i(this, MachineType::PointerRepresentation()); 2289 var_i.Bind(int_zero); 2290 2291 Variable* vars[] = {&var_i, &var_match_start}; 2292 Label loop(this, 2, vars); 2293 Goto(&loop); 2294 Bind(&loop); 2295 { 2296 Node* const i = var_i.value(); 2297 GotoIfNot(IntPtrLessThan(i, end), &create_result); 2298 2299 Node* const elem = LoadFixedArrayElement(res_elems, i); 2300 2301 Label if_issmi(this), if_isstring(this), loop_epilogue(this); 2302 Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring); 2303 2304 Bind(&if_issmi); 2305 { 2306 // Integers represent slices of the original string. 2307 Label if_isnegativeorzero(this), if_ispositive(this); 2308 BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero, 2309 &if_ispositive); 2310 2311 Bind(&if_ispositive); 2312 { 2313 Node* const int_elem = SmiUntag(elem); 2314 Node* const new_match_start = 2315 IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)), 2316 WordAnd(int_elem, IntPtrConstant(0x7ff))); 2317 var_match_start.Bind(SmiTag(new_match_start)); 2318 Goto(&loop_epilogue); 2319 } 2320 2321 Bind(&if_isnegativeorzero); 2322 { 2323 Node* const next_i = IntPtrAdd(i, int_one); 2324 var_i.Bind(next_i); 2325 2326 Node* const next_elem = LoadFixedArrayElement(res_elems, next_i); 2327 2328 Node* const new_match_start = SmiSub(next_elem, elem); 2329 var_match_start.Bind(new_match_start); 2330 Goto(&loop_epilogue); 2331 } 2332 } 2333 2334 Bind(&if_isstring); 2335 { 2336 CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(elem))); 2337 2338 Callable call_callable = CodeFactory::Call(isolate); 2339 Node* const replacement_obj = 2340 CallJS(call_callable, context, replace_callable, undefined, elem, 2341 var_match_start.value(), string); 2342 2343 Node* const replacement_str = ToString(context, replacement_obj); 2344 StoreFixedArrayElement(res_elems, i, replacement_str); 2345 2346 Node* const elem_length = LoadStringLength(elem); 2347 Node* const new_match_start = 2348 SmiAdd(var_match_start.value(), elem_length); 2349 var_match_start.Bind(new_match_start); 2350 2351 Goto(&loop_epilogue); 2352 } 2353 2354 Bind(&loop_epilogue); 2355 { 2356 var_i.Bind(IntPtrAdd(var_i.value(), int_one)); 2357 Goto(&loop); 2358 } 2359 } 2360 } 2361 2362 Bind(&if_hasexplicitcaptures); 2363 { 2364 Node* const from = int_zero; 2365 Node* const to = SmiUntag(res_length); 2366 const int increment = 1; 2367 2368 BuildFastLoop( 2369 from, to, 2370 [this, res_elems, isolate, native_context, context, undefined, 2371 replace_callable](Node* index) { 2372 Node* const elem = LoadFixedArrayElement(res_elems, index); 2373 2374 Label do_continue(this); 2375 GotoIf(TaggedIsSmi(elem), &do_continue); 2376 2377 // elem must be an Array. 2378 // Use the apply argument as backing for global RegExp properties. 2379 2380 CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE)); 2381 2382 // TODO(jgruber): Remove indirection through Call->ReflectApply. 2383 Callable call_callable = CodeFactory::Call(isolate); 2384 Node* const reflect_apply = 2385 LoadContextElement(native_context, Context::REFLECT_APPLY_INDEX); 2386 2387 Node* const replacement_obj = 2388 CallJS(call_callable, context, reflect_apply, undefined, 2389 replace_callable, undefined, elem); 2390 2391 // Overwrite the i'th element in the results with the string we got 2392 // back from the callback function. 2393 2394 Node* const replacement_str = ToString(context, replacement_obj); 2395 StoreFixedArrayElement(res_elems, index, replacement_str); 2396 2397 Goto(&do_continue); 2398 Bind(&do_continue); 2399 }, 2400 increment, CodeStubAssembler::INTPTR_PARAMETERS, 2401 CodeStubAssembler::IndexAdvanceMode::kPost); 2402 2403 Goto(&create_result); 2404 } 2405 2406 Bind(&create_result); 2407 { 2408 Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context, 2409 res, res_length, string); 2410 var_result.Bind(result); 2411 Goto(&out); 2412 } 2413 2414 Bind(&out); 2415 return var_result.value(); 2416 } 2417 2418 Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( 2419 Node* context, Node* regexp, Node* string, Node* replace_string) { 2420 // The fast path is reached only if {receiver} is an unmodified 2421 // JSRegExp instance, {replace_value} is non-callable, and 2422 // ToString({replace_value}) does not contain '$', i.e. we're doing a simple 2423 // string replacement. 2424 2425 Node* const int_zero = IntPtrConstant(0); 2426 Node* const smi_zero = SmiConstant(Smi::kZero); 2427 2428 Label out(this); 2429 Variable var_result(this, MachineRepresentation::kTagged); 2430 2431 // Load the last match info. 2432 Node* const native_context = LoadNativeContext(context); 2433 Node* const last_match_info = 2434 LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); 2435 2436 // Is {regexp} global? 2437 Label if_isglobal(this), if_isnonglobal(this); 2438 Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); 2439 Node* const is_global = 2440 WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal)); 2441 Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal); 2442 2443 Bind(&if_isglobal); 2444 { 2445 // Hand off global regexps to runtime. 2446 FastStoreLastIndex(regexp, smi_zero); 2447 Node* const result = 2448 CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context, 2449 string, regexp, replace_string, last_match_info); 2450 var_result.Bind(result); 2451 Goto(&out); 2452 } 2453 2454 Bind(&if_isnonglobal); 2455 { 2456 // Run exec, then manually construct the resulting string. 2457 Label if_didnotmatch(this); 2458 Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( 2459 context, regexp, string, &if_didnotmatch, true); 2460 2461 // Successful match. 2462 { 2463 Node* const subject_start = smi_zero; 2464 Node* const match_start = LoadFixedArrayElement( 2465 match_indices, RegExpMatchInfo::kFirstCaptureIndex); 2466 Node* const match_end = LoadFixedArrayElement( 2467 match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); 2468 Node* const subject_end = LoadStringLength(string); 2469 2470 Label if_replaceisempty(this), if_replaceisnotempty(this); 2471 Node* const replace_length = LoadStringLength(replace_string); 2472 Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty, 2473 &if_replaceisnotempty); 2474 2475 Bind(&if_replaceisempty); 2476 { 2477 // TODO(jgruber): We could skip many of the checks that using SubString 2478 // here entails. 2479 2480 Node* const first_part = 2481 SubString(context, string, subject_start, match_start); 2482 Node* const second_part = 2483 SubString(context, string, match_end, subject_end); 2484 2485 Node* const result = StringAdd(context, first_part, second_part); 2486 var_result.Bind(result); 2487 Goto(&out); 2488 } 2489 2490 Bind(&if_replaceisnotempty); 2491 { 2492 Node* const first_part = 2493 SubString(context, string, subject_start, match_start); 2494 Node* const second_part = replace_string; 2495 Node* const third_part = 2496 SubString(context, string, match_end, subject_end); 2497 2498 Node* result = StringAdd(context, first_part, second_part); 2499 result = StringAdd(context, result, third_part); 2500 2501 var_result.Bind(result); 2502 Goto(&out); 2503 } 2504 } 2505 2506 Bind(&if_didnotmatch); 2507 { 2508 var_result.Bind(string); 2509 Goto(&out); 2510 } 2511 } 2512 2513 Bind(&out); 2514 return var_result.value(); 2515 } 2516 2517 // Helper that skips a few initial checks. 2518 TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) { 2519 typedef RegExpReplaceDescriptor Descriptor; 2520 2521 Node* const regexp = Parameter(Descriptor::kReceiver); 2522 Node* const string = Parameter(Descriptor::kString); 2523 Node* const replace_value = Parameter(Descriptor::kReplaceValue); 2524 Node* const context = Parameter(Descriptor::kContext); 2525 2526 CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp))); 2527 CSA_ASSERT(this, IsString(string)); 2528 2529 Label checkreplacestring(this), if_iscallable(this), 2530 runtime(this, Label::kDeferred); 2531 2532 // 2. Is {replace_value} callable? 2533 GotoIf(TaggedIsSmi(replace_value), &checkreplacestring); 2534 Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable, 2535 &checkreplacestring); 2536 2537 // 3. Does ToString({replace_value}) contain '$'? 2538 Bind(&checkreplacestring); 2539 { 2540 Callable tostring_callable = CodeFactory::ToString(isolate()); 2541 Node* const replace_string = 2542 CallStub(tostring_callable, context, replace_value); 2543 2544 // ToString(replaceValue) could potentially change the shape of the RegExp 2545 // object. Recheck that we are still on the fast path and bail to runtime 2546 // otherwise. 2547 { 2548 Label next(this); 2549 BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime); 2550 Bind(&next); 2551 } 2552 2553 Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); 2554 Node* const dollar_string = HeapConstant( 2555 isolate()->factory()->LookupSingleCharacterStringFromCode('$')); 2556 Node* const dollar_ix = CallStub(indexof_callable, context, replace_string, 2557 dollar_string, SmiConstant(0)); 2558 GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime); 2559 2560 Return( 2561 ReplaceSimpleStringFastPath(context, regexp, string, replace_string)); 2562 } 2563 2564 // {regexp} is unmodified and {replace_value} is callable. 2565 Bind(&if_iscallable); 2566 { 2567 Node* const replace_fn = replace_value; 2568 2569 // Check if the {regexp} is global. 2570 Label if_isglobal(this), if_isnotglobal(this); 2571 2572 Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); 2573 Branch(is_global, &if_isglobal, &if_isnotglobal); 2574 2575 Bind(&if_isglobal); 2576 Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn)); 2577 2578 Bind(&if_isnotglobal); 2579 Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction, 2580 context, string, regexp, replace_fn)); 2581 } 2582 2583 Bind(&runtime); 2584 Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string, 2585 replace_value)); 2586 } 2587 2588 // ES#sec-regexp.prototype-@@replace 2589 // RegExp.prototype [ @@replace ] ( string, replaceValue ) 2590 TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { 2591 Node* const maybe_receiver = Parameter(0); 2592 Node* const maybe_string = Parameter(1); 2593 Node* const replace_value = Parameter(2); 2594 Node* const context = Parameter(5); 2595 2596 // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic: 2597 // 2598 // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace) 2599 // if (IsCallable(replace)) { 2600 // if (IsGlobal(receiver)) { 2601 // // Called 'fast-path' but contains several runtime calls. 2602 // ReplaceGlobalCallableFastPath() 2603 // } else { 2604 // CallRuntime(StringReplaceNonGlobalRegExpWithFunction) 2605 // } 2606 // } else { 2607 // if (replace.contains("$")) { 2608 // CallRuntime(RegExpReplace) 2609 // } else { 2610 // ReplaceSimpleStringFastPath() // Bails to runtime for global regexps. 2611 // } 2612 // } 2613 2614 // Ensure {maybe_receiver} is a JSReceiver. 2615 ThrowIfNotJSReceiver(context, maybe_receiver, 2616 MessageTemplate::kIncompatibleMethodReceiver, 2617 "RegExp.prototype.@@replace"); 2618 Node* const receiver = maybe_receiver; 2619 2620 // Convert {maybe_string} to a String. 2621 Callable tostring_callable = CodeFactory::ToString(isolate()); 2622 Node* const string = CallStub(tostring_callable, context, maybe_string); 2623 2624 // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? 2625 Label stub(this), runtime(this, Label::kDeferred); 2626 BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime); 2627 2628 Bind(&stub); 2629 Callable replace_callable = CodeFactory::RegExpReplace(isolate()); 2630 Return(CallStub(replace_callable, context, receiver, string, replace_value)); 2631 2632 Bind(&runtime); 2633 Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string, 2634 replace_value)); 2635 } 2636 2637 // Simple string matching functionality for internal use which does not modify 2638 // the last match info. 2639 TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) { 2640 Node* const regexp = Parameter(1); 2641 Node* const string = Parameter(2); 2642 Node* const context = Parameter(5); 2643 2644 Node* const null = NullConstant(); 2645 Node* const smi_zero = SmiConstant(Smi::FromInt(0)); 2646 2647 Node* const native_context = LoadNativeContext(context); 2648 Node* const internal_match_info = LoadContextElement( 2649 native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX); 2650 2651 Callable exec_callable = CodeFactory::RegExpExec(isolate()); 2652 Node* const match_indices = CallStub(exec_callable, context, regexp, string, 2653 smi_zero, internal_match_info); 2654 2655 Label if_matched(this), if_didnotmatch(this); 2656 Branch(WordEqual(match_indices, null), &if_didnotmatch, &if_matched); 2657 2658 Bind(&if_didnotmatch); 2659 Return(null); 2660 2661 Bind(&if_matched); 2662 { 2663 Node* result = 2664 ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); 2665 Return(result); 2666 } 2667 } 2668 2669 } // namespace internal 2670 } // namespace v8 2671