| // 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 { |
| |
| 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; |
| } |
| |
| extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive( |
| implicit context: Context)(HeapObject): never labels IsFast, |
| IsSlow; |
| |
| @export |
| macro IsFastRegExpPermissive(implicit context: Context)(o: HeapObject): bool { |
| BranchIfFastRegExp_Permissive(o) otherwise return true, return false; |
| } |
| |
| // 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, ''); |
| } |
| return result; |
| } |
| case (Object): { |
| const regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError( |
| MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec', |
| receiver); |
| return RegExpPrototypeExecSlow(regexp, string); |
| } |
| } |
| } |
| |
| 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)); |
| } |
| |
| let lastIndex = regexpLastIndex; |
| |
| // 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; |
| |
| // Grab and possibly update last index. |
| if (shouldUpdateLastIndex) { |
| if (!TaggedIsSmi(lastIndex) || (lastIndex > string.length_smi)) { |
| StoreLastIndex(regexp, SmiConstant(0), isFastPath); |
| goto IfDidNotMatch; |
| } |
| } else { |
| lastIndex = SmiConstant(0); |
| } |
| |
| const lastMatchInfo: RegExpMatchInfo = GetRegExpLastMatchInfo(); |
| |
| const matchIndices = |
| RegExpExecInternal(regexp, string, lastIndex, lastMatchInfo); |
| |
| // {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); |
| } |
| return matchIndicesRegExpMatchInfo; |
| } |
| if (shouldUpdateLastIndex) { |
| StoreLastIndex(regexp, SmiConstant(0), isFastPath); |
| } |
| goto IfDidNotMatch; |
| } |
| |
| @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; |
| } |
| |
| transitioning macro RegExpPrototypeExecBodyWithoutResultFast( |
| implicit context: Context)( |
| regexp: JSRegExp, string: String, |
| lastIndex: Number): RegExpMatchInfo labels IfDidNotMatch { |
| return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true) |
| otherwise IfDidNotMatch; |
| } |
| |
| // 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); |
| } |
| const lastIndex = LoadLastIndexAsLength(regexp, isFastPath); |
| const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult( |
| regexp, string, lastIndex, isFastPath) otherwise return Null; |
| return ConstructNewResultFromMatchInfo( |
| regexp, matchIndices, string, lastIndex); |
| } |
| |
| macro LoadRegExpFunction(nativeContext: NativeContext): JSFunction { |
| return *NativeContextSlot(nativeContext, ContextSlot::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, |
| kLinear |
| } |
| |
| 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)); |
| } |
| case (Object): { |
| } |
| } |
| if (!IsReceiverInitialRegExpPrototype(receiver)) { |
| ThrowTypeError(MessageTemplate::kRegExpNonRegExp, methodName); |
| } |
| if constexpr (counter != -1) { |
| IncrementUseCounter(context, SmiConstant(counter)); |
| } |
| return Undefined; |
| } |
| |
| // 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'); |
| } |
| |
| // 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'); |
| } |
| |
| // 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'); |
| } |
| |
| transitioning javascript builtin RegExpPrototypeLinearGetter( |
| js-implicit context: NativeContext, receiver: JSAny)(): JSAny { |
| return FlagGetter( |
| receiver, Flag::kLinear, kRegExpPrototypeOldFlagGetter, |
| 'RegExp.prototype.linear'); |
| } |
| |
| // 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'); |
| } |
| |
| // 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'); |
| } |
| |
| // 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'); |
| } |
| |
| extern transitioning macro |
| RegExpBuiltinsAssembler::FlagsGetter(implicit context: Context)( |
| Object, constexpr bool): String; |
| |
| 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); |
| } |
| |
| // 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); |
| } |
| |
| extern transitioning macro RegExpBuiltinsAssembler::SlowLoadLastIndex( |
| implicit context: Context)(JSAny): JSAny; |
| extern transitioning macro RegExpBuiltinsAssembler::SlowStoreLastIndex( |
| implicit context: Context)(JSAny, JSAny): void; |
| |
| extern macro RegExpBuiltinsAssembler::FastLoadLastIndex(JSRegExp): Smi; |
| extern macro RegExpBuiltinsAssembler::FastStoreLastIndex(JSRegExp, Smi): void; |
| |
| @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); |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| 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); |
| } |
| |
| 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)) { |
| IncrementUseCounter( |
| context, SmiConstant(kRegExpMatchIsTrueishOnNonJSRegExp)); |
| } |
| return true; |
| } |
| |
| assert(!ToBoolean(value)); |
| if (Is<JSRegExp>(receiver)) { |
| IncrementUseCounter(context, SmiConstant(kRegExpMatchIsFalseishOnJSRegExp)); |
| } |
| return false; |
| } |
| |
| 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); |
| } |
| } |