| Index: src/builtins/builtins-regexp.cc
|
| diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc
|
| index 93cb55899b449f95cecd87cde850d823a1551bbc..dbd968eb2670cc84ecba18225f65aac207083439 100644
|
| --- a/src/builtins/builtins-regexp.cc
|
| +++ b/src/builtins/builtins-regexp.cc
|
| @@ -247,19 +247,24 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
|
| Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
|
| var_lastindex.Bind(regexp_lastindex);
|
|
|
| - // Omit ToLength if lastindex is a non-negative smi.
|
| - Label call_tolength(this, Label::kDeferred), next(this);
|
| - Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
|
| + 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(&call_tolength);
|
| - {
|
| - Callable tolength_callable = CodeFactory::ToLength(isolate);
|
| - var_lastindex.Bind(
|
| - CallStub(tolength_callable, context, regexp_lastindex));
|
| - Goto(&next);
|
| + Bind(&next);
|
| }
|
| -
|
| - Bind(&next);
|
| }
|
|
|
| // Check whether the regexp is global or sticky, which determines whether we
|
| @@ -408,7 +413,11 @@ Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver(
|
| return var_value_map.value();
|
| }
|
|
|
| -Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
|
| +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);
|
| @@ -416,17 +425,33 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
|
| LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
| Node* const has_initialmap = WordEqual(map, initial_map);
|
|
|
| - return has_initialmap;
|
| + 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) and its
|
| -// prototype is unmodified.
|
| +// 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);
|
| @@ -442,18 +467,21 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
|
| Node* const proto_has_initialmap =
|
| WordEqual(proto_map, initial_proto_initial_map);
|
|
|
| - // TODO(ishell): Update this check once map changes for constant field
|
| - // tracking are landing.
|
| + GotoIfNot(proto_has_initialmap, if_ismodified);
|
|
|
| - Branch(proto_has_initialmap, if_isunmodified, 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, map, &yup, &nope);
|
| + BranchIfFastRegExp(context, object, map, &yup, &nope);
|
|
|
| Bind(&yup);
|
| var_result.Bind(Int32Constant(1));
|
| @@ -494,7 +522,7 @@ TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) {
|
| Node* const string = ToString(context, maybe_string);
|
|
|
| Label if_isfastpath(this), if_isslowpath(this);
|
| - Branch(IsInitialRegExpMap(context, regexp_map), &if_isfastpath,
|
| + Branch(IsInitialRegExpMap(context, receiver, regexp_map), &if_isfastpath,
|
| &if_isslowpath);
|
|
|
| Bind(&if_isfastpath);
|
| @@ -681,7 +709,8 @@ TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) {
|
| Node* const receiver = maybe_receiver;
|
|
|
| Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
|
| - Branch(IsInitialRegExpMap(context, map), &if_isfastpath, &if_isslowpath);
|
| + Branch(IsInitialRegExpMap(context, receiver, map), &if_isfastpath,
|
| + &if_isslowpath);
|
|
|
| Bind(&if_isfastpath);
|
| Return(FlagsGetter(context, receiver, true));
|
| @@ -1221,7 +1250,7 @@ Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
|
| Label out(this), if_isfastpath(this), if_isslowpath(this);
|
|
|
| Node* const map = LoadMap(regexp);
|
| - BranchIfFastRegExp(context, map, &if_isfastpath, &if_isslowpath);
|
| + BranchIfFastRegExp(context, regexp, map, &if_isfastpath, &if_isslowpath);
|
|
|
| Bind(&if_isfastpath);
|
| {
|
| @@ -1295,7 +1324,7 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
|
| Node* const string = ToString(context, maybe_string);
|
|
|
| Label fast_path(this), slow_path(this);
|
| - BranchIfFastRegExp(context, map, &fast_path, &slow_path);
|
| + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path);
|
|
|
| Bind(&fast_path);
|
| {
|
| @@ -1630,13 +1659,24 @@ void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
|
| GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
|
|
|
| Node* last_index = LoadLastIndex(context, regexp, is_fastpath);
|
| -
|
| - Callable tolength_callable = CodeFactory::ToLength(isolate);
|
| - last_index = CallStub(tolength_callable, context, last_index);
|
| + 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);
|
|
|
| + 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);
|
| @@ -1670,7 +1710,7 @@ TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) {
|
| Node* const string = ToString(context, maybe_string);
|
|
|
| Label fast_path(this), slow_path(this);
|
| - BranchIfFastRegExp(context, map, &fast_path, &slow_path);
|
| + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path);
|
|
|
| Bind(&fast_path);
|
| RegExpPrototypeMatchBody(context, receiver, string, true);
|
| @@ -1795,7 +1835,7 @@ TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) {
|
| Node* const string = ToString(context, maybe_string);
|
|
|
| Label fast_path(this), slow_path(this);
|
| - BranchIfFastRegExp(context, map, &fast_path, &slow_path);
|
| + BranchIfFastRegExp(context, receiver, map, &fast_path, &slow_path);
|
|
|
| Bind(&fast_path);
|
| RegExpPrototypeSearchBodyFast(context, receiver, string);
|
| @@ -2059,7 +2099,7 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
|
| Node* const maybe_limit = Parameter(Descriptor::kLimit);
|
| Node* const context = Parameter(Descriptor::kContext);
|
|
|
| - CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
|
| + 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
|
| @@ -2067,13 +2107,23 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
|
| // been changed.
|
|
|
| // Convert {maybe_limit} to a uint32, capping at the maximal smi value.
|
| - Variable var_limit(this, MachineRepresentation::kTagged);
|
| - Label if_limitissmimax(this), limit_done(this);
|
| + 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);
|
| {
|
| - 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);
|
| @@ -2093,6 +2143,14 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
|
| 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
|
| @@ -2113,7 +2171,7 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
|
| Node* const string = ToString(context, maybe_string);
|
|
|
| Label stub(this), runtime(this, Label::kDeferred);
|
| - BranchIfFastRegExp(context, map, &stub, &runtime);
|
| + BranchIfFastRegExp(context, receiver, map, &stub, &runtime);
|
|
|
| Bind(&stub);
|
| Callable split_callable = CodeFactory::RegExpSplit(isolate());
|
| @@ -2439,7 +2497,7 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
|
| Node* const replace_value = Parameter(Descriptor::kReplaceValue);
|
| Node* const context = Parameter(Descriptor::kContext);
|
|
|
| - CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
|
| + CSA_ASSERT(this, IsFastRegExpMap(context, regexp, LoadMap(regexp)));
|
| CSA_ASSERT(this, IsString(string));
|
|
|
| Label checkreplacestring(this), if_iscallable(this),
|
| @@ -2457,6 +2515,15 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
|
| 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('$'));
|
| @@ -2530,7 +2597,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
|
|
|
| // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
|
| Label stub(this), runtime(this, Label::kDeferred);
|
| - BranchIfFastRegExp(context, map, &stub, &runtime);
|
| + BranchIfFastRegExp(context, receiver, map, &stub, &runtime);
|
|
|
| Bind(&stub);
|
| Callable replace_callable = CodeFactory::RegExpReplace(isolate());
|
|
|