1// Copyright 2019 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include 'src/builtins/builtins-regexp-gen.h' 6 7namespace regexp { 8 9extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForMatch( 10 implicit context: Context)(HeapObject): never labels IsFast, 11 IsSlow; 12macro IsFastRegExpForMatch(implicit context: Context)(o: HeapObject): bool { 13 BranchIfFastRegExpForMatch(o) otherwise return true, return false; 14} 15 16extern macro RegExpBuiltinsAssembler::BranchIfFastRegExpForSearch( 17 implicit context: Context)(HeapObject): never labels IsFast, 18 IsSlow; 19macro IsFastRegExpForSearch(implicit context: Context)(o: HeapObject): bool { 20 BranchIfFastRegExpForSearch(o) otherwise return true, return false; 21} 22 23extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Strict( 24 implicit context: Context)(HeapObject): never labels IsFast, 25 IsSlow; 26macro IsFastRegExpStrict(implicit context: Context)(o: HeapObject): bool { 27 BranchIfFastRegExp_Strict(o) otherwise return true, return false; 28} 29 30extern macro RegExpBuiltinsAssembler::BranchIfFastRegExp_Permissive( 31 implicit context: Context)(HeapObject): never labels IsFast, 32 IsSlow; 33 34@export 35macro IsFastRegExpPermissive(implicit context: Context)(o: HeapObject): bool { 36 BranchIfFastRegExp_Permissive(o) otherwise return true, return false; 37} 38 39// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) 40@export 41transitioning macro RegExpExec(implicit context: Context)( 42 receiver: JSReceiver, string: String): JSAny { 43 // Take the slow path of fetching the exec property, calling it, and 44 // verifying its return value. 45 46 const exec = GetProperty(receiver, 'exec'); 47 48 // Is {exec} callable? 49 typeswitch (exec) { 50 case (execCallable: Callable): { 51 const result = Call(context, execCallable, receiver, string); 52 if (result != Null) { 53 ThrowIfNotJSReceiver( 54 result, MessageTemplate::kInvalidRegExpExecResult, ''); 55 } 56 return result; 57 } 58 case (Object): { 59 const regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError( 60 MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec', 61 receiver); 62 return RegExpPrototypeExecSlow(regexp, string); 63 } 64 } 65} 66 67extern macro RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( 68 implicit context: Context)(JSRegExp, RegExpMatchInfo, String, Number): 69 JSRegExpResult|JSRegExpResultWithIndices; 70 71const kGlobalOrSticky: constexpr int31 72 generates 'JSRegExp::kGlobal | JSRegExp::kSticky'; 73 74extern macro RegExpBuiltinsAssembler::RegExpExecInternal( 75 implicit context: Context)( 76 JSRegExp, String, Number, RegExpMatchInfo): HeapObject; 77 78// ES#sec-regexp.prototype.exec 79// RegExp.prototype.exec ( string ) 80// Implements the core of RegExp.prototype.exec but without actually 81// constructing the JSRegExpResult. Returns a fixed array containing match 82// indices as returned by RegExpExecStub on successful match, and jumps to 83// IfDidNotMatch otherwise. 84transitioning macro RegExpPrototypeExecBodyWithoutResult( 85 implicit context: Context)( 86 regexp: JSRegExp, string: String, regexpLastIndex: Number, 87 isFastPath: constexpr bool): RegExpMatchInfo labels IfDidNotMatch { 88 if (isFastPath) { 89 dcheck(HasInitialRegExpMap(regexp)); 90 } else { 91 IncrementUseCounter(context, SmiConstant(kRegExpExecCalledOnSlowRegExp)); 92 } 93 94 let lastIndex = regexpLastIndex; 95 96 // Check whether the regexp is global or sticky, which determines whether we 97 // update last index later on. 98 const flags = UnsafeCast<Smi>(regexp.flags); 99 const isGlobalOrSticky: intptr = 100 SmiUntag(flags) & IntPtrConstant(kGlobalOrSticky); 101 const shouldUpdateLastIndex: bool = isGlobalOrSticky != 0; 102 103 // Grab and possibly update last index. 104 if (shouldUpdateLastIndex) { 105 if (!TaggedIsSmi(lastIndex) || (lastIndex > string.length_smi)) { 106 StoreLastIndex(regexp, SmiConstant(0), isFastPath); 107 goto IfDidNotMatch; 108 } 109 } else { 110 lastIndex = SmiConstant(0); 111 } 112 113 const lastMatchInfo: RegExpMatchInfo = GetRegExpLastMatchInfo(); 114 115 const matchIndices = 116 RegExpExecInternal(regexp, string, lastIndex, lastMatchInfo); 117 118 // {match_indices} is either null or the RegExpMatchInfo array. 119 // Return early if exec failed, possibly updating last index. 120 if (matchIndices != Null) { 121 const matchIndicesRegExpMatchInfo = 122 UnsafeCast<RegExpMatchInfo>(matchIndices); 123 if (shouldUpdateLastIndex) { 124 // Update the new last index from {match_indices}. 125 const newLastIndex: Smi = matchIndicesRegExpMatchInfo.GetEndOfCapture(0); 126 StoreLastIndex(regexp, newLastIndex, isFastPath); 127 } 128 return matchIndicesRegExpMatchInfo; 129 } 130 if (shouldUpdateLastIndex) { 131 StoreLastIndex(regexp, SmiConstant(0), isFastPath); 132 } 133 goto IfDidNotMatch; 134} 135 136@export 137transitioning macro RegExpPrototypeExecBodyWithoutResultFast( 138 implicit context: Context)(regexp: JSRegExp, string: String): 139 RegExpMatchInfo labels IfDidNotMatch { 140 const lastIndex = LoadLastIndexAsLength(regexp, true); 141 return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true) 142 otherwise IfDidNotMatch; 143} 144 145transitioning macro RegExpPrototypeExecBodyWithoutResultFast( 146 implicit context: Context)( 147 regexp: JSRegExp, string: String, 148 lastIndex: Number): RegExpMatchInfo labels IfDidNotMatch { 149 return RegExpPrototypeExecBodyWithoutResult(regexp, string, lastIndex, true) 150 otherwise IfDidNotMatch; 151} 152 153// ES#sec-regexp.prototype.exec 154// RegExp.prototype.exec ( string ) 155transitioning macro RegExpPrototypeExecBody(implicit context: Context)( 156 receiver: JSReceiver, string: String, isFastPath: constexpr bool): JSAny { 157 let regexp: JSRegExp; 158 if constexpr (isFastPath) { 159 regexp = UnsafeCast<JSRegExp>(receiver); 160 } else { 161 regexp = Cast<JSRegExp>(receiver) otherwise ThrowTypeError( 162 MessageTemplate::kIncompatibleMethodReceiver, 'RegExp.prototype.exec', 163 receiver); 164 } 165 const lastIndex = LoadLastIndexAsLength(regexp, isFastPath); 166 const matchIndices: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResult( 167 regexp, string, lastIndex, isFastPath) otherwise return Null; 168 return ConstructNewResultFromMatchInfo( 169 regexp, matchIndices, string, lastIndex); 170} 171 172macro LoadRegExpFunction(nativeContext: NativeContext): JSFunction { 173 return *NativeContextSlot(nativeContext, ContextSlot::REGEXP_FUNCTION_INDEX); 174} 175 176// Note this doesn't guarantee const-ness of object properties, just 177// unchanged object layout. 178macro HasInitialRegExpMap(implicit context: Context)(o: HeapObject): bool { 179 const nativeContext = LoadNativeContext(context); 180 const function = LoadRegExpFunction(nativeContext); 181 const initialMap = UnsafeCast<Map>(function.prototype_or_initial_map); 182 return initialMap == o.map; 183} 184 185macro IsReceiverInitialRegExpPrototype(implicit context: Context)( 186 receiver: Object): bool { 187 const nativeContext = LoadNativeContext(context); 188 const regexpFun = LoadRegExpFunction(nativeContext); 189 const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map); 190 const initialPrototype: HeapObject = initialMap.prototype; 191 return TaggedEqual(receiver, initialPrototype); 192} 193 194extern enum Flag constexpr 'JSRegExp::Flag' { 195 kNone, 196 kGlobal, 197 kIgnoreCase, 198 kMultiline, 199 kSticky, 200 kUnicode, 201 kDotAll, 202 kHasIndices, 203 kLinear 204} 205 206const kNoCounterFlagGetter: constexpr int31 = -1; 207const kRegExpPrototypeStickyGetter: constexpr int31 208 generates 'v8::Isolate::kRegExpPrototypeStickyGetter'; 209const kRegExpPrototypeUnicodeGetter: constexpr int31 210 generates 'v8::Isolate::kRegExpPrototypeUnicodeGetter'; 211 212extern macro RegExpBuiltinsAssembler::FastFlagGetter( 213 JSRegExp, constexpr Flag): bool; 214extern runtime IncrementUseCounter(Context, Smi): void; 215 216macro FlagGetter(implicit context: Context)( 217 receiver: Object, flag: constexpr Flag, counter: constexpr int31, 218 methodName: constexpr string): JSAny { 219 typeswitch (receiver) { 220 case (receiver: JSRegExp): { 221 return SelectBooleanConstant(FastFlagGetter(receiver, flag)); 222 } 223 case (Object): { 224 } 225 } 226 if (!IsReceiverInitialRegExpPrototype(receiver)) { 227 ThrowTypeError(MessageTemplate::kRegExpNonRegExp, methodName); 228 } 229 if constexpr (counter != -1) { 230 IncrementUseCounter(context, SmiConstant(counter)); 231 } 232 return Undefined; 233} 234 235// ES6 21.2.5.4. 236// ES #sec-get-regexp.prototype.global 237transitioning javascript builtin RegExpPrototypeGlobalGetter( 238 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 239 return FlagGetter( 240 receiver, Flag::kGlobal, kNoCounterFlagGetter, 'RegExp.prototype.global'); 241} 242 243// ES6 21.2.5.5. 244// ES #sec-get-regexp.prototype.ignorecase 245transitioning javascript builtin RegExpPrototypeIgnoreCaseGetter( 246 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 247 return FlagGetter( 248 receiver, Flag::kIgnoreCase, kNoCounterFlagGetter, 249 'RegExp.prototype.ignoreCase'); 250} 251 252// ES6 21.2.5.7. 253// ES #sec-get-regexp.prototype.multiline 254transitioning javascript builtin RegExpPrototypeMultilineGetter( 255 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 256 return FlagGetter( 257 receiver, Flag::kMultiline, kNoCounterFlagGetter, 258 'RegExp.prototype.multiline'); 259} 260 261transitioning javascript builtin RegExpPrototypeHasIndicesGetter( 262 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 263 return FlagGetter( 264 receiver, Flag::kHasIndices, kNoCounterFlagGetter, 265 'RegExp.prototype.hasIndices'); 266} 267 268transitioning javascript builtin RegExpPrototypeLinearGetter( 269 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 270 return FlagGetter( 271 receiver, Flag::kLinear, kNoCounterFlagGetter, 'RegExp.prototype.linear'); 272} 273 274// ES #sec-get-regexp.prototype.dotAll 275transitioning javascript builtin RegExpPrototypeDotAllGetter( 276 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 277 return FlagGetter( 278 receiver, Flag::kDotAll, kNoCounterFlagGetter, 'RegExp.prototype.dotAll'); 279} 280 281// ES6 21.2.5.12. 282// ES #sec-get-regexp.prototype.sticky 283transitioning javascript builtin RegExpPrototypeStickyGetter( 284 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 285 return FlagGetter( 286 receiver, Flag::kSticky, kRegExpPrototypeStickyGetter, 287 'RegExp.prototype.sticky'); 288} 289 290// ES6 21.2.5.15. 291// ES #sec-get-regexp.prototype.unicode 292transitioning javascript builtin RegExpPrototypeUnicodeGetter( 293 js-implicit context: NativeContext, receiver: JSAny)(): JSAny { 294 return FlagGetter( 295 receiver, Flag::kUnicode, kRegExpPrototypeUnicodeGetter, 296 'RegExp.prototype.unicode'); 297} 298 299extern transitioning macro 300RegExpBuiltinsAssembler::FlagsGetter(implicit context: Context)( 301 Object, constexpr bool): String; 302 303transitioning macro 304FastFlagsGetter(implicit context: Context)(receiver: FastJSRegExp): String { 305 return FlagsGetter(receiver, true); 306} 307 308transitioning macro SlowFlagsGetter(implicit context: Context)(receiver: JSAny): 309 String { 310 return FlagsGetter(receiver, false); 311} 312 313// ES #sec-get-regexp.prototype.flags 314// TFJ(RegExpPrototypeFlagsGetter, 0, kReceiver) \ 315transitioning javascript builtin RegExpPrototypeFlagsGetter( 316 js-implicit context: NativeContext, receiver: JSAny)(): String { 317 ThrowIfNotJSReceiver( 318 receiver, MessageTemplate::kRegExpNonObject, 'RegExp.prototype.flags'); 319 320 // The check is strict because the following code relies on individual flag 321 // getters on the regexp prototype (e.g.: global, sticky, ...). We don't 322 // bother to check these individually. 323 const fastRegexp = Cast<FastJSRegExp>(receiver) 324 otherwise return SlowFlagsGetter(receiver); 325 return FastFlagsGetter(fastRegexp); 326} 327 328extern transitioning macro RegExpBuiltinsAssembler::SlowLoadLastIndex( 329 implicit context: Context)(JSAny): JSAny; 330extern transitioning macro RegExpBuiltinsAssembler::SlowStoreLastIndex( 331 implicit context: Context)(JSAny, JSAny): void; 332 333extern macro RegExpBuiltinsAssembler::FastLoadLastIndex(JSRegExp): Smi; 334extern macro RegExpBuiltinsAssembler::FastStoreLastIndex(JSRegExp, Smi): void; 335 336@export 337transitioning macro LoadLastIndex(implicit context: Context)( 338 regexp: JSAny, isFastPath: constexpr bool): JSAny { 339 return isFastPath ? FastLoadLastIndex(UnsafeCast<JSRegExp>(regexp)) : 340 SlowLoadLastIndex(regexp); 341} 342 343@export 344transitioning macro LoadLastIndexAsLength(implicit context: Context)( 345 regexp: JSRegExp, isFastPath: constexpr bool): Number { 346 const lastIndex = LoadLastIndex(regexp, isFastPath); 347 if (isFastPath) { 348 // ToLength on a positive smi is a nop and can be skipped. 349 return UnsafeCast<PositiveSmi>(lastIndex); 350 } else { 351 // Omit ToLength if last_index is a non-negative smi. 352 typeswitch (lastIndex) { 353 case (i: PositiveSmi): { 354 return i; 355 } 356 case (o: JSAny): { 357 return ToLength_Inline(o); 358 } 359 } 360 } 361} 362 363@export 364transitioning macro StoreLastIndex(implicit context: Context)( 365 regexp: JSAny, value: Number, isFastPath: constexpr bool): void { 366 if (isFastPath) { 367 FastStoreLastIndex(UnsafeCast<JSRegExp>(regexp), UnsafeCast<Smi>(value)); 368 } else { 369 SlowStoreLastIndex(regexp, value); 370 } 371} 372 373extern macro RegExpBuiltinsAssembler::AdvanceStringIndex( 374 String, Number, bool, constexpr bool): Number; 375extern macro 376RegExpBuiltinsAssembler::AdvanceStringIndexFast(String, Smi, bool): Smi; 377extern macro 378RegExpBuiltinsAssembler::AdvanceStringIndexSlow(String, Number, bool): Smi; 379 380type UseCounterFeature extends int31 381constexpr 'v8::Isolate::UseCounterFeature'; 382const kRegExpMatchIsTrueishOnNonJSRegExp: constexpr UseCounterFeature 383 generates 'v8::Isolate::kRegExpMatchIsTrueishOnNonJSRegExp'; 384const kRegExpMatchIsFalseishOnJSRegExp: constexpr UseCounterFeature 385 generates 'v8::Isolate::kRegExpMatchIsFalseishOnJSRegExp'; 386const kRegExpExecCalledOnSlowRegExp: constexpr UseCounterFeature 387 generates 'v8::Isolate::kRegExpExecCalledOnSlowRegExp'; 388 389// ES#sec-isregexp IsRegExp ( argument ) 390@export 391transitioning macro IsRegExp(implicit context: Context)(obj: JSAny): bool { 392 const receiver = Cast<JSReceiver>(obj) otherwise return false; 393 394 // Check @match. 395 const value = GetProperty(receiver, MatchSymbolConstant()); 396 if (value == Undefined) { 397 return Is<JSRegExp>(receiver); 398 } 399 400 dcheck(value != Undefined); 401 // The common path. Symbol.match exists, equals the RegExpPrototypeMatch 402 // function (and is thus trueish), and the receiver is a JSRegExp. 403 if (ToBoolean(value)) { 404 if (!Is<JSRegExp>(receiver)) { 405 IncrementUseCounter( 406 context, SmiConstant(kRegExpMatchIsTrueishOnNonJSRegExp)); 407 } 408 return true; 409 } 410 411 dcheck(!ToBoolean(value)); 412 if (Is<JSRegExp>(receiver)) { 413 IncrementUseCounter(context, SmiConstant(kRegExpMatchIsFalseishOnJSRegExp)); 414 } 415 return false; 416} 417 418extern runtime RegExpInitializeAndCompile( 419 Context, JSRegExp, String, String): JSAny; 420 421@export 422transitioning macro RegExpCreate(implicit context: Context)( 423 nativeContext: NativeContext, maybeString: JSAny, flags: String): JSAny { 424 const regexpFun = LoadRegExpFunction(nativeContext); 425 const initialMap = UnsafeCast<Map>(regexpFun.prototype_or_initial_map); 426 return RegExpCreate(initialMap, maybeString, flags); 427} 428 429@export 430transitioning macro RegExpCreate(implicit context: Context)( 431 initialMap: Map, maybeString: JSAny, flags: String): JSAny { 432 const pattern: String = 433 maybeString == Undefined ? kEmptyString : ToString_Inline(maybeString); 434 const regexp = 435 UnsafeCast<JSRegExp>(AllocateFastOrSlowJSObjectFromMap(initialMap)); 436 return RegExpInitializeAndCompile(context, regexp, pattern, flags); 437} 438} 439