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