regexp.tq 15.4 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include 'src/builtins/builtins-regexp-gen.h'

namespace regexp {

9 10 11 12 13 14 15 16 17 18 19 20 21 22
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForMatch(
    implicit context: Context)(HeapObject): never labels IsFast,
    IsSlow;
macro IsFastRegExpForMatch(implicit context: Context)(o: HeapObject): bool {
  BranchIfFastRegExpForMatch(o) otherwise return true, return false;
}

extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForSearch(
    implicit context: Context)(HeapObject): never labels IsFast,
    IsSlow;
macro IsFastRegExpForSearch(implicit context: Context)(o: HeapObject): bool {
  BranchIfFastRegExpForSearch(o) otherwise return true, return false;
}

23 24 25 26 27 28
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict(
    implicit context: Context)(HeapObject): never labels IsFast,
    IsSlow;
macro IsFastRegExpStrict(implicit context: Context)(o: HeapObject): bool {
  BranchIfFastRegExp_Strict(o) otherwise return true, return false;
}
29

30 31 32
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive(
    implicit context: Context)(HeapObject): never labels IsFast,
    IsSlow;
33

34 35 36 37
@export
macro IsFastRegExpPermissive(implicit context: Context)(o: HeapObject): bool {
  BranchIfFastRegExp_Permissive(o) otherwise return true, return false;
}
38

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
@export
transitioning macro RegExpExec(implicit context: Context)(
    receiver: JSReceiver, string: String): JSAny {
  // Take the slow path of fetching the exec property, calling it, and
  // verifying its return value.

  const exec = GetProperty(receiver, 'exec');

  // Is {exec} callable?
  typeswitch (exec) {
    case (execCallable: Callable): {
      const result = Call(context, execCallable, receiver, string);
      if (result != Null) {
        ThrowIfNotJSReceiver(
            result, MessageTemplate::kInvalidRegExpExecResult, '');
55
      }
56
      return result;
57
    }
58 59 60 61 62
    case (Object): {
      const regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
          MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec',
          receiver);
      return RegExpPrototypeExecSlow(regexp, string);
63
    }
64 65
  }
}
66

67
extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
68 69
    implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number):
    JSRegExpResult|JSRegExpResultWithIndices;
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

const kGlobalOrSticky: constexpr int31
    generates 'JSRegExp::kGlobal | JSRegExp::kSticky';

extern macro RegExpBuiltinsAssembler::RegExpExecInternal(
    implicit context: Context)(
    JSRegExp, String, Number, RegExpMatchInfo): HeapObject;

// ES#sec-regexp.prototype.exec
// RegExp.prototype.exec ( string )
// Implements the core of RegExp.prototype.exec but without actually
// constructing the JSRegExpResult. Returns a fixed array containing match
// indices as returned by RegExpExecStub on successful match, and jumps to
// IfDidNotMatch otherwise.
transitioning macro RegExpPrototypeExecBodyWithoutResult(
    implicit context: Context)(
    regexp: JSRegExp, string: String, regexpLastIndex: Number,
    isFastPath: constexpr bool): RegExpMatchInfo labels IfDidNotMatch {
  if (isFastPath) {
    assert(HasInitialRegExpMap(regexp));
  } else {
    IncrementUseCounter(context, SmiConstant(kRegExpExecCalledOnSlowRegExp));
  }
93

94
  let lastIndex = regexpLastIndex;
95

96 97 98 99 100 101
  // Check whether the regexp is global or sticky, which determines whether we
  // update last index later on.
  const flags = UnsafeCast<Smi>(regexp.flags);
  const isGlobalOrSticky: intptr =
      SmiUntag(flags) & IntPtrConstant(kGlobalOrSticky);
  const shouldUpdateLastIndex: bool = isGlobalOrSticky != 0;
102

103 104 105
  // Grab and possibly update last index.
  if (shouldUpdateLastIndex) {
    if (!TaggedIsSmi(lastIndex) || (lastIndex > string.length_smi)) {
106
      StoreLastIndex(regexp, SmiConstant(0), isFastPath);
107
      goto IfDidNotMatch;
108
    }
109 110
  } else {
    lastIndex = SmiConstant(0);
111 112
  }

113
  const lastMatchInfo: RegExpMatchInfo = GetRegExpLastMatchInfo();
114

115 116
  const matchIndices =
      RegExpExecInternal(regexp, string, lastIndex, lastMatchInfo);
117

118 119 120 121 122 123 124 125 126
  // {match_indices} is either null or the RegExpMatchInfo array.
  // Return early if exec failed, possibly updating last index.
  if (matchIndices != Null) {
    const matchIndicesRegExpMatchInfo =
        UnsafeCast<RegExpMatchInfo>(matchIndices);
    if (shouldUpdateLastIndex) {
      // Update the new last index from {match_indices}.
      const newLastIndex: Smi = matchIndicesRegExpMatchInfo.GetEndOfCapture(0);
      StoreLastIndex(regexp, newLastIndex, isFastPath);
127
    }
128
    return matchIndicesRegExpMatchInfo;
129
  }
130 131
  if (shouldUpdateLastIndex) {
    StoreLastIndex(regexp, SmiConstant(0), isFastPath);
132
  }
133 134
  goto IfDidNotMatch;
}
135

136 137 138 139 140 141 142 143
@export
transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
    implicit context: Context)(regexp: JSRegExp, string: String):
    RegExpMatchInfo labels IfDidNotMatch {
  const lastIndex = LoadLastIndexAsLength(regexp, true);
  return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
      otherwise IfDidNotMatch;
}
144

145 146 147 148 149 150 151
transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
    implicit context: Context)(
    regexp: JSRegExp, string: String,
    lastIndex: Number): RegExpMatchInfo labels IfDidNotMatch {
  return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
      otherwise IfDidNotMatch;
}
152

153 154 155 156 157 158 159 160 161 162 163
// ES#sec-regexp.prototype.exec
// RegExp.prototype.exec ( string )
transitioning macro RegExpPrototypeExecBody(implicit context: Context)(
    receiver: JSReceiver, string: String, isFastPath: constexpr bool): JSAny {
  let regexp: JSRegExp;
  if constexpr (isFastPath) {
    regexp = UnsafeCast<JSRegExp>(receiver);
  } else {
    regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
        MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec',
        receiver);
164
  }
165 166 167 168 169 170
  const lastIndex = LoadLastIndexAsLength(regexp, isFastPath);
  const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult(
      regexp, string, lastIndex, isFastPath) otherwise return Null;
  return ConstructNewResultFromMatchInfo(
      regexp, matchIndices, string, lastIndex);
}
171

172 173
macro LoadRegExpFunction(nativeContext: NativeContext): JSFunction {
  return *NativeContextSlot(nativeContext, ContextSlot::REGEXP_FUNCTION_INDEX);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
}

// Note this doesn't guarantee const-ness of object properties, just
// unchanged object layout.
macro HasInitialRegExpMap(implicit context: Context)(o: HeapObject): bool {
  const nativeContext = LoadNativeContext(context);
  const function = LoadRegExpFunction(nativeContext);
  const initialMap = UnsafeCast<Map>(function.prototype_or_initial_map);
  return initialMap == o.map;
}

macro IsReceiverInitialRegExpPrototype(implicit context: Context)(
    receiver: Object): bool {
  const nativeContext = LoadNativeContext(context);
  const regexpFun = LoadRegExpFunction(nativeContext);
  const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map);
  const initialPrototype: HeapObject = initialMap.prototype;
  return TaggedEqual(receiver, initialPrototype);
}

extern enum Flag constexpr 'JSRegExp::Flag' {
  kNone,
  kGlobal,
  kIgnoreCase,
  kMultiline,
  kSticky,
  kUnicode,
201
  kDotAll,
202
  kHasIndices,
203
  kLinear
204 205
}

206
const kNoCounterFlagGetter: constexpr int31 = -1;
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
const kRegExpPrototypeStickyGetter: constexpr int31
    generates 'v8::Isolate::kRegExpPrototypeStickyGetter';
const kRegExpPrototypeUnicodeGetter: constexpr int31
    generates 'v8::Isolate::kRegExpPrototypeUnicodeGetter';

extern macro RegExpBuiltinsAssembler::FastFlagGetter(
    JSRegExp, constexpr Flag): bool;
extern runtime IncrementUseCounter(Context, Smi): void;

macro FlagGetter(implicit context: Context)(
    receiver: Object, flag: constexpr Flag, counter: constexpr int31,
    methodName: constexpr string): JSAny {
  typeswitch (receiver) {
    case (receiver: JSRegExp): {
      return SelectBooleanConstant(FastFlagGetter(receiver, flag));
222
    }
223
    case (Object): {
224 225
    }
  }
226 227
  if (!IsReceiverInitialRegExpPrototype(receiver)) {
    ThrowTypeError(MessageTemplate::kRegExpNonRegExp, methodName);
228
  }
229 230
  if constexpr (counter != -1) {
    IncrementUseCounter(context, SmiConstant(counter));
231
  }
232 233
  return Undefined;
}
234

235 236 237 238 239
// ES6 21.2.5.4.
// ES #sec-get-regexp.prototype.global
transitioning javascript builtin RegExpPrototypeGlobalGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
240
      receiver, Flag::kGlobal, kNoCounterFlagGetter, 'RegExp.prototype.global');
241
}
242

243 244 245 246 247
// ES6 21.2.5.5.
// ES #sec-get-regexp.prototype.ignorecase
transitioning javascript builtin RegExpPrototypeIgnoreCaseGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
248
      receiver, Flag::kIgnoreCase, kNoCounterFlagGetter,
249 250
      'RegExp.prototype.ignoreCase');
}
251

252 253 254 255 256
// ES6 21.2.5.7.
// ES #sec-get-regexp.prototype.multiline
transitioning javascript builtin RegExpPrototypeMultilineGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
257
      receiver, Flag::kMultiline, kNoCounterFlagGetter,
258
      'RegExp.prototype.multiline');
259 260 261 262 263 264 265
}

transitioning javascript builtin RegExpPrototypeHasIndicesGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kHasIndices, kNoCounterFlagGetter,
      'RegExp.prototype.hasIndices');
266
}
267 268 269 270

transitioning javascript builtin RegExpPrototypeLinearGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
271
      receiver, Flag::kLinear, kNoCounterFlagGetter, 'RegExp.prototype.linear');
272
}
273

274 275 276 277
// ES #sec-get-regexp.prototype.dotAll
transitioning javascript builtin RegExpPrototypeDotAllGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
278
      receiver, Flag::kDotAll, kNoCounterFlagGetter, 'RegExp.prototype.dotAll');
279
}
280

281 282 283 284 285 286 287 288
// ES6 21.2.5.12.
// ES #sec-get-regexp.prototype.sticky
transitioning javascript builtin RegExpPrototypeStickyGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kSticky, kRegExpPrototypeStickyGetter,
      'RegExp.prototype.sticky');
}
289

290 291 292 293 294 295 296 297
// ES6 21.2.5.15.
// ES #sec-get-regexp.prototype.unicode
transitioning javascript builtin RegExpPrototypeUnicodeGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kUnicode, kRegExpPrototypeUnicodeGetter,
      'RegExp.prototype.unicode');
}
298

299 300 301
extern transitioning macro
RegExpBuiltinsAssembler::FlagsGetter(implicit context: Context)(
    Object, constexpr bool): String;
302

303 304 305 306 307 308 309 310 311
transitioning macro
FastFlagsGetter(implicit context: Context)(receiver: FastJSRegExp): String {
  return FlagsGetter(receiver, true);
}

transitioning macro SlowFlagsGetter(implicit context: Context)(receiver: JSAny):
    String {
  return FlagsGetter(receiver, false);
}
312

313 314 315 316 317 318 319 320 321 322 323 324 325 326
// ES #sec-get-regexp.prototype.flags
// TFJ(RegExpPrototypeFlagsGetter, 0, kReceiver) \
transitioning javascript builtin RegExpPrototypeFlagsGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): String {
  ThrowIfNotJSReceiver(
      receiver, MessageTemplate::kRegExpNonObject, 'RegExp.prototype.flags');

  // The check is strict because the following code relies on individual flag
  // getters on the regexp prototype (e.g.: global, sticky, ...). We don't
  // bother to check these individually.
  const fastRegexp = Cast<FastJSRegExp>(receiver)
      otherwise return SlowFlagsGetter(receiver);
  return FastFlagsGetter(fastRegexp);
}
327

328 329 330 331
extern transitioning macro RegExpBuiltinsAssembler::SlowLoadLastIndex(
    implicit context: Context)(JSAny): JSAny;
extern transitioning macro RegExpBuiltinsAssembler::SlowStoreLastIndex(
    implicit context: Context)(JSAny, JSAny): void;
332

333 334
extern macro RegExpBuiltinsAssembler::FastLoadLastIndex(JSRegExp): Smi;
extern macro RegExpBuiltinsAssembler::FastStoreLastIndex(JSRegExp, Smi): void;
335

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
@export
transitioning macro LoadLastIndex(implicit context: Context)(
    regexp: JSAny, isFastPath: constexpr bool): JSAny {
  return isFastPath ? FastLoadLastIndex(UnsafeCast<JSRegExp>(regexp)) :
                      SlowLoadLastIndex(regexp);
}

@export
transitioning macro LoadLastIndexAsLength(implicit context: Context)(
    regexp: JSRegExp, isFastPath: constexpr bool): Number {
  const lastIndex = LoadLastIndex(regexp, isFastPath);
  if (isFastPath) {
    // ToLength on a positive smi is a nop and can be skipped.
    return UnsafeCast<PositiveSmi>(lastIndex);
  } else {
    // Omit ToLength if last_index is a non-negative smi.
    typeswitch (lastIndex) {
      case (i: PositiveSmi): {
        return i;
      }
      case (o: JSAny): {
        return ToLength_Inline(o);
358 359 360
      }
    }
  }
361
}
362

363 364 365 366 367 368 369
@export
transitioning macro StoreLastIndex(implicit context: Context)(
    regexp: JSAny, value: Number, isFastPath: constexpr bool): void {
  if (isFastPath) {
    FastStoreLastIndex(UnsafeCast<JSRegExp>(regexp), UnsafeCast<Smi>(value));
  } else {
    SlowStoreLastIndex(regexp, value);
370
  }
371
}
372

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
extern macro RegExpBuiltinsAssembler::AdvanceStringIndex(
    String, Number, bool, constexpr bool): Number;
extern macro
RegExpBuiltinsAssembler::AdvanceStringIndexFast(String, Smi, bool): Smi;
extern macro
RegExpBuiltinsAssembler::AdvanceStringIndexSlow(String, Number, bool): Smi;

type UseCounterFeature extends int31
constexpr 'v8::Isolate::UseCounterFeature';
const kRegExpMatchIsTrueishOnNonJSRegExp: constexpr UseCounterFeature
    generates 'v8::Isolate::kRegExpMatchIsTrueishOnNonJSRegExp';
const kRegExpMatchIsFalseishOnJSRegExp: constexpr UseCounterFeature
    generates 'v8::Isolate::kRegExpMatchIsFalseishOnJSRegExp';
const kRegExpExecCalledOnSlowRegExp: constexpr UseCounterFeature
    generates 'v8::Isolate::kRegExpExecCalledOnSlowRegExp';

// ES#sec-isregexp IsRegExp ( argument )
@export
transitioning macro IsRegExp(implicit context: Context)(obj: JSAny): bool {
  const receiver = Cast<JSReceiver>(obj) otherwise return false;

  // Check @match.
  const value = GetProperty(receiver, MatchSymbolConstant());
  if (value == Undefined) {
    return Is<JSRegExp>(receiver);
  }
399

400 401 402 403 404
  assert(value != Undefined);
  // The common path. Symbol.match exists, equals the RegExpPrototypeMatch
  // function (and is thus trueish), and the receiver is a JSRegExp.
  if (ToBoolean(value)) {
    if (!Is<JSRegExp>(receiver)) {
405
      IncrementUseCounter(
406
          context, SmiConstant(kRegExpMatchIsTrueishOnNonJSRegExp));
407
    }
408
    return true;
409
  }
410

411 412 413
  assert(!ToBoolean(value));
  if (Is<JSRegExp>(receiver)) {
    IncrementUseCounter(context, SmiConstant(kRegExpMatchIsFalseishOnJSRegExp));
414
  }
415 416
  return false;
}
417

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
extern runtime RegExpInitializeAndCompile(
    Context, JSRegExp, String, String): JSAny;

@export
transitioning macro RegExpCreate(implicit context: Context)(
    nativeContext: NativeContext, maybeString: JSAny, flags: String): JSAny {
  const regexpFun = LoadRegExpFunction(nativeContext);
  const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map);
  return RegExpCreate(initialMap, maybeString, flags);
}

@export
transitioning macro RegExpCreate(implicit context: Context)(
    initialMap: Map, maybeString: JSAny, flags: String): JSAny {
  const pattern: String =
      maybeString == Undefined ? kEmptyString : ToString_Inline(maybeString);
  const regexp =
      UnsafeCast<JSRegExp>(AllocateFastOrSlowJSObjectFromMap(initialMap));
  return RegExpInitializeAndCompile(context, regexp, pattern, flags);
}
438
}