regexp.tq 14.6 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
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;
}
15

16 17 18
extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive(
    implicit context: Context)(HeapObject): never labels IsFast,
    IsSlow;
19

20 21 22 23
@export
macro IsFastRegExpPermissive(implicit context: Context)(o: HeapObject): bool {
  BranchIfFastRegExp_Permissive(o) otherwise return true, return false;
}
24

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// 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, '');
41
      }
42
      return result;
43
    }
44 45 46 47 48
    case (Object): {
      const regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError(
          MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec',
          receiver);
      return RegExpPrototypeExecSlow(regexp, string);
49
    }
50 51
  }
}
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo(
    implicit context: Context)(
    JSRegExp, RegExpMatchInfo, String, Number): JSRegExpResult;

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));
  }
79

80
  let lastIndex = regexpLastIndex;
81

82 83 84 85 86 87
  // 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;
88

89 90 91
  // Grab and possibly update last index.
  if (shouldUpdateLastIndex) {
    if (!TaggedIsSmi(lastIndex) || (lastIndex > string.length_smi)) {
92
      StoreLastIndex(regexp, SmiConstant(0), isFastPath);
93
      goto IfDidNotMatch;
94
    }
95 96
  } else {
    lastIndex = SmiConstant(0);
97 98
  }

99
  const lastMatchInfo: RegExpMatchInfo = GetRegExpLastMatchInfo();
100

101 102
  const matchIndices =
      RegExpExecInternal(regexp, string, lastIndex, lastMatchInfo);
103

104 105 106 107 108 109 110 111 112
  // {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);
113
    }
114
    return matchIndicesRegExpMatchInfo;
115
  }
116 117
  if (shouldUpdateLastIndex) {
    StoreLastIndex(regexp, SmiConstant(0), isFastPath);
118
  }
119 120
  goto IfDidNotMatch;
}
121

122 123 124 125 126 127 128 129
@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;
}
130

131 132 133 134 135 136 137
transitioning macro RegExpPrototypeExecBodyWithoutResultFast(
    implicit context: Context)(
    regexp: JSRegExp, string: String,
    lastIndex: Number): RegExpMatchInfo labels IfDidNotMatch {
  return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true)
      otherwise IfDidNotMatch;
}
138

139 140 141 142 143 144 145 146 147 148 149
// 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);
150
  }
151 152 153 154 155 156
  const lastIndex = LoadLastIndexAsLength(regexp, isFastPath);
  const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult(
      regexp, string, lastIndex, isFastPath) otherwise return Null;
  return ConstructNewResultFromMatchInfo(
      regexp, matchIndices, string, lastIndex);
}
157

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 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 201 202 203 204 205 206 207 208 209
macro LoadRegExpFunction(implicit context: Context)(
    nativeContext: NativeContext): JSFunction {
  return UnsafeCast<JSFunction>(
      nativeContext[NativeContextSlot::REGEXP_FUNCTION_INDEX]);
}

// 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,
  kDotAll,
  kInvalid
}

const kRegExpPrototypeOldFlagGetter: constexpr int31
    generates 'v8::Isolate::kRegExpPrototypeOldFlagGetter';
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));
210
    }
211
    case (Object): {
212 213
    }
  }
214 215
  if (!IsReceiverInitialRegExpPrototype(receiver)) {
    ThrowTypeError(MessageTemplate::kRegExpNonRegExp, methodName);
216
  }
217 218
  if constexpr (counter != -1) {
    IncrementUseCounter(context, SmiConstant(counter));
219
  }
220 221
  return Undefined;
}
222

223 224 225 226 227 228 229 230
// ES6 21.2.5.4.
// ES #sec-get-regexp.prototype.global
transitioning javascript builtin RegExpPrototypeGlobalGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kGlobal, kRegExpPrototypeOldFlagGetter,
      'RegExp.prototype.global');
}
231

232 233 234 235 236 237 238 239
// ES6 21.2.5.5.
// ES #sec-get-regexp.prototype.ignorecase
transitioning javascript builtin RegExpPrototypeIgnoreCaseGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kIgnoreCase, kRegExpPrototypeOldFlagGetter,
      'RegExp.prototype.ignoreCase');
}
240

241 242 243 244 245 246 247 248
// ES6 21.2.5.7.
// ES #sec-get-regexp.prototype.multiline
transitioning javascript builtin RegExpPrototypeMultilineGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return FlagGetter(
      receiver, Flag::kMultiline, kRegExpPrototypeOldFlagGetter,
      'RegExp.prototype.multiline');
}
249

250 251 252 253 254 255 256
// ES #sec-get-regexp.prototype.dotAll
transitioning javascript builtin RegExpPrototypeDotAllGetter(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  const kNoCounter: constexpr int31 = -1;
  return FlagGetter(
      receiver, Flag::kDotAll, kNoCounter, 'RegExp.prototype.dotAll');
}
257

258 259 260 261 262 263 264 265
// 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');
}
266

267 268 269 270 271 272 273 274
// 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');
}
275

276 277 278
extern transitioning macro
RegExpBuiltinsAssembler::FlagsGetter(implicit context: Context)(
    Object, constexpr bool): String;
279

280 281 282 283 284 285 286 287 288
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);
}
289

290 291 292 293 294 295 296 297 298 299 300 301 302 303
// 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);
}
304

305 306 307 308
extern transitioning macro RegExpBuiltinsAssembler::SlowLoadLastIndex(
    implicit context: Context)(JSAny): JSAny;
extern transitioning macro RegExpBuiltinsAssembler::SlowStoreLastIndex(
    implicit context: Context)(JSAny, JSAny): void;
309

310 311
extern macro RegExpBuiltinsAssembler::FastLoadLastIndex(JSRegExp): Smi;
extern macro RegExpBuiltinsAssembler::FastStoreLastIndex(JSRegExp, Smi): void;
312

313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
@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);
335 336 337
      }
    }
  }
338
}
339

340 341 342 343 344 345 346
@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);
347
  }
348
}
349

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
extern builtin
StringIndexOf(implicit context: Context)(String, String, Smi): Smi;

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 kRegExpPrototypeSourceGetter: constexpr UseCounterFeature
    generates 'v8::Isolate::kRegExpPrototypeSourceGetter';
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);
  }
381

382 383 384 385 386
  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)) {
387
      IncrementUseCounter(
388
          context, SmiConstant(kRegExpMatchIsTrueishOnNonJSRegExp));
389
    }
390
    return true;
391
  }
392

393 394 395
  assert(!ToBoolean(value));
  if (Is<JSRegExp>(receiver)) {
    IncrementUseCounter(context, SmiConstant(kRegExpMatchIsFalseishOnJSRegExp));
396
  }
397 398
  return false;
}
399

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
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);
}
420
}