| // 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 { |
| |
| const kATOM: constexpr int31 |
| generates 'JSRegExp::ATOM'; |
| const kTagIndex: constexpr int31 |
| generates 'JSRegExp::kTagIndex'; |
| const kAtomPatternIndex: constexpr int31 |
| generates 'JSRegExp::kAtomPatternIndex'; |
| |
| extern transitioning macro RegExpBuiltinsAssembler::FlagGetter( |
| implicit context: Context)(Object, constexpr Flag, constexpr bool): bool; |
| |
| extern macro UnsafeLoadFixedArrayElement( |
| RegExpMatchInfo, constexpr int31): Object; |
| |
| transitioning macro RegExpPrototypeMatchBody(implicit context: Context)( |
| regexp: JSReceiver, string: String, isFastPath: constexpr bool): JSAny { |
| if constexpr (isFastPath) { |
| assert(Is<FastJSRegExp>(regexp)); |
| } |
| |
| const isGlobal: bool = FlagGetter(regexp, Flag::kGlobal, isFastPath); |
| |
| if (!isGlobal) { |
| return isFastPath ? RegExpPrototypeExecBodyFast(regexp, string) : |
| RegExpExec(regexp, string); |
| } |
| |
| assert(isGlobal); |
| const isUnicode: bool = FlagGetter(regexp, Flag::kUnicode, isFastPath); |
| |
| StoreLastIndex(regexp, 0, isFastPath); |
| |
| // Allocate an array to store the resulting match strings. |
| |
| let array = growable_fixed_array::NewGrowableFixedArray(); |
| |
| // Check if the regexp is an ATOM type. If so, then keep the literal string |
| // to search for so that we can avoid calling substring in the loop below. |
| let atom: bool = false; |
| let searchString: String = EmptyStringConstant(); |
| if constexpr (isFastPath) { |
| const maybeAtomRegexp = UnsafeCast<JSRegExp>(regexp); |
| const data = UnsafeCast<FixedArray>(maybeAtomRegexp.data); |
| if (UnsafeCast<Smi>(data.objects[kTagIndex]) == kATOM) { |
| searchString = UnsafeCast<String>(data.objects[kAtomPatternIndex]); |
| atom = true; |
| } |
| } |
| |
| while (true) { |
| let match: String = EmptyStringConstant(); |
| try { |
| if constexpr (isFastPath) { |
| // On the fast path, grab the matching string from the raw match index |
| // array. |
| const matchIndices: RegExpMatchInfo = |
| RegExpPrototypeExecBodyWithoutResultFast( |
| UnsafeCast<JSRegExp>(regexp), string) otherwise IfDidNotMatch; |
| if (atom) { |
| match = searchString; |
| } else { |
| const matchFrom = UnsafeLoadFixedArrayElement( |
| matchIndices, kRegExpMatchInfoFirstCaptureIndex); |
| const matchTo = UnsafeLoadFixedArrayElement( |
| matchIndices, kRegExpMatchInfoFirstCaptureIndex + 1); |
| match = SubString( |
| string, UnsafeCast<Smi>(matchFrom), UnsafeCast<Smi>(matchTo)); |
| } |
| } else { |
| assert(!isFastPath); |
| const resultTemp = RegExpExec(regexp, string); |
| if (resultTemp == Null) { |
| goto IfDidNotMatch; |
| } |
| match = ToString_Inline(GetProperty(resultTemp, SmiConstant(0))); |
| } |
| goto IfDidMatch; |
| } label IfDidNotMatch { |
| return array.length == 0 ? Null : array.ToJSArray(); |
| } label IfDidMatch { |
| // Store the match, growing the fixed array if needed. |
| |
| array.Push(match); |
| |
| // Advance last index if the match is the empty string. |
| const matchLength: Smi = match.length_smi; |
| if (matchLength != 0) { |
| continue; |
| } |
| let lastIndex = LoadLastIndex(regexp, isFastPath); |
| if constexpr (isFastPath) { |
| assert(TaggedIsPositiveSmi(lastIndex)); |
| } else { |
| lastIndex = ToLength_Inline(lastIndex); |
| } |
| |
| const newLastIndex: Number = AdvanceStringIndex( |
| string, UnsafeCast<Number>(lastIndex), isUnicode, isFastPath); |
| |
| if constexpr (isFastPath) { |
| // On the fast path, we can be certain that lastIndex can never be |
| // incremented to overflow the Smi range since the maximal string |
| // length is less than the maximal Smi value. |
| const kMaxStringLengthFitsSmi: constexpr bool = |
| kStringMaxLengthUintptr < kSmiMaxValue; |
| static_assert(kMaxStringLengthFitsSmi); |
| assert(TaggedIsPositiveSmi(newLastIndex)); |
| } |
| |
| StoreLastIndex(regexp, newLastIndex, isFastPath); |
| } |
| } |
| |
| VerifiedUnreachable(); |
| } |
| |
| transitioning macro FastRegExpPrototypeMatchBody(implicit context: Context)( |
| receiver: FastJSRegExp, string: String): JSAny { |
| return RegExpPrototypeMatchBody(receiver, string, true); |
| } |
| |
| transitioning macro SlowRegExpPrototypeMatchBody(implicit context: Context)( |
| receiver: JSReceiver, string: String): JSAny { |
| return RegExpPrototypeMatchBody(receiver, string, false); |
| } |
| |
| // Helper that skips a few initial checks. and assumes... |
| // 1) receiver is a "fast" RegExp |
| // 2) pattern is a string |
| transitioning builtin RegExpMatchFast(implicit context: Context)( |
| receiver: FastJSRegExp, string: String): JSAny { |
| return FastRegExpPrototypeMatchBody(receiver, string); |
| } |
| |
| // ES#sec-regexp.prototype-@@match |
| // RegExp.prototype [ @@match ] ( string ) |
| transitioning javascript builtin RegExpPrototypeMatch( |
| js-implicit context: NativeContext, receiver: JSAny)(string: JSAny): JSAny { |
| ThrowIfNotJSReceiver( |
| receiver, MessageTemplate::kIncompatibleMethodReceiver, |
| 'RegExp.prototype.@@match'); |
| const receiver = UnsafeCast<JSReceiver>(receiver); |
| const string: String = ToString_Inline(string); |
| |
| // Strict: Reads global and unicode properties. |
| // TODO(jgruber): Handle slow flag accesses on the fast path and make this |
| // permissive. |
| const fastRegExp = Cast<FastJSRegExp>(receiver) |
| otherwise return SlowRegExpPrototypeMatchBody(receiver, string); |
| |
| // TODO(pwong): Could be optimized to remove the overhead of calling the |
| // builtin (at the cost of a larger builtin). |
| return RegExpMatchFast(fastRegExp, string); |
| } |
| } |