• 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 builtin
10SubString(implicit context: Context)(String, Smi, Smi): String;
11
12extern runtime RegExpExecMultiple(implicit context: Context)(
13    JSRegExp, String, RegExpMatchInfo, JSArray): Null|JSArray;
14extern transitioning runtime
15RegExpReplaceRT(Context, JSReceiver, String, Object): String;
16extern transitioning runtime
17StringBuilderConcat(implicit context: Context)(JSArray, Smi, String): String;
18extern transitioning runtime
19StringReplaceNonGlobalRegExpWithFunction(implicit context: Context)(
20    String, JSRegExp, Callable): String;
21
22transitioning macro RegExpReplaceCallableNoExplicitCaptures(
23    implicit context: Context)(
24    matchesElements: FixedArray, matchesLength: intptr, string: String,
25    replaceFn: Callable) {
26  let matchStart: Smi = 0;
27  for (let i: intptr = 0; i < matchesLength; i++) {
28    typeswitch (matchesElements.objects[i]) {
29      // Element represents a slice.
30      case (elSmi: Smi): {
31        // The slice's match start and end is either encoded as one or two
32        // smis. A positive smi indicates a single smi encoding (see
33        // ReplacementStringBuilder::AddSubjectSlice()).
34        if (elSmi > 0) {
35          // For single smi encoding, see
36          // StringBuilderSubstringLength::encode() and
37          // StringBuilderSubstringPosition::encode().
38          const elInt: intptr = Convert<intptr>(elSmi);
39          const newMatchStart: intptr = (elInt >> 11) + (elInt & 0x7FF);
40          matchStart = Convert<Smi>(newMatchStart);
41        } else {
42          // For two smi encoding, the length is negative followed by the
43          // match start.
44          const nextEl: Smi = UnsafeCast<Smi>(matchesElements.objects[++i]);
45          matchStart = nextEl - elSmi;
46        }
47      }
48      // Element represents the matched substring, which is then passed to the
49      // replace function.
50      case (elString: String): {
51        const replacementObj: JSAny =
52            Call(context, replaceFn, Undefined, elString, matchStart, string);
53        const replacement: String = ToString_Inline(replacementObj);
54        matchesElements.objects[i] = replacement;
55        matchStart += elString.length_smi;
56      }
57      case (Object): deferred {
58        unreachable;
59      }
60    }
61  }
62}
63
64transitioning macro
65RegExpReplaceCallableWithExplicitCaptures(implicit context: Context)(
66    matchesElements: FixedArray, matchesLength: intptr, replaceFn: Callable) {
67  for (let i: intptr = 0; i < matchesLength; i++) {
68    const elArray =
69        Cast<JSArray>(matchesElements.objects[i]) otherwise continue;
70
71    // The JSArray is expanded into the function args by Reflect.apply().
72    // TODO(jgruber): Remove indirection through Call->ReflectApply.
73    const replacementObj: JSAny = Call(
74        context, GetReflectApply(), Undefined, replaceFn, Undefined, elArray);
75
76    // Overwrite the i'th element in the results with the string
77    // we got back from the callback function.
78    matchesElements.objects[i] = ToString_Inline(replacementObj);
79  }
80}
81
82transitioning macro RegExpReplaceFastGlobalCallable(implicit context: Context)(
83    regexp: FastJSRegExp, string: String, replaceFn: Callable): String {
84  regexp.lastIndex = 0;
85
86  const kInitialCapacity: intptr = 16;
87  const kInitialLength: Smi = 0;
88  const result: Null|JSArray = RegExpExecMultiple(
89      regexp, string, GetRegExpLastMatchInfo(),
90      AllocateJSArray(
91          ElementsKind::PACKED_ELEMENTS, GetFastPackedElementsJSArrayMap(),
92          kInitialCapacity, kInitialLength));
93
94  regexp.lastIndex = 0;
95
96  // If no matches, return the subject string.
97  if (result == Null) return string;
98
99  const matches: JSArray = UnsafeCast<JSArray>(result);
100  const matchesLength: Smi = Cast<Smi>(matches.length) otherwise unreachable;
101  const matchesLengthInt: intptr = Convert<intptr>(matchesLength);
102  const matchesElements: FixedArray = UnsafeCast<FixedArray>(matches.elements);
103
104  // Reload last match info since it might have changed.
105  const nofCaptures: Smi = GetRegExpLastMatchInfo().NumberOfCaptures();
106
107  // If the number of captures is two then there are no explicit captures in
108  // the regexp, just the implicit capture that captures the whole match. In
109  // this case we can simplify quite a bit and end up with something faster.
110  if (nofCaptures == 2) {
111    RegExpReplaceCallableNoExplicitCaptures(
112        matchesElements, matchesLengthInt, string, replaceFn);
113  } else {
114    RegExpReplaceCallableWithExplicitCaptures(
115        matchesElements, matchesLengthInt, replaceFn);
116  }
117
118  return StringBuilderConcat(matches, matchesLength, string);
119}
120
121transitioning macro RegExpReplaceFastString(implicit context: Context)(
122    regexp: JSRegExp, string: String, replaceString: String): String {
123  // The fast path is reached only if {receiver} is an unmodified JSRegExp
124  // instance, {replace_value} is non-callable, and ToString({replace_value})
125  // does not contain '$', i.e. we're doing a simple string replacement.
126  let result: String = kEmptyString;
127  let lastMatchEnd: Smi = 0;
128  let unicode: bool = false;
129  const replaceLength: Smi = replaceString.length_smi;
130  const fastRegexp = UnsafeCast<FastJSRegExp>(regexp);
131  const global: bool = fastRegexp.global;
132
133  if (global) {
134    unicode = fastRegexp.unicode;
135    fastRegexp.lastIndex = 0;
136  }
137
138  while (true) {
139    const match: RegExpMatchInfo =
140        RegExpPrototypeExecBodyWithoutResultFast(regexp, string)
141        otherwise break;
142    const matchStart: Smi = match.GetStartOfCapture(0);
143    const matchEnd: Smi = match.GetEndOfCapture(0);
144
145    // TODO(jgruber): We could skip many of the checks that using SubString
146    // here entails.
147    result = result + SubString(string, lastMatchEnd, matchStart);
148    lastMatchEnd = matchEnd;
149
150    if (replaceLength != 0) result = result + replaceString;
151
152    // Non-global case ends here after the first replacement.
153    if (!global) break;
154
155    // If match is the empty string, we have to increment lastIndex.
156    if (matchEnd == matchStart) {
157      typeswitch (regexp) {
158        case (fastRegexp: FastJSRegExp): {
159          fastRegexp.lastIndex =
160              AdvanceStringIndexFast(string, fastRegexp.lastIndex, unicode);
161        }
162        case (Object): {
163          const lastIndex: JSAny = SlowLoadLastIndex(regexp);
164          const thisIndex: Number = ToLength_Inline(lastIndex);
165          const nextIndex: Number =
166              AdvanceStringIndexSlow(string, thisIndex, unicode);
167          SlowStoreLastIndex(regexp, nextIndex);
168        }
169      }
170    }
171  }
172
173  return result + SubString(string, lastMatchEnd, string.length_smi);
174}
175
176transitioning builtin RegExpReplace(implicit context: Context)(
177    regexp: FastJSRegExp, string: String, replaceValue: JSAny): String {
178  // TODO(pwong): Remove assert when all callers (StringPrototypeReplace) are
179  // from Torque.
180  assert(Is<FastJSRegExp>(regexp));
181
182  // 2. Is {replace_value} callable?
183  typeswitch (replaceValue) {
184    case (replaceFn: Callable): {
185      return regexp.global ?
186          RegExpReplaceFastGlobalCallable(regexp, string, replaceFn) :
187          StringReplaceNonGlobalRegExpWithFunction(string, regexp, replaceFn);
188    }
189    case (JSAny): {
190      const stableRegexp: JSRegExp = regexp;
191      const replaceString: String = ToString_Inline(replaceValue);
192
193      try {
194        // ToString(replaceValue) could potentially change the shape of the
195        // RegExp object. Recheck that we are still on the fast path and bail
196        // to runtime otherwise.
197        const fastRegexp = Cast<FastJSRegExp>(stableRegexp) otherwise Runtime;
198        if (StringIndexOf(
199                replaceString, SingleCharacterStringConstant('$'), 0) != -1) {
200          goto Runtime;
201        }
202
203        return RegExpReplaceFastString(fastRegexp, string, replaceString);
204      } label Runtime deferred {
205        return RegExpReplaceRT(context, stableRegexp, string, replaceString);
206      }
207    }
208  }
209}
210
211const kRegExpReplaceCalledOnSlowRegExp: constexpr int31
212    generates 'v8::Isolate::kRegExpReplaceCalledOnSlowRegExp';
213
214transitioning javascript builtin RegExpPrototypeReplace(
215    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
216  const methodName: constexpr string = 'RegExp.prototype.@@replace';
217
218  // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic:
219  //
220  // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace)
221  // if (IsCallable(replace)) {
222  //   if (IsGlobal(receiver)) {
223  //     // Called 'fast-path' but contains several runtime calls.
224  //     RegExpReplaceFastGlobalCallable()
225  //   } else {
226  //     CallRuntime(StringReplaceNonGlobalRegExpWithFunction)
227  //   }
228  // } else {
229  //   if (replace.contains("$")) {
230  //     CallRuntime(RegExpReplace)
231  //   } else {
232  //     RegExpReplaceFastString()
233  //   }
234  // }
235
236  const string: JSAny = arguments[0];
237  const replaceValue: JSAny = arguments[1];
238
239  // Let rx be the this value.
240  // If Type(rx) is not Object, throw a TypeError exception.
241  const rx = Cast<JSReceiver>(receiver)
242      otherwise ThrowTypeError(
243      MessageTemplate::kIncompatibleMethodReceiver, methodName);
244
245  // Let S be ? ToString(string).
246  const s = ToString_Inline(string);
247
248  // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
249  try {
250    const fastRx: FastJSRegExp = Cast<FastJSRegExp>(rx) otherwise Runtime;
251    return RegExpReplace(fastRx, s, replaceValue);
252  } label Runtime deferred {
253    IncrementUseCounter(context, SmiConstant(kRegExpReplaceCalledOnSlowRegExp));
254    return RegExpReplaceRT(context, rx, s, replaceValue);
255  }
256}
257}
258