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