• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include 'src/builtins/builtins-regexp-gen.h'
6
7namespace regexp {
8
9extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForMatch(
10    implicit context: Context)(HeapObject): never labels IsFast,
11    IsSlow;
12macro IsFastRegExpForMatch(implicit context: Context)(o: HeapObject): bool {
13  BranchIfFastRegExpForMatch(o) otherwise return true, return false;
14}
15
16extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForSearch(
17    implicit context: Context)(HeapObject): never labels IsFast,
18    IsSlow;
19macro IsFastRegExpForSearch(implicit context: Context)(o: HeapObject): bool {
20  BranchIfFastRegExpForSearch(o) otherwise return true, return false;
21}
22
23extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
24    implicit context: Context)(HeapObject): never labels IsFast,
25    IsSlow;
26macro IsFastRegExpStrict(implicit context: Context)(o: HeapObject): bool {
27  BranchIfFastRegExp_Strict(o) otherwise return true, return false;
28}
29
30extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive(
31    implicit context: Context)(HeapObject): never labels IsFast,
32    IsSlow;
33
34@export
35macro IsFastRegExpPermissive(implicit context: Context)(o: HeapObject): bool {
36  BranchIfFastRegExp_Permissive(o) otherwise return true, return false;
37}
38
39// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
40@export
41transitioning macro RegExpExec(implicit context: Context)(
42    receiver: JSReceiver, string: String): JSAny {
43  // Take the slow path of fetching the exec property, calling it, and
44  // verifying its return value.
45
46  const exec = GetProperty(receiver, 'exec');
47
48  // Is {exec} callable?
49  typeswitch (exec) {
50    case (execCallable: Callable): {
51      const result = Call(context, execCallable, receiver, string);
52      if (result != Null) {
53        ThrowIfNotJSReceiver(
54            result, MessageTemplate::kInvalidRegExpExecResult, '');
55      }
56      return result;
57    }
58    case (Object): {
59      const regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
60          MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec',
61          receiver);
62      return RegExpPrototypeExecSlow(regexp, string);
63    }
64  }
65}
66
67extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
68    implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number):
69    JSRegExpResult|JSRegExpResultWithIndices;
70
71const kGlobalOrSticky: constexpr int31
72    generates 'JSRegExp::kGlobal | JSRegExp::kSticky';
73
74extern macro RegExpBuiltinsAssembler::RegExpExecInternal(
75    implicit context: Context)(
76    JSRegExp, String, Number, RegExpMatchInfo): HeapObject;
77
78// ES#sec-regexp.prototype.exec
79// RegExp.prototype.exec ( string )
80// Implements the core of RegExp.prototype.exec but without actually
81// constructing the JSRegExpResult. Returns a fixed array containing match
82// indices as returned by RegExpExecStub on successful match, and jumps to
83// IfDidNotMatch otherwise.
84transitioning macro RegExpPrototypeExecBodyWithoutResult(
85    implicit context: Context)(
86    regexp: JSRegExp, string: String, regexpLastIndex: Number,
87    isFastPath: constexpr bool): RegExpMatchInfo labels IfDidNotMatch {
88  if (isFastPath) {
89    dcheck(HasInitialRegExpMap(regexp));
90  } else {
91    IncrementUseCounter(context, SmiConstant(kRegExpExecCalledOnSlowRegExp));
92  }
93
94  let lastIndex = regexpLastIndex;
95
96  // Check whether the regexp is global or sticky, which determines whether we
97  // update last index later on.
98  const flags = UnsafeCast<Smi>(regexp.flags);
99  const isGlobalOrSticky: intptr =
100      SmiUntag(flags) & IntPtrConstant(kGlobalOrSticky);
101  const shouldUpdateLastIndex: bool = isGlobalOrSticky != 0;
102
103  // Grab and possibly update last index.
104  if (shouldUpdateLastIndex) {
105    if (!TaggedIsSmi(lastIndex) || (lastIndex > string.length_smi)) {
106      StoreLastIndex(regexp, SmiConstant(0), isFastPath);
107      goto IfDidNotMatch;
108    }
109  } else {
110    lastIndex = SmiConstant(0);
111  }
112
113  const lastMatchInfo: RegExpMatchInfo = GetRegExpLastMatchInfo();
114
115  const matchIndices =
116      RegExpExecInternal(regexp, string, lastIndex, lastMatchInfo);
117
118  // {match_indices} is either null or the RegExpMatchInfo array.
119  // Return early if exec failed, possibly updating last index.
120  if (matchIndices != Null) {
121    const matchIndicesRegExpMatchInfo =
122        UnsafeCast<RegExpMatchInfo>(matchIndices);
123    if (shouldUpdateLastIndex) {
124      // Update the new last index from {match_indices}.
125      const newLastIndex: Smi = matchIndicesRegExpMatchInfo.GetEndOfCapture(0);
126      StoreLastIndex(regexp, newLastIndex, isFastPath);
127    }
128    return matchIndicesRegExpMatchInfo;
129  }
130  if (shouldUpdateLastIndex) {
131    StoreLastIndex(regexp, SmiConstant(0), isFastPath);
132  }
133  goto IfDidNotMatch;
134}
135
136@export
137transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
138    implicit context: Context)(regexp: JSRegExp, string: String):
139    RegExpMatchInfo labels IfDidNotMatch {
140  const lastIndex = LoadLastIndexAsLength(regexp, true);
141  return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
142      otherwise IfDidNotMatch;
143}
144
145transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
146    implicit context: Context)(
147    regexp: JSRegExp, string: String,
148    lastIndex: Number): RegExpMatchInfo labels IfDidNotMatch {
149  return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
150      otherwise IfDidNotMatch;
151}
152
153// ES#sec-regexp.prototype.exec
154// RegExp.prototype.exec ( string )
155transitioning macro RegExpPrototypeExecBody(implicit context: Context)(
156    receiver: JSReceiver, string: String, isFastPath: constexpr bool): JSAny {
157  let regexp: JSRegExp;
158  if constexpr (isFastPath) {
159    regexp = UnsafeCast<JSRegExp>(receiver);
160  } else {
161    regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
162        MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec',
163        receiver);
164  }
165  const lastIndex = LoadLastIndexAsLength(regexp, isFastPath);
166  const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult(
167      regexp, string, lastIndex, isFastPath) otherwise return Null;
168  return ConstructNewResultFromMatchInfo(
169      regexp, matchIndices, string, lastIndex);
170}
171
172macro LoadRegExpFunction(nativeContext: NativeContext): JSFunction {
173  return *NativeContextSlot(nativeContext, ContextSlot::REGEXP_FUNCTION_INDEX);
174}
175
176// Note this doesn't guarantee const-ness of object properties, just
177// unchanged object layout.
178macro HasInitialRegExpMap(implicit context: Context)(o: HeapObject): bool {
179  const nativeContext = LoadNativeContext(context);
180  const function = LoadRegExpFunction(nativeContext);
181  const initialMap = UnsafeCast<Map>(function.prototype_or_initial_map);
182  return initialMap == o.map;
183}
184
185macro IsReceiverInitialRegExpPrototype(implicit context: Context)(
186    receiver: Object): bool {
187  const nativeContext = LoadNativeContext(context);
188  const regexpFun = LoadRegExpFunction(nativeContext);
189  const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map);
190  const initialPrototype: HeapObject = initialMap.prototype;
191  return TaggedEqual(receiver, initialPrototype);
192}
193
194extern enum Flag constexpr 'JSRegExp::Flag' {
195  kNone,
196  kGlobal,
197  kIgnoreCase,
198  kMultiline,
199  kSticky,
200  kUnicode,
201  kDotAll,
202  kHasIndices,
203  kLinear
204}
205
206const kNoCounterFlagGetter: constexpr int31 = -1;
207const kRegExpPrototypeStickyGetter: constexpr int31
208    generates 'v8::Isolate::kRegExpPrototypeStickyGetter';
209const kRegExpPrototypeUnicodeGetter: constexpr int31
210    generates 'v8::Isolate::kRegExpPrototypeUnicodeGetter';
211
212extern macro RegExpBuiltinsAssembler::FastFlagGetter(
213    JSRegExp, constexpr Flag): bool;
214extern runtime IncrementUseCounter(Context, Smi): void;
215
216macro FlagGetter(implicit context: Context)(
217    receiver: Object, flag: constexpr Flag, counter: constexpr int31,
218    methodName: constexpr string): JSAny {
219  typeswitch (receiver) {
220    case (receiver: JSRegExp): {
221      return SelectBooleanConstant(FastFlagGetter(receiver, flag));
222    }
223    case (Object): {
224    }
225  }
226  if (!IsReceiverInitialRegExpPrototype(receiver)) {
227    ThrowTypeError(MessageTemplate::kRegExpNonRegExp, methodName);
228  }
229  if constexpr (counter != -1) {
230    IncrementUseCounter(context, SmiConstant(counter));
231  }
232  return Undefined;
233}
234
235// ES6 21.2.5.4.
236// ES #sec-get-regexp.prototype.global
237transitioning javascript builtin RegExpPrototypeGlobalGetter(
238    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
239  return FlagGetter(
240      receiver, Flag::kGlobal, kNoCounterFlagGetter, 'RegExp.prototype.global');
241}
242
243// ES6 21.2.5.5.
244// ES #sec-get-regexp.prototype.ignorecase
245transitioning javascript builtin RegExpPrototypeIgnoreCaseGetter(
246    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
247  return FlagGetter(
248      receiver, Flag::kIgnoreCase, kNoCounterFlagGetter,
249      'RegExp.prototype.ignoreCase');
250}
251
252// ES6 21.2.5.7.
253// ES #sec-get-regexp.prototype.multiline
254transitioning javascript builtin RegExpPrototypeMultilineGetter(
255    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
256  return FlagGetter(
257      receiver, Flag::kMultiline, kNoCounterFlagGetter,
258      'RegExp.prototype.multiline');
259}
260
261transitioning javascript builtin RegExpPrototypeHasIndicesGetter(
262    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
263  return FlagGetter(
264      receiver, Flag::kHasIndices, kNoCounterFlagGetter,
265      'RegExp.prototype.hasIndices');
266}
267
268transitioning javascript builtin RegExpPrototypeLinearGetter(
269    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
270  return FlagGetter(
271      receiver, Flag::kLinear, kNoCounterFlagGetter, 'RegExp.prototype.linear');
272}
273
274// ES #sec-get-regexp.prototype.dotAll
275transitioning javascript builtin RegExpPrototypeDotAllGetter(
276    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
277  return FlagGetter(
278      receiver, Flag::kDotAll, kNoCounterFlagGetter, 'RegExp.prototype.dotAll');
279}
280
281// ES6 21.2.5.12.
282// ES #sec-get-regexp.prototype.sticky
283transitioning javascript builtin RegExpPrototypeStickyGetter(
284    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
285  return FlagGetter(
286      receiver, Flag::kSticky, kRegExpPrototypeStickyGetter,
287      'RegExp.prototype.sticky');
288}
289
290// ES6 21.2.5.15.
291// ES #sec-get-regexp.prototype.unicode
292transitioning javascript builtin RegExpPrototypeUnicodeGetter(
293    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
294  return FlagGetter(
295      receiver, Flag::kUnicode, kRegExpPrototypeUnicodeGetter,
296      'RegExp.prototype.unicode');
297}
298
299extern transitioning macro
300RegExpBuiltinsAssembler::FlagsGetter(implicit context: Context)(
301    Object, constexpr bool): String;
302
303transitioning macro
304FastFlagsGetter(implicit context: Context)(receiver: FastJSRegExp): String {
305  return FlagsGetter(receiver, true);
306}
307
308transitioning macro SlowFlagsGetter(implicit context: Context)(receiver: JSAny):
309    String {
310  return FlagsGetter(receiver, false);
311}
312
313// ES #sec-get-regexp.prototype.flags
314// TFJ(RegExpPrototypeFlagsGetter, 0, kReceiver) \
315transitioning javascript builtin RegExpPrototypeFlagsGetter(
316    js-implicit context: NativeContext, receiver: JSAny)(): String {
317  ThrowIfNotJSReceiver(
318      receiver, MessageTemplate::kRegExpNonObject, 'RegExp.prototype.flags');
319
320  // The check is strict because the following code relies on individual flag
321  // getters on the regexp prototype (e.g.: global, sticky, ...). We don't
322  // bother to check these individually.
323  const fastRegexp = Cast<FastJSRegExp>(receiver)
324      otherwise return SlowFlagsGetter(receiver);
325  return FastFlagsGetter(fastRegexp);
326}
327
328extern transitioning macro RegExpBuiltinsAssembler::SlowLoadLastIndex(
329    implicit context: Context)(JSAny): JSAny;
330extern transitioning macro RegExpBuiltinsAssembler::SlowStoreLastIndex(
331    implicit context: Context)(JSAny, JSAny): void;
332
333extern macro RegExpBuiltinsAssembler::FastLoadLastIndex(JSRegExp): Smi;
334extern macro RegExpBuiltinsAssembler::FastStoreLastIndex(JSRegExp, Smi): void;
335
336@export
337transitioning macro LoadLastIndex(implicit context: Context)(
338    regexp: JSAny, isFastPath: constexpr bool): JSAny {
339  return isFastPath ? FastLoadLastIndex(UnsafeCast<JSRegExp>(regexp)) :
340                      SlowLoadLastIndex(regexp);
341}
342
343@export
344transitioning macro LoadLastIndexAsLength(implicit context: Context)(
345    regexp: JSRegExp, isFastPath: constexpr bool): Number {
346  const lastIndex = LoadLastIndex(regexp, isFastPath);
347  if (isFastPath) {
348    // ToLength on a positive smi is a nop and can be skipped.
349    return UnsafeCast<PositiveSmi>(lastIndex);
350  } else {
351    // Omit ToLength if last_index is a non-negative smi.
352    typeswitch (lastIndex) {
353      case (i: PositiveSmi): {
354        return i;
355      }
356      case (o: JSAny): {
357        return ToLength_Inline(o);
358      }
359    }
360  }
361}
362
363@export
364transitioning macro StoreLastIndex(implicit context: Context)(
365    regexp: JSAny, value: Number, isFastPath: constexpr bool): void {
366  if (isFastPath) {
367    FastStoreLastIndex(UnsafeCast<JSRegExp>(regexp), UnsafeCast<Smi>(value));
368  } else {
369    SlowStoreLastIndex(regexp, value);
370  }
371}
372
373extern macro RegExpBuiltinsAssembler::AdvanceStringIndex(
374    String, Number, bool, constexpr bool): Number;
375extern macro
376RegExpBuiltinsAssembler::AdvanceStringIndexFast(String, Smi, bool): Smi;
377extern macro
378RegExpBuiltinsAssembler::AdvanceStringIndexSlow(String, Number, bool): Smi;
379
380type UseCounterFeature extends int31
381constexpr 'v8::Isolate::UseCounterFeature';
382const kRegExpMatchIsTrueishOnNonJSRegExp: constexpr UseCounterFeature
383    generates 'v8::Isolate::kRegExpMatchIsTrueishOnNonJSRegExp';
384const kRegExpMatchIsFalseishOnJSRegExp: constexpr UseCounterFeature
385    generates 'v8::Isolate::kRegExpMatchIsFalseishOnJSRegExp';
386const kRegExpExecCalledOnSlowRegExp: constexpr UseCounterFeature
387    generates 'v8::Isolate::kRegExpExecCalledOnSlowRegExp';
388
389// ES#sec-isregexp IsRegExp ( argument )
390@export
391transitioning macro IsRegExp(implicit context: Context)(obj: JSAny): bool {
392  const receiver = Cast<JSReceiver>(obj) otherwise return false;
393
394  // Check @match.
395  const value = GetProperty(receiver, MatchSymbolConstant());
396  if (value == Undefined) {
397    return Is<JSRegExp>(receiver);
398  }
399
400  dcheck(value != Undefined);
401  // The common path. Symbol.match exists, equals the RegExpPrototypeMatch
402  // function (and is thus trueish), and the receiver is a JSRegExp.
403  if (ToBoolean(value)) {
404    if (!Is<JSRegExp>(receiver)) {
405      IncrementUseCounter(
406          context, SmiConstant(kRegExpMatchIsTrueishOnNonJSRegExp));
407    }
408    return true;
409  }
410
411  dcheck(!ToBoolean(value));
412  if (Is<JSRegExp>(receiver)) {
413    IncrementUseCounter(context, SmiConstant(kRegExpMatchIsFalseishOnJSRegExp));
414  }
415  return false;
416}
417
418extern runtime RegExpInitializeAndCompile(
419    Context, JSRegExp, String, String): JSAny;
420
421@export
422transitioning macro RegExpCreate(implicit context: Context)(
423    nativeContext: NativeContext, maybeString: JSAny, flags: String): JSAny {
424  const regexpFun = LoadRegExpFunction(nativeContext);
425  const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map);
426  return RegExpCreate(initialMap, maybeString, flags);
427}
428
429@export
430transitioning macro RegExpCreate(implicit context: Context)(
431    initialMap: Map, maybeString: JSAny, flags: String): JSAny {
432  const pattern: String =
433      maybeString == Undefined ? kEmptyString : ToString_Inline(maybeString);
434  const regexp =
435      UnsafeCast<JSRegExp>(AllocateFastOrSlowJSObjectFromMap(initialMap));
436  return RegExpInitializeAndCompile(context, regexp, pattern, flags);
437}
438}
439