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