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