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