code = CAST(var_code.value()); Label if_success(this), if_exception(this, Label::kDeferred); { IncrementCounter(isolate()->counters()->regexp_entry_native(), 1); // Set up args for the final call into generated Irregexp code. MachineType type_int32 = MachineType::Int32(); MachineType type_tagged = MachineType::AnyTagged(); MachineType type_ptr = MachineType::Pointer(); // Result: A NativeRegExpMacroAssembler::Result return code. MachineType retval_type = type_int32; // Argument 0: Original subject string. MachineType arg0_type = type_tagged; TNode arg0 = string; // Argument 1: Previous index. MachineType arg1_type = type_int32; TNode arg1 = TruncateIntPtrToInt32(int_last_index); // Argument 2: Start of string data. MachineType arg2_type = type_ptr; TNode arg2 = var_string_start.value(); // Argument 3: End of string data. MachineType arg3_type = type_ptr; TNode arg3 = var_string_end.value(); // Argument 4: static offsets vector buffer. MachineType arg4_type = type_ptr; TNode arg4 = static_offsets_vector_address; // Argument 5: Set the number of capture registers to zero to force global // regexps to behave as non-global. This does not affect non-global // regexps. MachineType arg5_type = type_int32; TNode arg5 = Int32Constant(0); // Argument 6: Start (high end) of backtracking stack memory area. TNode stack_start = UncheckedCast( Load(MachineType::Pointer(), regexp_stack_memory_address_address)); TNode stack_size = UncheckedCast( Load(MachineType::IntPtr(), regexp_stack_memory_size_address)); TNode stack_end = ReinterpretCast(IntPtrAdd(stack_start, stack_size)); MachineType arg6_type = type_ptr; TNode arg6 = stack_end; // Argument 7: Indicate that this is a direct call from JavaScript. MachineType arg7_type = type_int32; TNode arg7 = Int32Constant(1); // Argument 8: Pass current isolate address. MachineType arg8_type = type_ptr; TNode arg8 = isolate_address; TNode code_entry = ReinterpretCast( IntPtrAdd(BitcastTaggedToWord(code), IntPtrConstant(Code::kHeaderSize - kHeapObjectTag))); TNode result = UncheckedCast(CallCFunction9( retval_type, arg0_type, arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, code_entry, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); // Check the result. // We expect exactly one result since we force the called regexp to behave // as non-global. TNode int_result = ChangeInt32ToIntPtr(result); GotoIf(IntPtrEqual(int_result, IntPtrConstant(NativeRegExpMacroAssembler::SUCCESS)), &if_success); GotoIf(IntPtrEqual(int_result, IntPtrConstant(NativeRegExpMacroAssembler::FAILURE)), &if_failure); GotoIf(IntPtrEqual(int_result, IntPtrConstant(NativeRegExpMacroAssembler::EXCEPTION)), &if_exception); CSA_ASSERT(this, IntPtrEqual(int_result, IntPtrConstant(NativeRegExpMacroAssembler::RETRY))); Goto(&runtime); } BIND(&if_success); { // Check that the last match info has space for the capture registers and // the additional information. Ensure no overflow in add. STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset); TNode available_slots = SmiSub(LoadFixedArrayBaseLength(match_info), SmiConstant(RegExpMatchInfo::kLastMatchOverhead)); TNode capture_count = CAST(LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureCountIndex)); // Calculate number of register_count = (capture_count + 1) * 2. TNode register_count = SmiShl(SmiAdd(capture_count, SmiConstant(1)), 1); GotoIf(SmiGreaterThan(register_count, available_slots), &runtime); // Fill match_info. StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex, register_count, SKIP_WRITE_BARRIER); StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex, string); StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex, string); // Fill match and capture offsets in match_info. { TNode limit_offset = ElementOffsetFromIndex( register_count, INT32_ELEMENTS, SMI_PARAMETERS, 0); TNode to_offset = ElementOffsetFromIndex( IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), PACKED_ELEMENTS, INTPTR_PARAMETERS, RegExpMatchInfo::kHeaderSize - kHeapObjectTag); TVARIABLE(IntPtrT, var_to_offset, to_offset); VariableList vars({&var_to_offset}, zone()); BuildFastLoop( vars, IntPtrZero(), limit_offset, [=, &var_to_offset](Node* offset) { TNode value = UncheckedCast(Load( MachineType::Int32(), static_offsets_vector_address, offset)); TNode smi_value = SmiFromInt32(value); StoreNoWriteBarrier(MachineRepresentation::kTagged, match_info, var_to_offset.value(), smi_value); Increment(&var_to_offset, kPointerSize); }, kInt32Size, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); } var_result = match_info; Goto(&out); } BIND(&if_failure); { var_result = NullConstant(); Goto(&out); } BIND(&if_exception); { // A stack overflow was detected in RegExp code. #ifdef DEBUG TNode pending_exception_address = ExternalConstant(ExternalReference::Create( IsolateAddressId::kPendingExceptionAddress, isolate())); CSA_ASSERT(this, IsTheHole(Load(MachineType::AnyTagged(), pending_exception_address))); #endif // DEBUG CallRuntime(Runtime::kThrowStackOverflow, context); Unreachable(); } BIND(&runtime); { var_result = CAST(CallRuntime(Runtime::kRegExpExec, context, regexp, string, last_index, match_info)); Goto(&out); } BIND(&atom); { // TODO(jgruber): A call with 4 args stresses register allocation, this // should probably just be inlined. var_result = CAST(CallBuiltin(Builtins::kRegExpExecAtom, context, regexp, string, last_index, match_info)); Goto(&out); } BIND(&out); return var_result.value(); #endif // V8_INTERPRETED_REGEXP } // 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 // if_didnotmatch otherwise. TNode RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( TNode context, TNode maybe_regexp, TNode string, Label* if_didnotmatch, const bool is_fastpath) { if (!is_fastpath) { ThrowIfNotInstanceType(context, maybe_regexp, JS_REGEXP_TYPE, "RegExp.prototype.exec"); } TNode regexp = CAST(maybe_regexp); TVARIABLE(HeapObject, var_result); Label out(this); // Load lastIndex. TVARIABLE(Number, var_lastindex); { TNode regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath); if (is_fastpath) { // ToLength on a positive smi is a nop and can be skipped. CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex)); var_lastindex = CAST(regexp_lastindex); } else { // Omit ToLength if lastindex is a non-negative smi. Label call_tolength(this, Label::kDeferred), is_smi(this), next(this); Branch(TaggedIsPositiveSmi(regexp_lastindex), &is_smi, &call_tolength); BIND(&call_tolength); var_lastindex = ToLength_Inline(context, regexp_lastindex); Goto(&next); BIND(&is_smi); var_lastindex = CAST(regexp_lastindex); Goto(&next); BIND(&next); } } // Check whether the regexp is global or sticky, which determines whether we // update last index later on. TNode flags = CAST(LoadObjectField(regexp, JSRegExp::kFlagsOffset)); TNode is_global_or_sticky = WordAnd( SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky)); TNode should_update_last_index = WordNotEqual(is_global_or_sticky, IntPtrZero()); // Grab and possibly update last index. Label run_exec(this); { Label if_doupdate(this), if_dontupdate(this); Branch(should_update_last_index, &if_doupdate, &if_dontupdate); BIND(&if_doupdate); { Label if_isoob(this, Label::kDeferred); GotoIfNot(TaggedIsSmi(var_lastindex.value()), &if_isoob); TNode string_length = LoadStringLengthAsSmi(string); GotoIfNot(SmiLessThanOrEqual(CAST(var_lastindex.value()), string_length), &if_isoob); Goto(&run_exec); BIND(&if_isoob); { StoreLastIndex(context, regexp, SmiZero(), is_fastpath); Goto(if_didnotmatch); } } BIND(&if_dontupdate); { var_lastindex = SmiZero(); Goto(&run_exec); } } TNode match_indices; Label successful_match(this); BIND(&run_exec); { // Get last match info from the context. TNode native_context = LoadNativeContext(context); TNode last_match_info = CAST(LoadContextElement( native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX)); // Call the exec stub. match_indices = RegExpExecInternal(context, regexp, string, var_lastindex.value(), last_match_info); var_result = match_indices; // {match_indices} is either null or the RegExpMatchInfo array. // Return early if exec failed, possibly updating last index. GotoIfNot(IsNull(match_indices), &successful_match); GotoIfNot(should_update_last_index, if_didnotmatch); StoreLastIndex(context, regexp, SmiZero(), is_fastpath); Goto(if_didnotmatch); } BIND(&successful_match); { GotoIfNot(should_update_last_index, &out); // Update the new last index from {match_indices}. TNode new_lastindex = CAST(LoadFixedArrayElement( CAST(match_indices), RegExpMatchInfo::kFirstCaptureIndex + 1)); StoreLastIndex(context, regexp, new_lastindex, is_fastpath); Goto(&out); } BIND(&out); return CAST(var_result.value()); } // ES#sec-regexp.prototype.exec // RegExp.prototype.exec ( string ) TNode RegExpBuiltinsAssembler::RegExpPrototypeExecBody( TNode context, TNode maybe_regexp, TNode string, const bool is_fastpath) { TVARIABLE(HeapObject, var_result); Label if_didnotmatch(this), out(this); TNode match_indices = RegExpPrototypeExecBodyWithoutResult( context, maybe_regexp, string, &if_didnotmatch, is_fastpath); // Successful match. { var_result = ConstructNewResultFromMatchInfo(context, maybe_regexp, match_indices, string); Goto(&out); } BIND(&if_didnotmatch); { var_result = NullConstant(); Goto(&out); } BIND(&out); return var_result.value(); } Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver( Node* context, Node* maybe_receiver, MessageTemplate::Template msg_template, char const* method_name) { Label out(this), throw_exception(this, Label::kDeferred); VARIABLE(var_value_map, MachineRepresentation::kTagged); GotoIf(TaggedIsSmi(maybe_receiver), &throw_exception); // Load the instance type of the {value}. var_value_map.Bind(LoadMap(maybe_receiver)); Node* const value_instance_type = LoadMapInstanceType(var_value_map.value()); Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception); // The {value} is not a compatible receiver for this method. BIND(&throw_exception); { Node* const value_str = CallBuiltin(Builtins::kToString, context, maybe_receiver); ThrowTypeError(context, msg_template, StringConstant(method_name), value_str); } BIND(&out); return var_value_map.value(); } Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context, Node* const object, Node* const map) { Label out(this); VARIABLE(var_result, MachineRepresentation::kWord32); #ifdef V8_ENABLE_FORCE_SLOW_PATH var_result.Bind(Int32Constant(0)); GotoIfForceSlowPath(&out); #endif Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); Node* const initial_map = LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); Node* const has_initialmap = WordEqual(map, initial_map); var_result.Bind(has_initialmap); GotoIfNot(has_initialmap, &out); // The smi check is required to omit ToLength(lastIndex) calls with possible // user-code execution on the fast path. Node* const last_index = FastLoadLastIndex(CAST(object)); var_result.Bind(TaggedIsPositiveSmi(last_index)); Goto(&out); BIND(&out); return var_result.value(); } // We also return true if exec is undefined (and hence per spec) // the original {exec} will be used. TNode RegExpBuiltinsAssembler::IsFastRegExpWithOriginalExec( TNode context, TNode object) { CSA_ASSERT(this, TaggedIsNotSmi(object)); Label out(this); Label check_last_index(this); TVARIABLE(BoolT, var_result); #ifdef V8_ENABLE_FORCE_SLOW_PATH var_result = BoolConstant(0); GotoIfForceSlowPath(&out); #endif TNode is_regexp = HasInstanceType(object, JS_REGEXP_TYPE); var_result = is_regexp; GotoIfNot(is_regexp, &out); TNode native_context = LoadNativeContext(context); TNode original_exec = LoadContextElement(native_context, Context::REGEXP_EXEC_FUNCTION_INDEX); TNode regexp_exec = GetProperty(context, object, isolate()->factory()->exec_string()); TNode has_initialexec = WordEqual(regexp_exec, original_exec); var_result = has_initialexec; GotoIf(has_initialexec, &check_last_index); TNode is_undefined = IsUndefined(regexp_exec); var_result = is_undefined; GotoIfNot(is_undefined, &out); Goto(&check_last_index); BIND(&check_last_index); // The smi check is required to omit ToLength(lastIndex) calls with possible // user-code execution on the fast path. TNode last_index = FastLoadLastIndex(object); var_result = TaggedIsPositiveSmi(last_index); Goto(&out); BIND(&out); return var_result.value(); } Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context, Node* const object) { CSA_ASSERT(this, TaggedIsNotSmi(object)); return IsFastRegExpNoPrototype(context, object, LoadMap(object)); } // RegExp fast path implementations rely on unmodified JSRegExp instances. // We use a fairly coarse granularity for this and simply check whether both // the regexp itself is unmodified (i.e. its map has not changed), its // prototype is unmodified, and lastIndex is a non-negative smi. void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, Node* const object, Node* const map, Label* const if_isunmodified, Label* const if_ismodified) { CSA_ASSERT(this, WordEqual(LoadMap(object), map)); GotoIfForceSlowPath(if_ismodified); // TODO(ishell): Update this check once map changes for constant field // tracking are landing. Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); Node* const initial_map = LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); Node* const has_initialmap = WordEqual(map, initial_map); GotoIfNot(has_initialmap, if_ismodified); Node* const initial_proto_initial_map = LoadContextElement(native_context, Context::REGEXP_PROTOTYPE_MAP_INDEX); Node* const proto_map = LoadMap(LoadMapPrototype(map)); Node* const proto_has_initialmap = WordEqual(proto_map, initial_proto_initial_map); GotoIfNot(proto_has_initialmap, if_ismodified); // The smi check is required to omit ToLength(lastIndex) calls with possible // user-code execution on the fast path. Node* const last_index = FastLoadLastIndex(CAST(object)); Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified); } void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context, Node* const object, Label* const if_isunmodified, Label* const if_ismodified) { CSA_ASSERT(this, TaggedIsNotSmi(object)); BranchIfFastRegExp(context, object, LoadMap(object), if_isunmodified, if_ismodified); } TNode RegExpBuiltinsAssembler::IsFastRegExp(SloppyTNode context, SloppyTNode object) { Label yup(this), nope(this), out(this); TVARIABLE(BoolT, var_result); BranchIfFastRegExp(context, object, &yup, &nope); BIND(&yup); var_result = Int32TrueConstant(); Goto(&out); BIND(&nope); var_result = Int32FalseConstant(); Goto(&out); BIND(&out); return var_result.value(); } void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* const context, Node* const object, Label* if_isunmodified, Label* if_ismodified) { // Could be a Smi. Node* const map = LoadReceiverMap(object); Node* const native_context = LoadNativeContext(context); Node* const initial_regexp_result_map = LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX); Branch(WordEqual(map, initial_regexp_result_map), if_isunmodified, if_ismodified); } // Slow path stub for RegExpPrototypeExec to decrease code size. TF_BUILTIN(RegExpPrototypeExecSlow, RegExpBuiltinsAssembler) { TNode regexp = CAST(Parameter(Descriptor::kReceiver)); TNode string = CAST(Parameter(Descriptor::kString)); TNode context = CAST(Parameter(Descriptor::kContext)); Return(RegExpPrototypeExecBody(context, regexp, string, false)); } // Fast path stub for ATOM regexps. String matching is done by StringIndexOf, // and {match_info} is updated on success. // The slow path is implemented in RegExpImpl::AtomExec. TF_BUILTIN(RegExpExecAtom, RegExpBuiltinsAssembler) { TNode regexp = CAST(Parameter(Descriptor::kRegExp)); TNode subject_string = CAST(Parameter(Descriptor::kString)); TNode last_index = CAST(Parameter(Descriptor::kLastIndex)); TNode match_info = CAST(Parameter(Descriptor::kMatchInfo)); TNode context = CAST(Parameter(Descriptor::kContext)); CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); TNode data = CAST(LoadObjectField(regexp, JSRegExp::kDataOffset)); CSA_ASSERT(this, SmiEqual(CAST(LoadFixedArrayElement(data, JSRegExp::kTagIndex)), SmiConstant(JSRegExp::ATOM))); // Callers ensure that last_index is in-bounds. CSA_ASSERT(this, UintPtrLessThanOrEqual(SmiUntag(last_index), LoadStringLengthAsWord(subject_string))); Node* const needle_string = LoadFixedArrayElement(data, JSRegExp::kAtomPatternIndex); CSA_ASSERT(this, IsString(needle_string)); TNode const match_from = CAST(CallBuiltin(Builtins::kStringIndexOf, context, subject_string, needle_string, last_index)); Label if_failure(this), if_success(this); Branch(SmiEqual(match_from, SmiConstant(-1)), &if_failure, &if_success); BIND(&if_success); { CSA_ASSERT(this, TaggedIsPositiveSmi(match_from)); CSA_ASSERT(this, UintPtrLessThan(SmiUntag(match_from), LoadStringLengthAsWord(subject_string))); const int kNumRegisters = 2; STATIC_ASSERT(RegExpMatchInfo::kInitialCaptureIndices >= kNumRegisters); TNode const match_to = SmiAdd(match_from, LoadStringLengthAsSmi(needle_string)); StoreFixedArrayElement(match_info, RegExpMatchInfo::kNumberOfCapturesIndex, SmiConstant(kNumRegisters), SKIP_WRITE_BARRIER); StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastSubjectIndex, subject_string); StoreFixedArrayElement(match_info, RegExpMatchInfo::kLastInputIndex, subject_string); StoreFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex, match_from, SKIP_WRITE_BARRIER); StoreFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex + 1, match_to, SKIP_WRITE_BARRIER); Return(match_info); } BIND(&if_failure); Return(NullConstant()); } TF_BUILTIN(RegExpExecInternal, RegExpBuiltinsAssembler) { TNode regexp = CAST(Parameter(Descriptor::kRegExp)); TNode string = CAST(Parameter(Descriptor::kString)); TNode last_index = CAST(Parameter(Descriptor::kLastIndex)); TNode match_info = CAST(Parameter(Descriptor::kMatchInfo)); TNode context = CAST(Parameter(Descriptor::kContext)); CSA_ASSERT(this, IsNumberNormalized(last_index)); CSA_ASSERT(this, IsNumberPositive(last_index)); Return(RegExpExecInternal(context, regexp, string, last_index, match_info)); } // ES#sec-regexp.prototype.exec // RegExp.prototype.exec ( string ) TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { TNode maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); TNode maybe_string = CAST(Parameter(Descriptor::kString)); TNode context = CAST(Parameter(Descriptor::kContext)); // Ensure {maybe_receiver} is a JSRegExp. ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec"); TNode receiver = CAST(maybe_receiver); // Convert {maybe_string} to a String. TNode string = ToString_Inline(context, maybe_string); Label if_isfastpath(this), if_isslowpath(this); Branch(IsFastRegExpNoPrototype(context, receiver), &if_isfastpath, &if_isslowpath); BIND(&if_isfastpath); Return(RegExpPrototypeExecBody(context, receiver, string, true)); BIND(&if_isslowpath); Return(CallBuiltin(Builtins::kRegExpPrototypeExecSlow, context, receiver, string)); } Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath) { Isolate* isolate = this->isolate(); TNode const int_one = IntPtrConstant(1); TVARIABLE(Smi, var_length, SmiZero()); TVARIABLE(IntPtrT, var_flags); // First, count the number of characters we will need and check which flags // are set. if (is_fastpath) { // Refer to JSRegExp's flag property on the fast-path. CSA_ASSERT(this, IsJSRegExp(regexp)); Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset); var_flags = SmiUntag(flags_smi); #define CASE_FOR_FLAG(FLAG) \ do { \ Label next(this); \ GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \ var_length = SmiAdd(var_length.value(), SmiConstant(1)); \ Goto(&next); \ BIND(&next); \ } while (false) CASE_FOR_FLAG(JSRegExp::kGlobal); CASE_FOR_FLAG(JSRegExp::kIgnoreCase); CASE_FOR_FLAG(JSRegExp::kMultiline); CASE_FOR_FLAG(JSRegExp::kDotAll); CASE_FOR_FLAG(JSRegExp::kUnicode); CASE_FOR_FLAG(JSRegExp::kSticky); #undef CASE_FOR_FLAG } else { DCHECK(!is_fastpath); // Fall back to GetProperty stub on the slow-path. var_flags = IntPtrZero(); #define CASE_FOR_FLAG(NAME, FLAG) \ do { \ Label next(this); \ Node* const flag = GetProperty( \ context, regexp, isolate->factory()->InternalizeUtf8String(NAME)); \ Label if_isflagset(this); \ BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \ BIND(&if_isflagset); \ var_length = SmiAdd(var_length.value(), SmiConstant(1)); \ var_flags = Signed(WordOr(var_flags.value(), IntPtrConstant(FLAG))); \ Goto(&next); \ BIND(&next); \ } while (false) CASE_FOR_FLAG("global", JSRegExp::kGlobal); CASE_FOR_FLAG("ignoreCase", JSRegExp::kIgnoreCase); CASE_FOR_FLAG("multiline", JSRegExp::kMultiline); CASE_FOR_FLAG("dotAll", JSRegExp::kDotAll); CASE_FOR_FLAG("unicode", JSRegExp::kUnicode); CASE_FOR_FLAG("sticky", JSRegExp::kSticky); #undef CASE_FOR_FLAG } // Allocate a string of the required length and fill it with the corresponding // char for each set flag. { Node* const result = AllocateSeqOneByteString(context, var_length.value()); VARIABLE(var_offset, MachineType::PointerRepresentation(), IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag)); #define CASE_FOR_FLAG(FLAG, CHAR) \ do { \ Label next(this); \ GotoIfNot(IsSetWord(var_flags.value(), FLAG), &next); \ Node* const value = Int32Constant(CHAR); \ StoreNoWriteBarrier(MachineRepresentation::kWord8, result, \ var_offset.value(), value); \ var_offset.Bind(IntPtrAdd(var_offset.value(), int_one)); \ Goto(&next); \ BIND(&next); \ } while (false) CASE_FOR_FLAG(JSRegExp::kGlobal, 'g'); CASE_FOR_FLAG(JSRegExp::kIgnoreCase, 'i'); CASE_FOR_FLAG(JSRegExp::kMultiline, 'm'); CASE_FOR_FLAG(JSRegExp::kDotAll, 's'); CASE_FOR_FLAG(JSRegExp::kUnicode, 'u'); CASE_FOR_FLAG(JSRegExp::kSticky, 'y'); #undef CASE_FOR_FLAG return result; } } // ES#sec-isregexp IsRegExp ( argument ) Node* RegExpBuiltinsAssembler::IsRegExp(Node* const context, Node* const maybe_receiver) { Label out(this), if_isregexp(this); VARIABLE(var_result, MachineRepresentation::kWord32, Int32Constant(0)); GotoIf(TaggedIsSmi(maybe_receiver), &out); GotoIfNot(IsJSReceiver(maybe_receiver), &out); Node* const receiver = maybe_receiver; // Check @@match. { Node* const value = GetProperty(context, receiver, isolate()->factory()->match_symbol()); Label match_isundefined(this), match_isnotundefined(this); Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined); BIND(&match_isundefined); Branch(IsJSRegExp(receiver), &if_isregexp, &out); BIND(&match_isnotundefined); BranchIfToBooleanIsTrue(value, &if_isregexp, &out); } BIND(&if_isregexp); var_result.Bind(Int32Constant(1)); Goto(&out); BIND(&out); return var_result.value(); } // ES#sec-regexpinitialize // Runtime Semantics: RegExpInitialize ( obj, pattern, flags ) Node* RegExpBuiltinsAssembler::RegExpInitialize(Node* const context, Node* const regexp, Node* const maybe_pattern, Node* const maybe_flags) { CSA_ASSERT(this, IsJSRegExp(regexp)); // Normalize pattern. TNode const pattern = Select( IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); }, [=] { return ToString_Inline(context, maybe_pattern); }); // Normalize flags. TNode const flags = Select( IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); }, [=] { return ToString_Inline(context, maybe_flags); }); // Initialize. return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, pattern, flags); } // ES #sec-get-regexp.prototype.flags TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { TNode maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); TNode context = CAST(Parameter(Descriptor::kContext)); TNode map = CAST(ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kRegExpNonObject, "RegExp.prototype.flags")); TNode receiver = CAST(maybe_receiver); Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); BranchIfFastRegExp(context, receiver, map, &if_isfastpath, &if_isslowpath); BIND(&if_isfastpath); Return(FlagsGetter(context, receiver, true)); BIND(&if_isslowpath); Return(FlagsGetter(context, receiver, false)); } // ES#sec-regexp-pattern-flags // RegExp ( pattern, flags ) TF_BUILTIN(RegExpConstructor, RegExpBuiltinsAssembler) { TNode pattern = CAST(Parameter(Descriptor::kPattern)); TNode flags = CAST(Parameter(Descriptor::kFlags)); TNode new_target = CAST(Parameter(Descriptor::kJSNewTarget)); TNode context = CAST(Parameter(Descriptor::kContext)); Isolate* isolate = this->isolate(); VARIABLE(var_flags, MachineRepresentation::kTagged, flags); VARIABLE(var_pattern, MachineRepresentation::kTagged, pattern); VARIABLE(var_new_target, MachineRepresentation::kTagged, new_target); Node* const native_context = LoadNativeContext(context); Node* const regexp_function = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); Node* const pattern_is_regexp = IsRegExp(context, pattern); { Label next(this); GotoIfNot(IsUndefined(new_target), &next); var_new_target.Bind(regexp_function); GotoIfNot(pattern_is_regexp, &next); GotoIfNot(IsUndefined(flags), &next); Node* const value = GetProperty(context, pattern, isolate->factory()->constructor_string()); GotoIfNot(WordEqual(value, regexp_function), &next); Return(pattern); BIND(&next); } { Label next(this), if_patternisfastregexp(this), if_patternisslowregexp(this); GotoIf(TaggedIsSmi(pattern), &next); GotoIf(IsJSRegExp(CAST(pattern)), &if_patternisfastregexp); Branch(pattern_is_regexp, &if_patternisslowregexp, &next); BIND(&if_patternisfastregexp); { Node* const source = LoadObjectField(CAST(pattern), JSRegExp::kSourceOffset); var_pattern.Bind(source); { Label inner_next(this); GotoIfNot(IsUndefined(flags), &inner_next); Node* const value = FlagsGetter(context, pattern, true); var_flags.Bind(value); Goto(&inner_next); BIND(&inner_next); } Goto(&next); } BIND(&if_patternisslowregexp); { { Node* const value = GetProperty(context, pattern, isolate->factory()->source_string()); var_pattern.Bind(value); } { Label inner_next(this); GotoIfNot(IsUndefined(flags), &inner_next); Node* const value = GetProperty(context, pattern, isolate->factory()->flags_string()); var_flags.Bind(value); Goto(&inner_next); BIND(&inner_next); } Goto(&next); } BIND(&next); } // Allocate. VARIABLE(var_regexp, MachineRepresentation::kTagged); { Label allocate_jsregexp(this), allocate_generic(this, Label::kDeferred), next(this); Branch(WordEqual(var_new_target.value(), regexp_function), &allocate_jsregexp, &allocate_generic); BIND(&allocate_jsregexp); { Node* const initial_map = LoadObjectField( regexp_function, JSFunction::kPrototypeOrInitialMapOffset); Node* const regexp = AllocateJSObjectFromMap(initial_map); var_regexp.Bind(regexp); Goto(&next); } BIND(&allocate_generic); { ConstructorBuiltinsAssembler constructor_assembler(this->state()); Node* const regexp = constructor_assembler.EmitFastNewObject( context, regexp_function, var_new_target.value()); var_regexp.Bind(regexp); Goto(&next); } BIND(&next); } Node* const result = RegExpInitialize(context, var_regexp.value(), var_pattern.value(), var_flags.value()); Return(result); } // ES#sec-regexp.prototype.compile // RegExp.prototype.compile ( pattern, flags ) TF_BUILTIN(RegExpPrototypeCompile, RegExpBuiltinsAssembler) { TNode maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); TNode maybe_pattern = CAST(Parameter(Descriptor::kPattern)); TNode maybe_flags = CAST(Parameter(Descriptor::kFlags)); TNode context = CAST(Parameter(Descriptor::kContext)); ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.compile"); Node* const receiver = maybe_receiver; VARIABLE(var_flags, MachineRepresentation::kTagged, maybe_flags); VARIABLE(var_pattern, MachineRepresentation::kTagged, maybe_pattern); // Handle a JSRegExp pattern. { Label next(this); GotoIf(TaggedIsSmi(maybe_pattern), &next); GotoIfNot(IsJSRegExp(CAST(maybe_pattern)), &next); Node* const pattern = maybe_pattern; // {maybe_flags} must be undefined in this case, otherwise throw. { Label next(this); GotoIf(IsUndefined(maybe_flags), &next); ThrowTypeError(context, MessageTemplate::kRegExpFlags); BIND(&next); } Node* const new_flags = FlagsGetter(context, pattern, true); Node* const new_pattern = LoadObjectField(pattern, JSRegExp::kSourceOffset); var_flags.Bind(new_flags); var_pattern.Bind(new_pattern); Goto(&next); BIND(&next); } Node* const result = RegExpInitialize(context, receiver, var_pattern.value(), var_flags.value()); Return(result); } // ES6 21.2.5.10. // ES #sec-get-regexp.prototype.source TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) { TNode receiver = CAST(Parameter(Descriptor::kReceiver)); TNode context = CAST(Parameter(Descriptor::kContext)); // Check whether we have an unmodified regexp instance. Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp); Branch(IsJSRegExp(CAST(receiver)), &if_isjsregexp, &if_isnotjsregexp); BIND(&if_isjsregexp); Return(LoadObjectField(CAST(receiver), JSRegExp::kSourceOffset)); BIND(&if_isnotjsregexp); { Isolate* isolate = this->isolate(); Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); Node* const initial_map = LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); Node* const initial_prototype = LoadMapPrototype(initial_map); Label if_isprototype(this), if_isnotprototype(this); Branch(WordEqual(receiver, initial_prototype), &if_isprototype, &if_isnotprototype); BIND(&if_isprototype); { const int counter = v8::Isolate::kRegExpPrototypeSourceGetter; Node* const counter_smi = SmiConstant(counter); CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); Node* const result = HeapConstant(isolate->factory()->NewStringFromAsciiChecked("(?:)")); Return(result); } BIND(&if_isnotprototype); { ThrowTypeError(context, MessageTemplate::kRegExpNonRegExp, "RegExp.prototype.source"); } } } // Fast-path implementation for flag checks on an unmodified JSRegExp instance. Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp, JSRegExp::Flag flag) { TNode const flags = CAST(LoadObjectField(regexp, JSRegExp::kFlagsOffset)); TNode const mask = SmiConstant(flag); return SmiToInt32(SmiAnd(flags, mask)); } // Load through the GetProperty stub. Node* RegExpBuiltinsAssembler::SlowFlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag) { Factory* factory = isolate()->factory(); Label out(this); VARIABLE(var_result, MachineRepresentation::kWord32); Handle name; switch (flag) { case JSRegExp::kGlobal: name = factory->global_string(); break; case JSRegExp::kIgnoreCase: name = factory->ignoreCase_string(); break; case JSRegExp::kMultiline: name = factory->multiline_string(); break; case JSRegExp::kDotAll: UNREACHABLE(); // Never called for dotAll. break; case JSRegExp::kSticky: name = factory->sticky_string(); break; case JSRegExp::kUnicode: name = factory->unicode_string(); break; default: UNREACHABLE(); } Node* const value = GetProperty(context, regexp, name); Label if_true(this), if_false(this); BranchIfToBooleanIsTrue(value, &if_true, &if_false); BIND(&if_true); { var_result.Bind(Int32Constant(1)); Goto(&out); } BIND(&if_false); { var_result.Bind(Int32Constant(0)); Goto(&out); } BIND(&out); return var_result.value(); } Node* RegExpBuiltinsAssembler::FlagGetter(Node* const context, Node* const regexp, JSRegExp::Flag flag, bool is_fastpath) { return is_fastpath ? FastFlagGetter(regexp, flag) : SlowFlagGetter(context, regexp, flag); } void RegExpBuiltinsAssembler::FlagGetter(Node* context, Node* receiver, JSRegExp::Flag flag, int counter, const char* method_name) { // Check whether we have an unmodified regexp instance. Label if_isunmodifiedjsregexp(this), if_isnotunmodifiedjsregexp(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp); Branch(IsJSRegExp(receiver), &if_isunmodifiedjsregexp, &if_isnotunmodifiedjsregexp); BIND(&if_isunmodifiedjsregexp); { // Refer to JSRegExp's flag property on the fast-path. Node* const is_flag_set = FastFlagGetter(receiver, flag); Return(SelectBooleanConstant(is_flag_set)); } BIND(&if_isnotunmodifiedjsregexp); { Node* const native_context = LoadNativeContext(context); Node* const regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); Node* const initial_map = LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset); Node* const initial_prototype = LoadMapPrototype(initial_map); Label if_isprototype(this), if_isnotprototype(this); Branch(WordEqual(receiver, initial_prototype), &if_isprototype, &if_isnotprototype); BIND(&if_isprototype); { if (counter != -1) { Node* const counter_smi = SmiConstant(counter); CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); } Return(UndefinedConstant()); } BIND(&if_isnotprototype); { ThrowTypeError(context, MessageTemplate::kRegExpNonRegExp, method_name); } } } // ES6 21.2.5.4. // ES #sec-get-regexp.prototype.global TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); FlagGetter(context, receiver, JSRegExp::kGlobal, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.global"); } // ES6 21.2.5.5. // ES #sec-get-regexp.prototype.ignorecase TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); FlagGetter(context, receiver, JSRegExp::kIgnoreCase, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.ignoreCase"); } // ES6 21.2.5.7. // ES #sec-get-regexp.prototype.multiline TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); FlagGetter(context, receiver, JSRegExp::kMultiline, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.multiline"); } // ES #sec-get-regexp.prototype.dotAll TF_BUILTIN(RegExpPrototypeDotAllGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); static const int kNoCounter = -1; FlagGetter(context, receiver, JSRegExp::kDotAll, kNoCounter, "RegExp.prototype.dotAll"); } // ES6 21.2.5.12. // ES #sec-get-regexp.prototype.sticky TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); FlagGetter(context, receiver, JSRegExp::kSticky, v8::Isolate::kRegExpPrototypeStickyGetter, "RegExp.prototype.sticky"); } // ES6 21.2.5.15. // ES #sec-get-regexp.prototype.unicode TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); FlagGetter(context, receiver, JSRegExp::kUnicode, v8::Isolate::kRegExpPrototypeUnicodeGetter, "RegExp.prototype.unicode"); } // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, Node* string) { VARIABLE(var_result, MachineRepresentation::kTagged); Label out(this); // Take the slow path of fetching the exec property, calling it, and // verifying its return value. // Get the exec property. Node* const exec = GetProperty(context, regexp, isolate()->factory()->exec_string()); // Is {exec} callable? Label if_iscallable(this), if_isnotcallable(this); GotoIf(TaggedIsSmi(exec), &if_isnotcallable); Node* const exec_map = LoadMap(exec); Branch(IsCallableMap(exec_map), &if_iscallable, &if_isnotcallable); BIND(&if_iscallable); { Callable call_callable = CodeFactory::Call(isolate()); Node* const result = CallJS(call_callable, context, exec, regexp, string); var_result.Bind(result); GotoIf(IsNull(result), &out); ThrowIfNotJSReceiver(context, result, MessageTemplate::kInvalidRegExpExecResult, ""); Goto(&out); } BIND(&if_isnotcallable); { ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, "RegExp.prototype.exec"); Node* const result = CallBuiltin(Builtins::kRegExpPrototypeExecSlow, context, regexp, string); var_result.Bind(result); Goto(&out); } BIND(&out); return var_result.value(); } // ES#sec-regexp.prototype.test // RegExp.prototype.test ( S ) TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) { TNode maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); TNode maybe_string = CAST(Parameter(Descriptor::kString)); TNode context = CAST(Parameter(Descriptor::kContext)); // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.test"); TNode receiver = CAST(maybe_receiver); // Convert {maybe_string} to a String. TNode string = ToString_Inline(context, maybe_string); Label fast_path(this), slow_path(this); BranchIfFastRegExp(context, receiver, &fast_path, &slow_path); BIND(&fast_path); { Label if_didnotmatch(this); RegExpPrototypeExecBodyWithoutResult(context, receiver, string, &if_didnotmatch, true); Return(TrueConstant()); BIND(&if_didnotmatch); Return(FalseConstant()); } BIND(&slow_path); { // Call exec. TNode match_indices = CAST(RegExpExec(context, receiver, string)); // Return true iff exec matched successfully. Return(SelectBooleanConstant(IsNotNull(match_indices))); } } TF_BUILTIN(RegExpPrototypeTestFast, RegExpBuiltinsAssembler) { TNode regexp = CAST(Parameter(Descriptor::kReceiver)); TNode string = CAST(Parameter(Descriptor::kString)); TNode context = CAST(Parameter(Descriptor::kContext)); Label if_didnotmatch(this); CSA_ASSERT(this, IsFastRegExpWithOriginalExec(context, regexp)); RegExpPrototypeExecBodyWithoutResult(context, regexp, string, &if_didnotmatch, true); Return(TrueConstant()); BIND(&if_didnotmatch); Return(FalseConstant()); } Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string, Node* const index, Node* const is_unicode, bool is_fastpath) { CSA_ASSERT(this, IsString(string)); CSA_ASSERT(this, IsNumberNormalized(index)); if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index)); // Default to last_index + 1. Node* const index_plus_one = NumberInc(index); VARIABLE(var_result, MachineRepresentation::kTagged, index_plus_one); // Advancing the index has some subtle issues involving the distinction // between Smis and HeapNumbers. There's three cases: // * {index} is a Smi, {index_plus_one} is a Smi. The standard case. // * {index} is a Smi, {index_plus_one} overflows into a HeapNumber. // In this case we can return the result early, because // {index_plus_one} > {string}.length. // * {index} is a HeapNumber, {index_plus_one} is a HeapNumber. This can only // occur when {index} is outside the Smi range since we normalize // explicitly. Again we can return early. if (is_fastpath) { // Must be in Smi range on the fast path. We control the value of {index} // on all call-sites and can never exceed the length of the string. STATIC_ASSERT(String::kMaxLength + 2 < Smi::kMaxValue); CSA_ASSERT(this, TaggedIsPositiveSmi(index_plus_one)); } Label if_isunicode(this), out(this); GotoIfNot(is_unicode, &out); // Keep this unconditional (even on the fast path) just to be safe. Branch(TaggedIsPositiveSmi(index_plus_one), &if_isunicode, &out); BIND(&if_isunicode); { TNode const string_length = LoadStringLengthAsWord(string); TNode untagged_plus_one = SmiUntag(index_plus_one); GotoIfNot(IntPtrLessThan(untagged_plus_one, string_length), &out); Node* const lead = StringCharCodeAt(string, SmiUntag(index)); GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)), Int32Constant(0xD800)), &out); Node* const trail = StringCharCodeAt(string, untagged_plus_one); GotoIfNot(Word32Equal(Word32And(trail, Int32Constant(0xFC00)), Int32Constant(0xDC00)), &out); // At a surrogate pair, return index + 2. Node* const index_plus_two = NumberInc(index_plus_one); var_result.Bind(index_plus_two); Goto(&out); } BIND(&out); return var_result.value(); } void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, Node* const regexp, TNode string, const bool is_fastpath) { if (is_fastpath) CSA_ASSERT(this, IsFastRegExp(context, regexp)); Node* const is_global = FlagGetter(context, regexp, JSRegExp::kGlobal, is_fastpath); Label if_isglobal(this), if_isnotglobal(this); Branch(is_global, &if_isglobal, &if_isnotglobal); BIND(&if_isnotglobal); { Node* const result = is_fastpath ? RegExpPrototypeExecBody(CAST(context), CAST(regexp), string, true) : RegExpExec(context, regexp, string); Return(result); } BIND(&if_isglobal); { Node* const is_unicode = FlagGetter(context, regexp, JSRegExp::kUnicode, is_fastpath); StoreLastIndex(context, regexp, SmiZero(), is_fastpath); // Allocate an array to store the resulting match strings. GrowableFixedArray array(state()); // Loop preparations. Within the loop, collect results from RegExpExec // and store match strings in the array. Variable* vars[] = {array.var_array(), array.var_length(), array.var_capacity()}; Label loop(this, 3, vars), out(this); Goto(&loop); BIND(&loop); { VARIABLE(var_match, MachineRepresentation::kTagged); Label if_didmatch(this), if_didnotmatch(this); if (is_fastpath) { // On the fast path, grab the matching string from the raw match index // array. TNode match_indices = RegExpPrototypeExecBodyWithoutResult(CAST(context), CAST(regexp), string, &if_didnotmatch, true); Node* const match_from = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex); Node* const match_to = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); var_match.Bind(CallBuiltin(Builtins::kSubString, context, string, match_from, match_to)); Goto(&if_didmatch); } else { DCHECK(!is_fastpath); Node* const result = RegExpExec(context, regexp, string); Label load_match(this); Branch(IsNull(result), &if_didnotmatch, &load_match); BIND(&load_match); var_match.Bind( ToString_Inline(context, GetProperty(context, result, SmiZero()))); Goto(&if_didmatch); } BIND(&if_didnotmatch); { // Return null if there were no matches, otherwise just exit the loop. GotoIfNot(IntPtrEqual(array.length(), IntPtrZero()), &out); Return(NullConstant()); } BIND(&if_didmatch); { Node* match = var_match.value(); // Store the match, growing the fixed array if needed. array.Push(CAST(match)); // Advance last index if the match is the empty string. TNode const match_length = LoadStringLengthAsSmi(match); GotoIfNot(SmiEqual(match_length, SmiZero()), &loop); Node* last_index = LoadLastIndex(CAST(context), CAST(regexp), is_fastpath); if (is_fastpath) { CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); } else { last_index = ToLength_Inline(context, last_index); } Node* const new_last_index = AdvanceStringIndex(string, last_index, is_unicode, is_fastpath); if (is_fastpath) { // 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. STATIC_ASSERT(String::kMaxLength < Smi::kMaxValue); CSA_ASSERT(this, TaggedIsPositiveSmi(new_last_index)); } StoreLastIndex(context, regexp, new_last_index, is_fastpath); Goto(&loop); } } BIND(&out); { // Wrap the match in a JSArray. Node* const result = array.ToJSArray(CAST(context)); Return(result); } } } // ES#sec-regexp.prototype-@@match // RegExp.prototype [ @@match ] ( string ) TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { TNode maybe_receiver = CAST(Parameter(Descriptor::kReceiver)); TNode maybe_string = CAST(Parameter(Descriptor::kString)); TNode context = CAST(Parameter(Descriptor::kContext)); // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.@@match"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. TNode const string = ToString_Inline(context, maybe_string); Label fast_path(this), slow_path(this); BranchIfFastRegExp(context, receiver, &fast_path, &slow_path); BIND(&fast_path); // TODO(pwong): Could be optimized to remove the overhead of calling the // builtin (at the cost of a larger builtin). Return(CallBuiltin(Builtins::kRegExpMatchFast, context, receiver, string)); BIND(&slow_path); RegExpPrototypeMatchBody(context, receiver, string, false); } TNode RegExpBuiltinsAssembler::MatchAllIterator( TNode context, TNode native_context, TNode maybe_regexp, TNode string, TNode is_fast_regexp, char const* method_name) { Label create_iterator(this), if_fast_regexp(this), if_slow_regexp(this, Label::kDeferred), if_not_regexp(this); // 1. Let S be ? ToString(O). // Handled by the caller of MatchAllIterator. CSA_ASSERT(this, IsString(string)); TVARIABLE(Object, var_matcher); TVARIABLE(Int32T, var_global); TVARIABLE(Int32T, var_unicode); // 2. If ? IsRegExp(R) is true, then GotoIf(is_fast_regexp, &if_fast_regexp); Branch(IsRegExp(context, maybe_regexp), &if_slow_regexp, &if_not_regexp); BIND(&if_fast_regexp); { CSA_ASSERT(this, IsFastRegExp(context, maybe_regexp)); TNode fast_regexp = CAST(maybe_regexp); TNode source = LoadObjectField(fast_regexp, JSRegExp::kSourceOffset); TNode flags = CAST(FlagsGetter(context, fast_regexp, true)); // c. Let matcher be ? Construct(C, « R, flags »). var_matcher = RegExpCreate(context, native_context, source, flags); CSA_ASSERT(this, IsFastRegExp(context, var_matcher.value())); // d. Let global be ? ToBoolean(? Get(matcher, "global")). var_global = UncheckedCast( FastFlagGetter(var_matcher.value(), JSRegExp::kGlobal)); // e. Let fullUnicode be ? ToBoolean(? Get(matcher, "unicode"). var_unicode = UncheckedCast( FastFlagGetter(var_matcher.value(), JSRegExp::kUnicode)); // f. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). // g. Perform ? Set(matcher, "lastIndex", lastIndex, true). FastStoreLastIndex(var_matcher.value(), FastLoadLastIndex(fast_regexp)); Goto(&create_iterator); } BIND(&if_slow_regexp); { // a. Let C be ? SpeciesConstructor(R, %RegExp%). TNode regexp_fun = LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX); TNode species_constructor = SpeciesConstructor(native_context, maybe_regexp, regexp_fun); // b. Let flags be ? ToString(? Get(R, "flags")). TNode flags = GetProperty(context, maybe_regexp, isolate()->factory()->flags_string()); TNode flags_string = ToString_Inline(context, flags); // c. Let matcher be ? Construct(C, « R, flags »). var_matcher = CAST(ConstructJS(CodeFactory::Construct(isolate()), context, species_constructor, maybe_regexp, flags_string)); // d. Let global be ? ToBoolean(? Get(matcher, "global")). var_global = UncheckedCast( SlowFlagGetter(context, var_matcher.value(), JSRegExp::kGlobal)); // e. Let fullUnicode be ? ToBoolean(? Get(matcher, "unicode"). var_unicode = UncheckedCast( SlowFlagGetter(context, var_matcher.value(), JSRegExp::kUnicode)); // f. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). TNode last_index = UncheckedCast( ToLength_Inline(context, SlowLoadLastIndex(context, maybe_regexp))); // g. Perform ? Set(matcher, "lastIndex", lastIndex, true). SlowStoreLastIndex(context, var_matcher.value(), last_index); Goto(&create_iterator); } // 3. Else, BIND(&if_not_regexp); { // a. Let flags be "g". // b. Let matcher be ? RegExpCreate(R, flags). var_matcher = RegExpCreate(context, native_context, maybe_regexp, StringConstant("g")); // c. Let global be true. var_global = Int32Constant(1); // d. Let fullUnicode be false. var_unicode = Int32Constant(0); #ifdef DEBUG // Assert: ! Get(matcher, "lastIndex") is 0. TNode last_index = SlowLoadLastIndex(context, var_matcher.value()); CSA_ASSERT(this, WordEqual(SmiZero(), last_index)); #endif // DEBUG Goto(&create_iterator); } // 4. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode). BIND(&create_iterator); { TNode map = CAST(LoadContextElement( native_context, Context::INITIAL_REGEXP_STRING_ITERATOR_PROTOTYPE_MAP_INDEX)); // 4. Let iterator be ObjectCreate(%RegExpStringIteratorPrototype%, « // [[IteratingRegExp]], [[IteratedString]], [[Global]], [[Unicode]], // [[Done]] »). TNode iterator = CAST(Allocate(JSRegExpStringIterator::kSize)); StoreMapNoWriteBarrier(iterator, map); StoreObjectFieldRoot(iterator, JSRegExpStringIterator::kPropertiesOrHashOffset, Heap::kEmptyFixedArrayRootIndex); StoreObjectFieldRoot(iterator, JSRegExpStringIterator::kElementsOffset, Heap::kEmptyFixedArrayRootIndex); // 5. Set iterator.[[IteratingRegExp]] to R. StoreObjectFieldNoWriteBarrier( iterator, JSRegExpStringIterator::kIteratingRegExpOffset, var_matcher.value()); // 6. Set iterator.[[IteratedString]] to S. StoreObjectFieldNoWriteBarrier( iterator, JSRegExpStringIterator::kIteratedStringOffset, string); #ifdef DEBUG // Verify global and unicode can be bitwise shifted without masking. TNode zero = Int32Constant(0); TNode one = Int32Constant(1); CSA_ASSERT(this, Word32Or(Word32Equal(var_global.value(), zero), Word32Equal(var_global.value(), one))); CSA_ASSERT(this, Word32Or(Word32Equal(var_unicode.value(), zero), Word32Equal(var_unicode.value(), one))); #endif // DEBUG // 7. Set iterator.[[Global]] to global. // 8. Set iterator.[[Unicode]] to fullUnicode. // 9. Set iterator.[[Done]] to false. TNode global_flag = Word32Shl( var_global.value(), Int32Constant(JSRegExpStringIterator::kGlobalBit)); TNode unicode_flag = Word32Shl(var_unicode.value(), Int32Constant(JSRegExpStringIterator::kUnicodeBit)); TNode iterator_flags = Word32Or(global_flag, unicode_flag); StoreObjectFieldNoWriteBarrier(iterator, JSRegExpStringIterator::kFlagsOffset, SmiFromInt32(Signed(iterator_flags))); return iterator; } } // https://tc39.github.io/proposal-string-matchall/ // RegExp.prototype [ @@matchAll ] ( string ) TF_BUILTIN(RegExpPrototypeMatchAll, RegExpBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode