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 |