// Copyright 2016 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.h" #include "src/builtins/builtins-constructor.h" #include "src/builtins/builtins-utils.h" #include "src/builtins/builtins.h" #include "src/code-factory.h" #include "src/code-stub-assembler.h" #include "src/counters.h" #include "src/objects-inl.h" #include "src/objects/regexp-match-info.h" #include "src/regexp/jsregexp.h" #include "src/regexp/regexp-utils.h" #include "src/string-builder.h" namespace v8 { namespace internal { typedef CodeStubAssembler::ParameterMode ParameterMode; // ----------------------------------------------------------------------------- // ES6 section 21.2 RegExp Objects Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) { // Load the in-object field. static const int field_offset = JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; return LoadObjectField(regexp, field_offset); } Node* RegExpBuiltinsAssembler::SlowLoadLastIndex(Node* context, Node* regexp) { // Load through the GetProperty stub. Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); Callable getproperty_callable = CodeFactory::GetProperty(isolate()); return CallStub(getproperty_callable, context, regexp, name); } Node* RegExpBuiltinsAssembler::LoadLastIndex(Node* context, Node* regexp, bool is_fastpath) { return is_fastpath ? FastLoadLastIndex(regexp) : SlowLoadLastIndex(context, regexp); } // The fast-path of StoreLastIndex when regexp is guaranteed to be an unmodified // JSRegExp instance. void RegExpBuiltinsAssembler::FastStoreLastIndex(Node* regexp, Node* value) { // Store the in-object field. static const int field_offset = JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize; StoreObjectField(regexp, field_offset, value); } void RegExpBuiltinsAssembler::SlowStoreLastIndex(Node* context, Node* regexp, Node* value) { // Store through runtime. // TODO(ishell): Use SetPropertyStub here once available. Node* const name = HeapConstant(isolate()->factory()->lastIndex_string()); Node* const language_mode = SmiConstant(Smi::FromInt(STRICT)); CallRuntime(Runtime::kSetProperty, context, regexp, name, value, language_mode); } void RegExpBuiltinsAssembler::StoreLastIndex(Node* context, Node* regexp, Node* value, bool is_fastpath) { if (is_fastpath) { FastStoreLastIndex(regexp, value); } else { SlowStoreLastIndex(context, regexp, value); } } Node* RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo( Node* const context, Node* const regexp, Node* const match_info, Node* const string) { Label named_captures(this), out(this); Node* const num_indices = SmiUntag(LoadFixedArrayElement( match_info, RegExpMatchInfo::kNumberOfCapturesIndex)); Node* const num_results = SmiTag(WordShr(num_indices, 1)); Node* const start = LoadFixedArrayElement(match_info, RegExpMatchInfo::kFirstCaptureIndex); Node* const end = LoadFixedArrayElement( match_info, RegExpMatchInfo::kFirstCaptureIndex + 1); // Calculate the substring of the first match before creating the result array // to avoid an unnecessary write barrier storing the first result. Node* const first = SubString(context, string, start, end); Node* const result = AllocateRegExpResult(context, num_results, start, string); Node* const result_elements = LoadElements(result); StoreFixedArrayElement(result_elements, 0, first, SKIP_WRITE_BARRIER); // If no captures exist we can skip named capture handling as well. GotoIf(SmiEqual(num_results, SmiConstant(1)), &out); // Store all remaining captures. Node* const limit = IntPtrAdd( IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex), num_indices); Variable var_from_cursor( this, MachineType::PointerRepresentation(), IntPtrConstant(RegExpMatchInfo::kFirstCaptureIndex + 2)); Variable var_to_cursor(this, MachineType::PointerRepresentation(), IntPtrConstant(1)); Variable* vars[] = {&var_from_cursor, &var_to_cursor}; Label loop(this, 2, vars); Goto(&loop); Bind(&loop); { Node* const from_cursor = var_from_cursor.value(); Node* const to_cursor = var_to_cursor.value(); Node* const start = LoadFixedArrayElement(match_info, from_cursor); Label next_iter(this); GotoIf(SmiEqual(start, SmiConstant(-1)), &next_iter); Node* const from_cursor_plus1 = IntPtrAdd(from_cursor, IntPtrConstant(1)); Node* const end = LoadFixedArrayElement(match_info, from_cursor_plus1); Node* const capture = SubString(context, string, start, end); StoreFixedArrayElement(result_elements, to_cursor, capture); Goto(&next_iter); Bind(&next_iter); var_from_cursor.Bind(IntPtrAdd(from_cursor, IntPtrConstant(2))); var_to_cursor.Bind(IntPtrAdd(to_cursor, IntPtrConstant(1))); Branch(UintPtrLessThan(var_from_cursor.value(), limit), &loop, &named_captures); } Bind(&named_captures); { // We reach this point only if captures exist, implying that this is an // IRREGEXP JSRegExp. CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); CSA_ASSERT(this, SmiGreaterThan(num_results, SmiConstant(1))); // Preparations for named capture properties. Exit early if the result does // not have any named captures to minimize performance impact. Node* const data = LoadObjectField(regexp, JSRegExp::kDataOffset); CSA_ASSERT(this, SmiEqual(LoadFixedArrayElement(data, JSRegExp::kTagIndex), SmiConstant(JSRegExp::IRREGEXP))); // The names fixed array associates names at even indices with a capture // index at odd indices. Node* const names = LoadFixedArrayElement(data, JSRegExp::kIrregexpCaptureNameMapIndex); GotoIf(SmiEqual(names, SmiConstant(0)), &out); // Allocate a new object to store the named capture properties. // TODO(jgruber): Could be optimized by adding the object map to the heap // root list. Node* const native_context = LoadNativeContext(context); Node* const map = LoadContextElement( native_context, Context::SLOW_OBJECT_WITH_NULL_PROTOTYPE_MAP); Node* const properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); Node* const group_object = AllocateJSObjectFromMap(map, properties); // Store it on the result as a 'group' property. { Node* const name = HeapConstant(isolate()->factory()->group_string()); CallRuntime(Runtime::kCreateDataProperty, context, result, name, group_object); } // One or more named captures exist, add a property for each one. CSA_ASSERT(this, HasInstanceType(names, FIXED_ARRAY_TYPE)); Node* const names_length = LoadAndUntagFixedArrayBaseLength(names); CSA_ASSERT(this, IntPtrGreaterThan(names_length, IntPtrConstant(0))); Variable var_i(this, MachineType::PointerRepresentation()); var_i.Bind(IntPtrConstant(0)); Variable* vars[] = {&var_i}; const int vars_count = sizeof(vars) / sizeof(vars[0]); Label loop(this, vars_count, vars); Goto(&loop); Bind(&loop); { Node* const i = var_i.value(); Node* const i_plus_1 = IntPtrAdd(i, IntPtrConstant(1)); Node* const i_plus_2 = IntPtrAdd(i_plus_1, IntPtrConstant(1)); Node* const name = LoadFixedArrayElement(names, i); Node* const index = LoadFixedArrayElement(names, i_plus_1); Node* const capture = LoadFixedArrayElement(result_elements, SmiUntag(index)); CallRuntime(Runtime::kCreateDataProperty, context, group_object, name, capture); var_i.Bind(i_plus_2); Branch(IntPtrGreaterThanOrEqual(var_i.value(), names_length), &out, &loop); } } Bind(&out); return result; } // ES#sec-regexp.prototype.exec // RegExp.prototype.exec ( string ) // Implements the core of RegExp.prototype.exec but without actually // constructing the JSRegExpResult. Returns either null (if the RegExp did not // match) or a fixed array containing match indices as returned by // RegExpExecStub. Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult( Node* const context, Node* const regexp, Node* const string, Label* if_didnotmatch, const bool is_fastpath) { Isolate* const isolate = this->isolate(); Node* const null = NullConstant(); Node* const int_zero = IntPtrConstant(0); Node* const smi_zero = SmiConstant(Smi::kZero); if (!is_fastpath) { ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, "RegExp.prototype.exec"); } CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(string))); CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE)); Variable var_result(this, MachineRepresentation::kTagged); Label out(this); // Load lastIndex. Variable var_lastindex(this, MachineRepresentation::kTagged); { Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath); var_lastindex.Bind(regexp_lastindex); if (is_fastpath) { // ToLength on a positive smi is a nop and can be skipped. CSA_ASSERT(this, TaggedIsPositiveSmi(regexp_lastindex)); } else { // Omit ToLength if lastindex is a non-negative smi. Label call_tolength(this, Label::kDeferred), next(this); Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength); Bind(&call_tolength); { Callable tolength_callable = CodeFactory::ToLength(isolate); var_lastindex.Bind( CallStub(tolength_callable, context, regexp_lastindex)); Goto(&next); } Bind(&next); } } // Check whether the regexp is global or sticky, which determines whether we // update last index later on. Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); Node* const is_global_or_sticky = WordAnd( SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal | JSRegExp::kSticky)); Node* const should_update_last_index = WordNotEqual(is_global_or_sticky, int_zero); // 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); { Node* const lastindex = var_lastindex.value(); Label if_isoob(this, Label::kDeferred); GotoIfNot(TaggedIsSmi(lastindex), &if_isoob); Node* const string_length = LoadStringLength(string); GotoIfNot(SmiLessThanOrEqual(lastindex, string_length), &if_isoob); Goto(&run_exec); Bind(&if_isoob); { StoreLastIndex(context, regexp, smi_zero, is_fastpath); var_result.Bind(null); Goto(if_didnotmatch); } } Bind(&if_dontupdate); { var_lastindex.Bind(smi_zero); Goto(&run_exec); } } Node* match_indices; Label successful_match(this); Bind(&run_exec); { // Get last match info from the context. Node* const native_context = LoadNativeContext(context); Node* const last_match_info = LoadContextElement( native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); // Call the exec stub. Callable exec_callable = CodeFactory::RegExpExec(isolate); match_indices = CallStub(exec_callable, context, regexp, string, var_lastindex.value(), last_match_info); var_result.Bind(match_indices); // {match_indices} is either null or the RegExpMatchInfo array. // Return early if exec failed, possibly updating last index. GotoIfNot(WordEqual(match_indices, null), &successful_match); GotoIfNot(should_update_last_index, if_didnotmatch); StoreLastIndex(context, regexp, smi_zero, is_fastpath); Goto(if_didnotmatch); } Bind(&successful_match); { GotoIfNot(should_update_last_index, &out); // Update the new last index from {match_indices}. Node* const new_lastindex = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); StoreLastIndex(context, regexp, new_lastindex, is_fastpath); Goto(&out); } Bind(&out); return var_result.value(); } // ES#sec-regexp.prototype.exec // RegExp.prototype.exec ( string ) Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBody(Node* const context, Node* const regexp, Node* const string, const bool is_fastpath) { Node* const null = NullConstant(); Variable var_result(this, MachineRepresentation::kTagged); Label if_didnotmatch(this), out(this); Node* const indices_or_null = RegExpPrototypeExecBodyWithoutResult( context, regexp, string, &if_didnotmatch, is_fastpath); // Successful match. { Node* const match_indices = indices_or_null; Node* const result = ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); var_result.Bind(result); Goto(&out); } Bind(&if_didnotmatch); { var_result.Bind(null); 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(this, 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 message_id = SmiConstant(Smi::FromInt(msg_template)); Node* const method_name_str = HeapConstant( isolate()->factory()->NewStringFromAsciiChecked(method_name, TENURED)); Callable callable = CodeFactory::ToString(isolate()); Node* const value_str = CallStub(callable, context, maybe_receiver); CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str, value_str); Unreachable(); } Bind(&out); return var_value_map.value(); } Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* object, Node* map) { Label out(this); Variable var_result(this, MachineRepresentation::kWord32); 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(object); var_result.Bind(TaggedIsPositiveSmi(last_index)); Goto(&out); Bind(&out); return var_result.value(); } // 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)); // 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(object); Branch(TaggedIsPositiveSmi(last_index), if_isunmodified, if_ismodified); } Node* RegExpBuiltinsAssembler::IsFastRegExpMap(Node* const context, Node* const object, Node* const map) { Label yup(this), nope(this), out(this); Variable var_result(this, MachineRepresentation::kWord32); BranchIfFastRegExp(context, object, map, &yup, &nope); Bind(&yup); var_result.Bind(Int32Constant(1)); Goto(&out); Bind(&nope); var_result.Bind(Int32Constant(0)); Goto(&out); Bind(&out); return var_result.value(); } void RegExpBuiltinsAssembler::BranchIfFastRegExpResult(Node* context, Node* map, Label* if_isunmodified, Label* if_ismodified) { 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); } // ES#sec-regexp.prototype.exec // RegExp.prototype.exec ( string ) TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const context = Parameter(4); // Ensure {maybe_receiver} is a JSRegExp. ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.exec"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. Node* const string = ToString(context, maybe_string); Label if_isfastpath(this), if_isslowpath(this); Branch(IsInitialRegExpMap(context, receiver, LoadMap(receiver)), &if_isfastpath, &if_isslowpath); Bind(&if_isfastpath); { Node* const result = RegExpPrototypeExecBody(context, receiver, string, true); Return(result); } Bind(&if_isslowpath); { Node* const result = RegExpPrototypeExecBody(context, receiver, string, false); Return(result); } } Node* RegExpBuiltinsAssembler::FlagsGetter(Node* const context, Node* const regexp, bool is_fastpath) { Isolate* isolate = this->isolate(); Node* const int_zero = IntPtrConstant(0); Node* const int_one = IntPtrConstant(1); Variable var_length(this, MachineType::PointerRepresentation(), int_zero); Variable var_flags(this, MachineType::PointerRepresentation()); // 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. Node* const flags_smi = LoadObjectField(regexp, JSRegExp::kFlagsOffset); Node* const flags_intptr = SmiUntag(flags_smi); var_flags.Bind(flags_intptr); #define CASE_FOR_FLAG(FLAG) \ do { \ Label next(this); \ GotoIfNot(IsSetWord(flags_intptr, FLAG), &next); \ var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ 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::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.Bind(int_zero); Callable getproperty_callable = CodeFactory::GetProperty(isolate); #define CASE_FOR_FLAG(NAME, FLAG) \ do { \ Label next(this); \ Node* const name = \ HeapConstant(isolate->factory()->InternalizeUtf8String(NAME)); \ Node* const flag = CallStub(getproperty_callable, context, regexp, name); \ Label if_isflagset(this); \ BranchIfToBooleanIsTrue(flag, &if_isflagset, &next); \ Bind(&if_isflagset); \ var_length.Bind(IntPtrAdd(var_length.value(), int_one)); \ var_flags.Bind(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("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()); Node* const flags_intptr = var_flags.value(); Variable var_offset( this, MachineType::PointerRepresentation(), IntPtrConstant(SeqOneByteString::kHeaderSize - kHeapObjectTag)); #define CASE_FOR_FLAG(FLAG, CHAR) \ do { \ Label next(this); \ GotoIfNot(IsSetWord(flags_intptr, 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::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(this, MachineRepresentation::kWord32, Int32Constant(0)); GotoIf(TaggedIsSmi(maybe_receiver), &out); GotoIfNot(IsJSReceiver(maybe_receiver), &out); Node* const receiver = maybe_receiver; // Check @@match. { Callable getproperty_callable = CodeFactory::GetProperty(isolate()); Node* const name = HeapConstant(isolate()->factory()->match_symbol()); Node* const value = CallStub(getproperty_callable, context, receiver, name); Label match_isundefined(this), match_isnotundefined(this); Branch(IsUndefined(value), &match_isundefined, &match_isnotundefined); Bind(&match_isundefined); Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &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) { // Normalize pattern. Node* const pattern = Select(IsUndefined(maybe_pattern), [=] { return EmptyStringConstant(); }, [=] { return ToString(context, maybe_pattern); }, MachineRepresentation::kTagged); // Normalize flags. Node* const flags = Select(IsUndefined(maybe_flags), [=] { return EmptyStringConstant(); }, [=] { return ToString(context, maybe_flags); }, MachineRepresentation::kTagged); // Initialize. return CallRuntime(Runtime::kRegExpInitializeAndCompile, context, regexp, pattern, flags); } TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const context = Parameter(3); Node* const map = ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kRegExpNonObject, "RegExp.prototype.flags"); Node* const receiver = maybe_receiver; Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred); Branch(IsInitialRegExpMap(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) { Node* const pattern = Parameter(1); Node* const flags = Parameter(2); Node* const new_target = Parameter(3); Node* const context = Parameter(5); Isolate* isolate = this->isolate(); Variable var_flags(this, MachineRepresentation::kTagged, flags); Variable var_pattern(this, MachineRepresentation::kTagged, pattern); Variable var_new_target(this, 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); Callable getproperty_callable = CodeFactory::GetProperty(isolate); Node* const name = HeapConstant(isolate->factory()->constructor_string()); Node* const value = CallStub(getproperty_callable, context, pattern, name); GotoIfNot(WordEqual(value, regexp_function), &next); Return(pattern); Bind(&next); } { Label next(this), if_patternisfastregexp(this), if_patternisslowregexp(this); GotoIf(TaggedIsSmi(pattern), &next); GotoIf(HasInstanceType(pattern, JS_REGEXP_TYPE), &if_patternisfastregexp); Branch(pattern_is_regexp, &if_patternisslowregexp, &next); Bind(&if_patternisfastregexp); { Node* const source = LoadObjectField(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); { Callable getproperty_callable = CodeFactory::GetProperty(isolate); { Node* const name = HeapConstant(isolate->factory()->source_string()); Node* const value = CallStub(getproperty_callable, context, pattern, name); var_pattern.Bind(value); } { Label inner_next(this); GotoIfNot(IsUndefined(flags), &inner_next); Node* const name = HeapConstant(isolate->factory()->flags_string()); Node* const value = CallStub(getproperty_callable, context, pattern, name); var_flags.Bind(value); Goto(&inner_next); Bind(&inner_next); } Goto(&next); } Bind(&next); } // Allocate. Variable var_regexp(this, 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) { Node* const maybe_receiver = Parameter(0); Node* const maybe_pattern = Parameter(1); Node* const maybe_flags = Parameter(2); Node* const context = Parameter(5); ThrowIfNotInstanceType(context, maybe_receiver, JS_REGEXP_TYPE, "RegExp.prototype.compile"); Node* const receiver = maybe_receiver; Variable var_flags(this, MachineRepresentation::kTagged, maybe_flags); Variable var_pattern(this, MachineRepresentation::kTagged, maybe_pattern); // Handle a JSRegExp pattern. { Label next(this); GotoIf(TaggedIsSmi(maybe_pattern), &next); GotoIfNot(HasInstanceType(maybe_pattern, JS_REGEXP_TYPE), &next); Node* const pattern = maybe_pattern; // {maybe_flags} must be undefined in this case, otherwise throw. { Label next(this); GotoIf(IsUndefined(maybe_flags), &next); Node* const message_id = SmiConstant(MessageTemplate::kRegExpFlags); TailCallRuntime(Runtime::kThrowTypeError, context, message_id); 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. TF_BUILTIN(RegExpPrototypeSourceGetter, RegExpBuiltinsAssembler) { Node* const receiver = Parameter(0); Node* const context = Parameter(3); // Check whether we have an unmodified regexp instance. Label if_isjsregexp(this), if_isnotjsregexp(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &if_isnotjsregexp); Branch(HasInstanceType(receiver, JS_REGEXP_TYPE), &if_isjsregexp, &if_isnotjsregexp); Bind(&if_isjsregexp); { Node* const source = LoadObjectField(receiver, JSRegExp::kSourceOffset); Return(source); } 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); { Node* const message_id = SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); Node* const method_name_str = HeapConstant(isolate->factory()->NewStringFromAsciiChecked( "RegExp.prototype.source")); TailCallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str); } } } BUILTIN(RegExpPrototypeToString) { HandleScope scope(isolate); CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.toString"); if (*recv == isolate->regexp_function()->prototype()) { isolate->CountUsage(v8::Isolate::kRegExpPrototypeToString); } IncrementalStringBuilder builder(isolate); builder.AppendCharacter('/'); { Handle source; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, source, JSReceiver::GetProperty(recv, isolate->factory()->source_string())); Handle source_str; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, source_str, Object::ToString(isolate, source)); builder.AppendString(source_str); } builder.AppendCharacter('/'); { Handle flags; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, flags, JSReceiver::GetProperty(recv, isolate->factory()->flags_string())); Handle flags_str; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags_str, Object::ToString(isolate, flags)); builder.AppendString(flags_str); } RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); } // Fast-path implementation for flag checks on an unmodified JSRegExp instance. Node* RegExpBuiltinsAssembler::FastFlagGetter(Node* const regexp, JSRegExp::Flag flag) { Node* const smi_zero = SmiConstant(Smi::kZero); Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); Node* const mask = SmiConstant(Smi::FromInt(flag)); Node* const is_flag_set = WordNotEqual(SmiAnd(flags, mask), smi_zero); return is_flag_set; } // 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(this, MachineRepresentation::kWord32); Node* name; switch (flag) { case JSRegExp::kGlobal: name = HeapConstant(factory->global_string()); break; case JSRegExp::kIgnoreCase: name = HeapConstant(factory->ignoreCase_string()); break; case JSRegExp::kMultiline: name = HeapConstant(factory->multiline_string()); break; case JSRegExp::kSticky: name = HeapConstant(factory->sticky_string()); break; case JSRegExp::kUnicode: name = HeapConstant(factory->unicode_string()); break; default: UNREACHABLE(); } Callable getproperty_callable = CodeFactory::GetProperty(isolate()); Node* const value = CallStub(getproperty_callable, 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(JSRegExp::Flag flag, v8::Isolate::UseCounterFeature counter, const char* method_name) { Node* const receiver = Parameter(0); Node* const context = Parameter(3); Isolate* isolate = this->isolate(); // Check whether we have an unmodified regexp instance. Label if_isunmodifiedjsregexp(this), if_isnotunmodifiedjsregexp(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &if_isnotunmodifiedjsregexp); Node* const receiver_map = LoadMap(receiver); Node* const instance_type = LoadMapInstanceType(receiver_map); Branch(Word32Equal(instance_type, Int32Constant(JS_REGEXP_TYPE)), &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); { Node* const counter_smi = SmiConstant(Smi::FromInt(counter)); CallRuntime(Runtime::kIncrementUseCounter, context, counter_smi); Return(UndefinedConstant()); } Bind(&if_isnotprototype); { Node* const message_id = SmiConstant(Smi::FromInt(MessageTemplate::kRegExpNonRegExp)); Node* const method_name_str = HeapConstant( isolate->factory()->NewStringFromAsciiChecked(method_name)); CallRuntime(Runtime::kThrowTypeError, context, message_id, method_name_str); Unreachable(); } } } // ES6 21.2.5.4. TF_BUILTIN(RegExpPrototypeGlobalGetter, RegExpBuiltinsAssembler) { FlagGetter(JSRegExp::kGlobal, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.global"); } // ES6 21.2.5.5. TF_BUILTIN(RegExpPrototypeIgnoreCaseGetter, RegExpBuiltinsAssembler) { FlagGetter(JSRegExp::kIgnoreCase, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.ignoreCase"); } // ES6 21.2.5.7. TF_BUILTIN(RegExpPrototypeMultilineGetter, RegExpBuiltinsAssembler) { FlagGetter(JSRegExp::kMultiline, v8::Isolate::kRegExpPrototypeOldFlagGetter, "RegExp.prototype.multiline"); } // ES6 21.2.5.12. TF_BUILTIN(RegExpPrototypeStickyGetter, RegExpBuiltinsAssembler) { FlagGetter(JSRegExp::kSticky, v8::Isolate::kRegExpPrototypeStickyGetter, "RegExp.prototype.sticky"); } // ES6 21.2.5.15. TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) { FlagGetter(JSRegExp::kUnicode, v8::Isolate::kRegExpPrototypeUnicodeGetter, "RegExp.prototype.unicode"); } // The properties $1..$9 are the first nine capturing substrings of the last // successful match, or ''. The function RegExpMakeCaptureGetter will be // called with indices from 1 to 9. #define DEFINE_CAPTURE_GETTER(i) \ BUILTIN(RegExpCapture##i##Getter) { \ HandleScope scope(isolate); \ return *RegExpUtils::GenericCaptureGetter( \ isolate, isolate->regexp_last_match_info(), i); \ } DEFINE_CAPTURE_GETTER(1) DEFINE_CAPTURE_GETTER(2) DEFINE_CAPTURE_GETTER(3) DEFINE_CAPTURE_GETTER(4) DEFINE_CAPTURE_GETTER(5) DEFINE_CAPTURE_GETTER(6) DEFINE_CAPTURE_GETTER(7) DEFINE_CAPTURE_GETTER(8) DEFINE_CAPTURE_GETTER(9) #undef DEFINE_CAPTURE_GETTER // The properties `input` and `$_` are aliases for each other. When this // value is set, the value it is set to is coerced to a string. // Getter and setter for the input. BUILTIN(RegExpInputGetter) { HandleScope scope(isolate); Handle obj(isolate->regexp_last_match_info()->LastInput(), isolate); return obj->IsUndefined(isolate) ? isolate->heap()->empty_string() : String::cast(*obj); } BUILTIN(RegExpInputSetter) { HandleScope scope(isolate); Handle value = args.atOrUndefined(isolate, 1); Handle str; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str, Object::ToString(isolate, value)); isolate->regexp_last_match_info()->SetLastInput(*str); return isolate->heap()->undefined_value(); } // Getters for the static properties lastMatch, lastParen, leftContext, and // rightContext of the RegExp constructor. The properties are computed based // on the captures array of the last successful match and the subject string // of the last successful match. BUILTIN(RegExpLastMatchGetter) { HandleScope scope(isolate); return *RegExpUtils::GenericCaptureGetter( isolate, isolate->regexp_last_match_info(), 0); } BUILTIN(RegExpLastParenGetter) { HandleScope scope(isolate); Handle match_info = isolate->regexp_last_match_info(); const int length = match_info->NumberOfCaptureRegisters(); if (length <= 2) return isolate->heap()->empty_string(); // No captures. DCHECK_EQ(0, length % 2); const int last_capture = (length / 2) - 1; // We match the SpiderMonkey behavior: return the substring defined by the // last pair (after the first pair) of elements of the capture array even if // it is empty. return *RegExpUtils::GenericCaptureGetter(isolate, match_info, last_capture); } BUILTIN(RegExpLeftContextGetter) { HandleScope scope(isolate); Handle match_info = isolate->regexp_last_match_info(); const int start_index = match_info->Capture(0); Handle last_subject(match_info->LastSubject()); return *isolate->factory()->NewSubString(last_subject, 0, start_index); } BUILTIN(RegExpRightContextGetter) { HandleScope scope(isolate); Handle match_info = isolate->regexp_last_match_info(); const int start_index = match_info->Capture(1); Handle last_subject(match_info->LastSubject()); const int len = last_subject->length(); return *isolate->factory()->NewSubString(last_subject, start_index, len); } // ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S ) Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp, Node* string) { Isolate* isolate = this->isolate(); Node* const null = NullConstant(); Variable var_result(this, MachineRepresentation::kTagged); Label out(this), if_isfastpath(this), if_isslowpath(this); Node* const map = LoadMap(regexp); BranchIfFastRegExp(context, regexp, map, &if_isfastpath, &if_isslowpath); Bind(&if_isfastpath); { Node* const result = RegExpPrototypeExecBody(context, regexp, string, true); var_result.Bind(result); Goto(&out); } Bind(&if_isslowpath); { // Take the slow path of fetching the exec property, calling it, and // verifying its return value. // Get the exec property. Node* const name = HeapConstant(isolate->factory()->exec_string()); Callable getproperty_callable = CodeFactory::GetProperty(isolate); Node* const exec = CallStub(getproperty_callable, context, regexp, name); // 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(WordEqual(result, null), &out); ThrowIfNotJSReceiver(context, result, MessageTemplate::kInvalidRegExpExecResult, "unused"); Goto(&out); } Bind(&if_isnotcallable); { ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE, "RegExp.prototype.exec"); Node* const result = RegExpPrototypeExecBody(context, regexp, string, false); 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) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const context = Parameter(4); // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.test"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); BranchIfFastRegExp(context, receiver, LoadMap(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. Node* const match_indices = RegExpExec(context, receiver, string); // Return true iff exec matched successfully. Node* const result = SelectBooleanConstant(WordNotEqual(match_indices, NullConstant())); Return(result); } } Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string, Node* const index, Node* const is_unicode, bool is_fastpath) { CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(index))); if (is_fastpath) CSA_ASSERT(this, TaggedIsPositiveSmi(index)); // Default to last_index + 1. Node* const index_plus_one = NumberInc(index); Variable var_result(this, 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); { Node* const string_length = LoadStringLength(string); GotoIfNot(SmiLessThan(index_plus_one, string_length), &out); Node* const lead = StringCharCodeAt(string, index); GotoIfNot(Word32Equal(Word32And(lead, Int32Constant(0xFC00)), Int32Constant(0xD800)), &out); Node* const trail = StringCharCodeAt(string, index_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(); } namespace { // Utility class implementing a growable fixed array through CSA. class GrowableFixedArray { typedef CodeStubAssembler::Label Label; typedef CodeStubAssembler::Variable Variable; public: explicit GrowableFixedArray(CodeStubAssembler* a) : assembler_(a), var_array_(a, MachineRepresentation::kTagged), var_length_(a, MachineType::PointerRepresentation()), var_capacity_(a, MachineType::PointerRepresentation()) { Initialize(); } Node* length() const { return var_length_.value(); } Variable* var_array() { return &var_array_; } Variable* var_length() { return &var_length_; } Variable* var_capacity() { return &var_capacity_; } void Push(Node* const value) { CodeStubAssembler* a = assembler_; Node* const length = var_length_.value(); Node* const capacity = var_capacity_.value(); Label grow(a), store(a); a->Branch(a->IntPtrEqual(capacity, length), &grow, &store); a->Bind(&grow); { Node* const new_capacity = NewCapacity(a, capacity); Node* const new_array = ResizeFixedArray(length, new_capacity); var_capacity_.Bind(new_capacity); var_array_.Bind(new_array); a->Goto(&store); } a->Bind(&store); { Node* const array = var_array_.value(); a->StoreFixedArrayElement(array, length, value); Node* const new_length = a->IntPtrAdd(length, a->IntPtrConstant(1)); var_length_.Bind(new_length); } } Node* ToJSArray(Node* const context) { CodeStubAssembler* a = assembler_; const ElementsKind kind = FAST_ELEMENTS; Node* const native_context = a->LoadNativeContext(context); Node* const array_map = a->LoadJSArrayElementsMap(kind, native_context); // Shrink to fit if necessary. { Label next(a); Node* const length = var_length_.value(); Node* const capacity = var_capacity_.value(); a->GotoIf(a->WordEqual(length, capacity), &next); Node* const array = ResizeFixedArray(length, length); var_array_.Bind(array); var_capacity_.Bind(length); a->Goto(&next); a->Bind(&next); } Node* const result_length = a->SmiTag(length()); Node* const result = a->AllocateUninitializedJSArrayWithoutElements( kind, array_map, result_length, nullptr); // Note: We do not currently shrink the fixed array. a->StoreObjectField(result, JSObject::kElementsOffset, var_array_.value()); return result; } private: void Initialize() { CodeStubAssembler* a = assembler_; const ElementsKind kind = FAST_ELEMENTS; static const int kInitialArraySize = 8; Node* const capacity = a->IntPtrConstant(kInitialArraySize); Node* const array = a->AllocateFixedArray(kind, capacity); a->FillFixedArrayWithValue(kind, array, a->IntPtrConstant(0), capacity, Heap::kTheHoleValueRootIndex); var_array_.Bind(array); var_capacity_.Bind(capacity); var_length_.Bind(a->IntPtrConstant(0)); } Node* NewCapacity(CodeStubAssembler* a, Node* const current_capacity) { CSA_ASSERT(a, a->IntPtrGreaterThan(current_capacity, a->IntPtrConstant(0))); // Growth rate is analog to JSObject::NewElementsCapacity: // new_capacity = (current_capacity + (current_capacity >> 1)) + 16. Node* const new_capacity = a->IntPtrAdd( a->IntPtrAdd(current_capacity, a->WordShr(current_capacity, 1)), a->IntPtrConstant(16)); return new_capacity; } // Creates a new array with {new_capacity} and copies the first // {element_count} elements from the current array. Node* ResizeFixedArray(Node* const element_count, Node* const new_capacity) { CodeStubAssembler* a = assembler_; CSA_ASSERT(a, a->IntPtrGreaterThan(element_count, a->IntPtrConstant(0))); CSA_ASSERT(a, a->IntPtrGreaterThan(new_capacity, a->IntPtrConstant(0))); CSA_ASSERT(a, a->IntPtrGreaterThanOrEqual(new_capacity, element_count)); const ElementsKind kind = FAST_ELEMENTS; const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; const CodeStubAssembler::AllocationFlags flags = CodeStubAssembler::kAllowLargeObjectAllocation; Node* const from_array = var_array_.value(); Node* const to_array = a->AllocateFixedArray(kind, new_capacity, mode, flags); a->CopyFixedArrayElements(kind, from_array, kind, to_array, element_count, new_capacity, barrier_mode, mode); return to_array; } private: CodeStubAssembler* const assembler_; Variable var_array_; Variable var_length_; Variable var_capacity_; }; } // namespace void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context, Node* const regexp, Node* const string, const bool is_fastpath) { Isolate* const isolate = this->isolate(); Node* const null = NullConstant(); Node* const int_zero = IntPtrConstant(0); Node* const smi_zero = SmiConstant(Smi::kZero); 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(context, 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, smi_zero, is_fastpath); // Allocate an array to store the resulting match strings. GrowableFixedArray array(this); // 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(this, 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. Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( context, regexp, string, &if_didnotmatch, true); Node* const match_from = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex); Node* const match_to = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); Node* match = SubString(context, string, match_from, match_to); var_match.Bind(match); Goto(&if_didmatch); } else { DCHECK(!is_fastpath); Node* const result = RegExpExec(context, regexp, string); Label load_match(this); Branch(WordEqual(result, null), &if_didnotmatch, &load_match); Bind(&load_match); { Label fast_result(this), slow_result(this); BranchIfFastRegExpResult(context, LoadMap(result), &fast_result, &slow_result); Bind(&fast_result); { Node* const result_fixed_array = LoadElements(result); Node* const match = LoadFixedArrayElement(result_fixed_array, 0); // The match is guaranteed to be a string on the fast path. CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(match))); var_match.Bind(match); Goto(&if_didmatch); } Bind(&slow_result); { // TODO(ishell): Use GetElement stub once it's available. Node* const name = smi_zero; Callable getproperty_callable = CodeFactory::GetProperty(isolate); Node* const match = CallStub(getproperty_callable, context, result, name); var_match.Bind(ToString(context, match)); Goto(&if_didmatch); } } } Bind(&if_didnotmatch); { // Return null if there were no matches, otherwise just exit the loop. GotoIfNot(IntPtrEqual(array.length(), int_zero), &out); Return(null); } Bind(&if_didmatch); { Node* match = var_match.value(); // Store the match, growing the fixed array if needed. array.Push(match); // Advance last index if the match is the empty string. Node* const match_length = LoadStringLength(match); GotoIfNot(SmiEqual(match_length, smi_zero), &loop); Node* last_index = LoadLastIndex(context, regexp, is_fastpath); if (is_fastpath) { CSA_ASSERT(this, TaggedIsPositiveSmi(last_index)); } else { Callable tolength_callable = CodeFactory::ToLength(isolate); last_index = CallStub(tolength_callable, 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(context); Return(result); } } } // ES#sec-regexp.prototype-@@match // RegExp.prototype [ @@match ] ( string ) TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const context = Parameter(4); // 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. Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path, &slow_path); Bind(&fast_path); RegExpPrototypeMatchBody(context, receiver, string, true); Bind(&slow_path); RegExpPrototypeMatchBody(context, receiver, string, false); } void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodyFast( Node* const context, Node* const regexp, Node* const string) { // Grab the initial value of last index. Node* const previous_last_index = FastLoadLastIndex(regexp); // Ensure last index is 0. FastStoreLastIndex(regexp, SmiConstant(Smi::kZero)); // Call exec. Label if_didnotmatch(this); Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( context, regexp, string, &if_didnotmatch, true); // Successful match. { // Reset last index. FastStoreLastIndex(regexp, previous_last_index); // Return the index of the match. Node* const index = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex); Return(index); } Bind(&if_didnotmatch); { // Reset last index and return -1. FastStoreLastIndex(regexp, previous_last_index); Return(SmiConstant(-1)); } } void RegExpBuiltinsAssembler::RegExpPrototypeSearchBodySlow( Node* const context, Node* const regexp, Node* const string) { Isolate* const isolate = this->isolate(); Node* const smi_zero = SmiConstant(Smi::kZero); // Grab the initial value of last index. Node* const previous_last_index = SlowLoadLastIndex(context, regexp); // Ensure last index is 0. { Label next(this); GotoIf(SameValue(previous_last_index, smi_zero, context), &next); SlowStoreLastIndex(context, regexp, smi_zero); Goto(&next); Bind(&next); } // Call exec. Node* const exec_result = RegExpExec(context, regexp, string); // Reset last index if necessary. { Label next(this); Node* const current_last_index = SlowLoadLastIndex(context, regexp); GotoIf(SameValue(current_last_index, previous_last_index, context), &next); SlowStoreLastIndex(context, regexp, previous_last_index); Goto(&next); Bind(&next); } // Return -1 if no match was found. { Label next(this); GotoIfNot(WordEqual(exec_result, NullConstant()), &next); Return(SmiConstant(-1)); Bind(&next); } // Return the index of the match. { Label fast_result(this), slow_result(this, Label::kDeferred); BranchIfFastRegExpResult(context, LoadMap(exec_result), &fast_result, &slow_result); Bind(&fast_result); { Node* const index = LoadObjectField(exec_result, JSRegExpResult::kIndexOffset); Return(index); } Bind(&slow_result); { Node* const name = HeapConstant(isolate->factory()->index_string()); Callable getproperty_callable = CodeFactory::GetProperty(isolate); Node* const index = CallStub(getproperty_callable, context, exec_result, name); Return(index); } } } // ES#sec-regexp.prototype-@@search // RegExp.prototype [ @@search ] ( string ) TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const context = Parameter(4); // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.@@search"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. Node* const string = ToString(context, maybe_string); Label fast_path(this), slow_path(this); BranchIfFastRegExp(context, receiver, LoadMap(receiver), &fast_path, &slow_path); Bind(&fast_path); RegExpPrototypeSearchBodyFast(context, receiver, string); Bind(&slow_path); RegExpPrototypeSearchBodySlow(context, receiver, string); } // Generates the fast path for @@split. {regexp} is an unmodified JSRegExp, // {string} is a String, and {limit} is a Smi. void RegExpBuiltinsAssembler::RegExpPrototypeSplitBody(Node* const context, Node* const regexp, Node* const string, Node* const limit) { Isolate* isolate = this->isolate(); Node* const null = NullConstant(); Node* const smi_zero = SmiConstant(0); Node* const int_zero = IntPtrConstant(0); Node* const int_limit = SmiUntag(limit); const ElementsKind kind = FAST_ELEMENTS; const ParameterMode mode = CodeStubAssembler::INTPTR_PARAMETERS; Node* const allocation_site = nullptr; Node* const native_context = LoadNativeContext(context); Node* const array_map = LoadJSArrayElementsMap(kind, native_context); Label return_empty_array(this, Label::kDeferred); // If limit is zero, return an empty array. { Label next(this), if_limitiszero(this, Label::kDeferred); Branch(SmiEqual(limit, smi_zero), &return_empty_array, &next); Bind(&next); } Node* const string_length = LoadStringLength(string); // If passed the empty {string}, return either an empty array or a singleton // array depending on whether the {regexp} matches. { Label next(this), if_stringisempty(this, Label::kDeferred); Branch(SmiEqual(string_length, smi_zero), &if_stringisempty, &next); Bind(&if_stringisempty); { Node* const last_match_info = LoadContextElement( native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); Callable exec_callable = CodeFactory::RegExpExec(isolate); Node* const match_indices = CallStub(exec_callable, context, regexp, string, smi_zero, last_match_info); Label return_singleton_array(this); Branch(WordEqual(match_indices, null), &return_singleton_array, &return_empty_array); Bind(&return_singleton_array); { Node* const length = SmiConstant(1); Node* const capacity = IntPtrConstant(1); Node* const result = AllocateJSArray(kind, array_map, capacity, length, allocation_site, mode); Node* const fixed_array = LoadElements(result); StoreFixedArrayElement(fixed_array, 0, string); Return(result); } } Bind(&next); } // Loop preparations. GrowableFixedArray array(this); Variable var_last_matched_until(this, MachineRepresentation::kTagged); Variable var_next_search_from(this, MachineRepresentation::kTagged); var_last_matched_until.Bind(smi_zero); var_next_search_from.Bind(smi_zero); Variable* vars[] = {array.var_array(), array.var_length(), array.var_capacity(), &var_last_matched_until, &var_next_search_from}; const int vars_count = sizeof(vars) / sizeof(vars[0]); Label loop(this, vars_count, vars), push_suffix_and_out(this), out(this); Goto(&loop); Bind(&loop); { Node* const next_search_from = var_next_search_from.value(); Node* const last_matched_until = var_last_matched_until.value(); // We're done if we've reached the end of the string. { Label next(this); Branch(SmiEqual(next_search_from, string_length), &push_suffix_and_out, &next); Bind(&next); } // Search for the given {regexp}. Node* const last_match_info = LoadContextElement( native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); Callable exec_callable = CodeFactory::RegExpExec(isolate); Node* const match_indices = CallStub(exec_callable, context, regexp, string, next_search_from, last_match_info); // We're done if no match was found. { Label next(this); Branch(WordEqual(match_indices, null), &push_suffix_and_out, &next); Bind(&next); } Node* const match_from = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex); // We're done if the match starts beyond the string. { Label next(this); Branch(WordEqual(match_from, string_length), &push_suffix_and_out, &next); Bind(&next); } Node* const match_to = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); // Advance index and continue if the match is empty. { Label next(this); GotoIfNot(SmiEqual(match_to, next_search_from), &next); GotoIfNot(SmiEqual(match_to, last_matched_until), &next); Node* const is_unicode = FastFlagGetter(regexp, JSRegExp::kUnicode); Node* const new_next_search_from = AdvanceStringIndex(string, next_search_from, is_unicode, true); var_next_search_from.Bind(new_next_search_from); Goto(&loop); Bind(&next); } // A valid match was found, add the new substring to the array. { Node* const from = last_matched_until; Node* const to = match_from; Node* const substr = SubString(context, string, from, to); array.Push(substr); GotoIf(WordEqual(array.length(), int_limit), &out); } // Add all captures to the array. { Node* const num_registers = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kNumberOfCapturesIndex); Node* const int_num_registers = SmiUntag(num_registers); Variable var_reg(this, MachineType::PointerRepresentation()); var_reg.Bind(IntPtrConstant(2)); Variable* vars[] = {array.var_array(), array.var_length(), array.var_capacity(), &var_reg}; const int vars_count = sizeof(vars) / sizeof(vars[0]); Label nested_loop(this, vars_count, vars), nested_loop_out(this); Branch(IntPtrLessThan(var_reg.value(), int_num_registers), &nested_loop, &nested_loop_out); Bind(&nested_loop); { Node* const reg = var_reg.value(); Node* const from = LoadFixedArrayElement( match_indices, reg, RegExpMatchInfo::kFirstCaptureIndex * kPointerSize, mode); Node* const to = LoadFixedArrayElement( match_indices, reg, (RegExpMatchInfo::kFirstCaptureIndex + 1) * kPointerSize, mode); Label select_capture(this), select_undefined(this), store_value(this); Variable var_value(this, MachineRepresentation::kTagged); Branch(SmiEqual(to, SmiConstant(-1)), &select_undefined, &select_capture); Bind(&select_capture); { Node* const substr = SubString(context, string, from, to); var_value.Bind(substr); Goto(&store_value); } Bind(&select_undefined); { Node* const undefined = UndefinedConstant(); var_value.Bind(undefined); Goto(&store_value); } Bind(&store_value); { array.Push(var_value.value()); GotoIf(WordEqual(array.length(), int_limit), &out); Node* const new_reg = IntPtrAdd(reg, IntPtrConstant(2)); var_reg.Bind(new_reg); Branch(IntPtrLessThan(new_reg, int_num_registers), &nested_loop, &nested_loop_out); } } Bind(&nested_loop_out); } var_last_matched_until.Bind(match_to); var_next_search_from.Bind(match_to); Goto(&loop); } Bind(&push_suffix_and_out); { Node* const from = var_last_matched_until.value(); Node* const to = string_length; Node* const substr = SubString(context, string, from, to); array.Push(substr); Goto(&out); } Bind(&out); { Node* const result = array.ToJSArray(context); Return(result); } Bind(&return_empty_array); { Node* const length = smi_zero; Node* const capacity = int_zero; Node* const result = AllocateJSArray(kind, array_map, capacity, length, allocation_site, mode); Return(result); } } // Helper that skips a few initial checks. TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) { typedef RegExpSplitDescriptor Descriptor; Node* const regexp = Parameter(Descriptor::kReceiver); Node* const string = Parameter(Descriptor::kString); Node* const maybe_limit = Parameter(Descriptor::kLimit); Node* const context = Parameter(Descriptor::kContext); CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp))); CSA_ASSERT(this, IsString(string)); // TODO(jgruber): Even if map checks send us to the fast path, we still need // to verify the constructor property and jump to the slow path if it has // been changed. // Convert {maybe_limit} to a uint32, capping at the maximal smi value. Variable var_limit(this, MachineRepresentation::kTagged, maybe_limit); Label if_limitissmimax(this), limit_done(this), runtime(this); GotoIf(IsUndefined(maybe_limit), &if_limitissmimax); GotoIf(TaggedIsPositiveSmi(maybe_limit), &limit_done); Node* const limit = ToUint32(context, maybe_limit); { // ToUint32(limit) could potentially change the shape of the RegExp // object. Recheck that we are still on the fast path and bail to runtime // otherwise. { Label next(this); BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime); Bind(&next); } GotoIfNot(TaggedIsSmi(limit), &if_limitissmimax); var_limit.Bind(limit); Goto(&limit_done); } Bind(&if_limitissmimax); { // TODO(jgruber): In this case, we can probably avoid generation of limit // checks in Generate_RegExpPrototypeSplitBody. var_limit.Bind(SmiConstant(Smi::kMaxValue)); Goto(&limit_done); } Bind(&limit_done); { Node* const limit = var_limit.value(); RegExpPrototypeSplitBody(context, regexp, string, limit); } Bind(&runtime); { // The runtime call passes in limit to ensure the second ToUint32(limit) // call is not observable. CSA_ASSERT(this, IsHeapNumberMap(LoadReceiverMap(limit))); Return(CallRuntime(Runtime::kRegExpSplit, context, regexp, string, limit)); } } // ES#sec-regexp.prototype-@@split // RegExp.prototype [ @@split ] ( string, limit ) TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const maybe_limit = Parameter(2); Node* const context = Parameter(5); // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.@@split"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. Node* const string = ToString(context, maybe_string); Label stub(this), runtime(this, Label::kDeferred); BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime); Bind(&stub); Callable split_callable = CodeFactory::RegExpSplit(isolate()); Return(CallStub(split_callable, context, receiver, string, maybe_limit)); Bind(&runtime); Return(CallRuntime(Runtime::kRegExpSplit, context, receiver, string, maybe_limit)); } Node* RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath( Node* context, Node* regexp, Node* string, Node* replace_callable) { // The fast path is reached only if {receiver} is a global unmodified // JSRegExp instance and {replace_callable} is callable. Isolate* const isolate = this->isolate(); Node* const null = NullConstant(); Node* const undefined = UndefinedConstant(); Node* const int_zero = IntPtrConstant(0); Node* const int_one = IntPtrConstant(1); Node* const smi_zero = SmiConstant(Smi::kZero); Node* const native_context = LoadNativeContext(context); Label out(this); Variable var_result(this, MachineRepresentation::kTagged); // Set last index to 0. FastStoreLastIndex(regexp, smi_zero); // Allocate {result_array}. Node* result_array; { ElementsKind kind = FAST_ELEMENTS; Node* const array_map = LoadJSArrayElementsMap(kind, native_context); Node* const capacity = IntPtrConstant(16); Node* const length = smi_zero; Node* const allocation_site = nullptr; ParameterMode capacity_mode = CodeStubAssembler::INTPTR_PARAMETERS; result_array = AllocateJSArray(kind, array_map, capacity, length, allocation_site, capacity_mode); } // Call into runtime for RegExpExecMultiple. Node* last_match_info = LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); Node* const res = CallRuntime(Runtime::kRegExpExecMultiple, context, regexp, string, last_match_info, result_array); // Reset last index to 0. FastStoreLastIndex(regexp, smi_zero); // If no matches, return the subject string. var_result.Bind(string); GotoIf(WordEqual(res, null), &out); // Reload last match info since it might have changed. last_match_info = LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); Node* const res_length = LoadJSArrayLength(res); Node* const res_elems = LoadElements(res); CSA_ASSERT(this, HasInstanceType(res_elems, FIXED_ARRAY_TYPE)); Node* const num_capture_registers = LoadFixedArrayElement( last_match_info, RegExpMatchInfo::kNumberOfCapturesIndex); Label if_hasexplicitcaptures(this), if_noexplicitcaptures(this), create_result(this); Branch(SmiEqual(num_capture_registers, SmiConstant(Smi::FromInt(2))), &if_noexplicitcaptures, &if_hasexplicitcaptures); Bind(&if_noexplicitcaptures); { // If the number of captures is two then there are no explicit captures in // the regexp, just the implicit capture that captures the whole match. In // this case we can simplify quite a bit and end up with something faster. // The builder will consist of some integers that indicate slices of the // input string and some replacements that were returned from the replace // function. Variable var_match_start(this, MachineRepresentation::kTagged); var_match_start.Bind(smi_zero); Node* const end = SmiUntag(res_length); Variable var_i(this, MachineType::PointerRepresentation()); var_i.Bind(int_zero); Variable* vars[] = {&var_i, &var_match_start}; Label loop(this, 2, vars); Goto(&loop); Bind(&loop); { Node* const i = var_i.value(); GotoIfNot(IntPtrLessThan(i, end), &create_result); Node* const elem = LoadFixedArrayElement(res_elems, i); Label if_issmi(this), if_isstring(this), loop_epilogue(this); Branch(TaggedIsSmi(elem), &if_issmi, &if_isstring); Bind(&if_issmi); { // Integers represent slices of the original string. Label if_isnegativeorzero(this), if_ispositive(this); BranchIfSmiLessThanOrEqual(elem, smi_zero, &if_isnegativeorzero, &if_ispositive); Bind(&if_ispositive); { Node* const int_elem = SmiUntag(elem); Node* const new_match_start = IntPtrAdd(WordShr(int_elem, IntPtrConstant(11)), WordAnd(int_elem, IntPtrConstant(0x7ff))); var_match_start.Bind(SmiTag(new_match_start)); Goto(&loop_epilogue); } Bind(&if_isnegativeorzero); { Node* const next_i = IntPtrAdd(i, int_one); var_i.Bind(next_i); Node* const next_elem = LoadFixedArrayElement(res_elems, next_i); Node* const new_match_start = SmiSub(next_elem, elem); var_match_start.Bind(new_match_start); Goto(&loop_epilogue); } } Bind(&if_isstring); { CSA_ASSERT(this, IsStringInstanceType(LoadInstanceType(elem))); Callable call_callable = CodeFactory::Call(isolate); Node* const replacement_obj = CallJS(call_callable, context, replace_callable, undefined, elem, var_match_start.value(), string); Node* const replacement_str = ToString(context, replacement_obj); StoreFixedArrayElement(res_elems, i, replacement_str); Node* const elem_length = LoadStringLength(elem); Node* const new_match_start = SmiAdd(var_match_start.value(), elem_length); var_match_start.Bind(new_match_start); Goto(&loop_epilogue); } Bind(&loop_epilogue); { var_i.Bind(IntPtrAdd(var_i.value(), int_one)); Goto(&loop); } } } Bind(&if_hasexplicitcaptures); { Node* const from = int_zero; Node* const to = SmiUntag(res_length); const int increment = 1; BuildFastLoop( from, to, [this, res_elems, isolate, native_context, context, undefined, replace_callable](Node* index) { Node* const elem = LoadFixedArrayElement(res_elems, index); Label do_continue(this); GotoIf(TaggedIsSmi(elem), &do_continue); // elem must be an Array. // Use the apply argument as backing for global RegExp properties. CSA_ASSERT(this, HasInstanceType(elem, JS_ARRAY_TYPE)); // TODO(jgruber): Remove indirection through Call->ReflectApply. Callable call_callable = CodeFactory::Call(isolate); Node* const reflect_apply = LoadContextElement(native_context, Context::REFLECT_APPLY_INDEX); Node* const replacement_obj = CallJS(call_callable, context, reflect_apply, undefined, replace_callable, undefined, elem); // Overwrite the i'th element in the results with the string we got // back from the callback function. Node* const replacement_str = ToString(context, replacement_obj); StoreFixedArrayElement(res_elems, index, replacement_str); Goto(&do_continue); Bind(&do_continue); }, increment, CodeStubAssembler::INTPTR_PARAMETERS, CodeStubAssembler::IndexAdvanceMode::kPost); Goto(&create_result); } Bind(&create_result); { Node* const result = CallRuntime(Runtime::kStringBuilderConcat, context, res, res_length, string); var_result.Bind(result); Goto(&out); } Bind(&out); return var_result.value(); } Node* RegExpBuiltinsAssembler::ReplaceSimpleStringFastPath( Node* context, Node* regexp, Node* string, Node* replace_string) { // The fast path is reached only if {receiver} is an unmodified // JSRegExp instance, {replace_value} is non-callable, and // ToString({replace_value}) does not contain '$', i.e. we're doing a simple // string replacement. Node* const int_zero = IntPtrConstant(0); Node* const smi_zero = SmiConstant(Smi::kZero); Label out(this); Variable var_result(this, MachineRepresentation::kTagged); // Load the last match info. Node* const native_context = LoadNativeContext(context); Node* const last_match_info = LoadContextElement(native_context, Context::REGEXP_LAST_MATCH_INFO_INDEX); // Is {regexp} global? Label if_isglobal(this), if_isnonglobal(this); Node* const flags = LoadObjectField(regexp, JSRegExp::kFlagsOffset); Node* const is_global = WordAnd(SmiUntag(flags), IntPtrConstant(JSRegExp::kGlobal)); Branch(WordEqual(is_global, int_zero), &if_isnonglobal, &if_isglobal); Bind(&if_isglobal); { // Hand off global regexps to runtime. FastStoreLastIndex(regexp, smi_zero); Node* const result = CallRuntime(Runtime::kStringReplaceGlobalRegExpWithString, context, string, regexp, replace_string, last_match_info); var_result.Bind(result); Goto(&out); } Bind(&if_isnonglobal); { // Run exec, then manually construct the resulting string. Label if_didnotmatch(this); Node* const match_indices = RegExpPrototypeExecBodyWithoutResult( context, regexp, string, &if_didnotmatch, true); // Successful match. { Node* const subject_start = smi_zero; Node* const match_start = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex); Node* const match_end = LoadFixedArrayElement( match_indices, RegExpMatchInfo::kFirstCaptureIndex + 1); Node* const subject_end = LoadStringLength(string); Label if_replaceisempty(this), if_replaceisnotempty(this); Node* const replace_length = LoadStringLength(replace_string); Branch(SmiEqual(replace_length, smi_zero), &if_replaceisempty, &if_replaceisnotempty); Bind(&if_replaceisempty); { // TODO(jgruber): We could skip many of the checks that using SubString // here entails. Node* const first_part = SubString(context, string, subject_start, match_start); Node* const second_part = SubString(context, string, match_end, subject_end); Node* const result = StringAdd(context, first_part, second_part); var_result.Bind(result); Goto(&out); } Bind(&if_replaceisnotempty); { Node* const first_part = SubString(context, string, subject_start, match_start); Node* const second_part = replace_string; Node* const third_part = SubString(context, string, match_end, subject_end); Node* result = StringAdd(context, first_part, second_part); result = StringAdd(context, result, third_part); var_result.Bind(result); Goto(&out); } } Bind(&if_didnotmatch); { var_result.Bind(string); Goto(&out); } } Bind(&out); return var_result.value(); } // Helper that skips a few initial checks. TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) { typedef RegExpReplaceDescriptor Descriptor; Node* const regexp = Parameter(Descriptor::kReceiver); Node* const string = Parameter(Descriptor::kString); Node* const replace_value = Parameter(Descriptor::kReplaceValue); Node* const context = Parameter(Descriptor::kContext); CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp))); CSA_ASSERT(this, IsString(string)); Label checkreplacestring(this), if_iscallable(this), runtime(this, Label::kDeferred); // 2. Is {replace_value} callable? GotoIf(TaggedIsSmi(replace_value), &checkreplacestring); Branch(IsCallableMap(LoadMap(replace_value)), &if_iscallable, &checkreplacestring); // 3. Does ToString({replace_value}) contain '$'? Bind(&checkreplacestring); { Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const replace_string = CallStub(tostring_callable, context, replace_value); // ToString(replaceValue) could potentially change the shape of the RegExp // object. Recheck that we are still on the fast path and bail to runtime // otherwise. { Label next(this); BranchIfFastRegExp(context, regexp, LoadMap(regexp), &next, &runtime); Bind(&next); } Callable indexof_callable = CodeFactory::StringIndexOf(isolate()); Node* const dollar_string = HeapConstant( isolate()->factory()->LookupSingleCharacterStringFromCode('$')); Node* const dollar_ix = CallStub(indexof_callable, context, replace_string, dollar_string, SmiConstant(0)); GotoIfNot(SmiEqual(dollar_ix, SmiConstant(-1)), &runtime); Return( ReplaceSimpleStringFastPath(context, regexp, string, replace_string)); } // {regexp} is unmodified and {replace_value} is callable. Bind(&if_iscallable); { Node* const replace_fn = replace_value; // Check if the {regexp} is global. Label if_isglobal(this), if_isnotglobal(this); Node* const is_global = FastFlagGetter(regexp, JSRegExp::kGlobal); Branch(is_global, &if_isglobal, &if_isnotglobal); Bind(&if_isglobal); Return(ReplaceGlobalCallableFastPath(context, regexp, string, replace_fn)); Bind(&if_isnotglobal); Return(CallRuntime(Runtime::kStringReplaceNonGlobalRegExpWithFunction, context, string, regexp, replace_fn)); } Bind(&runtime); Return(CallRuntime(Runtime::kRegExpReplace, context, regexp, string, replace_value)); } // ES#sec-regexp.prototype-@@replace // RegExp.prototype [ @@replace ] ( string, replaceValue ) TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) { Node* const maybe_receiver = Parameter(0); Node* const maybe_string = Parameter(1); Node* const replace_value = Parameter(2); Node* const context = Parameter(5); // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic: // // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace) // if (IsCallable(replace)) { // if (IsGlobal(receiver)) { // // Called 'fast-path' but contains several runtime calls. // ReplaceGlobalCallableFastPath() // } else { // CallRuntime(StringReplaceNonGlobalRegExpWithFunction) // } // } else { // if (replace.contains("$")) { // CallRuntime(RegExpReplace) // } else { // ReplaceSimpleStringFastPath() // Bails to runtime for global regexps. // } // } // Ensure {maybe_receiver} is a JSReceiver. ThrowIfNotJSReceiver(context, maybe_receiver, MessageTemplate::kIncompatibleMethodReceiver, "RegExp.prototype.@@replace"); Node* const receiver = maybe_receiver; // Convert {maybe_string} to a String. Callable tostring_callable = CodeFactory::ToString(isolate()); Node* const string = CallStub(tostring_callable, context, maybe_string); // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance? Label stub(this), runtime(this, Label::kDeferred); BranchIfFastRegExp(context, receiver, LoadMap(receiver), &stub, &runtime); Bind(&stub); Callable replace_callable = CodeFactory::RegExpReplace(isolate()); Return(CallStub(replace_callable, context, receiver, string, replace_value)); Bind(&runtime); Return(CallRuntime(Runtime::kRegExpReplace, context, receiver, string, replace_value)); } // Simple string matching functionality for internal use which does not modify // the last match info. TF_BUILTIN(RegExpInternalMatch, RegExpBuiltinsAssembler) { Node* const regexp = Parameter(1); Node* const string = Parameter(2); Node* const context = Parameter(5); Node* const null = NullConstant(); Node* const smi_zero = SmiConstant(Smi::FromInt(0)); Node* const native_context = LoadNativeContext(context); Node* const internal_match_info = LoadContextElement( native_context, Context::REGEXP_INTERNAL_MATCH_INFO_INDEX); Callable exec_callable = CodeFactory::RegExpExec(isolate()); Node* const match_indices = CallStub(exec_callable, context, regexp, string, smi_zero, internal_match_info); Label if_matched(this), if_didnotmatch(this); Branch(WordEqual(match_indices, null), &if_didnotmatch, &if_matched); Bind(&if_didnotmatch); Return(null); Bind(&if_matched); { Node* result = ConstructNewResultFromMatchInfo(context, regexp, match_indices, string); Return(result); } } } // namespace internal } // namespace v8