[regexp] Ensure there are no shape changes on the fast path
BUG=v8:5437,chromium:708247 Review-Url: https://codereview.chromium.org/2797993002 Cr-Commit-Position: refs/heads/master@{#44428}
This commit is contained in:
parent
108e96a4fc
commit
ae45935646
@ -482,7 +482,9 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
|
|||||||
Node* const int_zero = IntPtrConstant(0);
|
Node* const int_zero = IntPtrConstant(0);
|
||||||
Node* const smi_zero = SmiConstant(Smi::kZero);
|
Node* const smi_zero = SmiConstant(Smi::kZero);
|
||||||
|
|
||||||
if (!is_fastpath) {
|
if (is_fastpath) {
|
||||||
|
CSA_ASSERT(this, IsFastRegExpNoPrototype(context, regexp, LoadMap(regexp)));
|
||||||
|
} else {
|
||||||
ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
|
ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
|
||||||
"RegExp.prototype.exec");
|
"RegExp.prototype.exec");
|
||||||
}
|
}
|
||||||
@ -499,18 +501,23 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
|
|||||||
Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
|
Node* const regexp_lastindex = LoadLastIndex(context, regexp, is_fastpath);
|
||||||
var_lastindex.Bind(regexp_lastindex);
|
var_lastindex.Bind(regexp_lastindex);
|
||||||
|
|
||||||
// Omit ToLength if lastindex is a non-negative smi.
|
if (is_fastpath) {
|
||||||
Label call_tolength(this, Label::kDeferred), next(this);
|
// ToLength on a positive smi is a nop and can be skipped.
|
||||||
Branch(TaggedIsPositiveSmi(regexp_lastindex), &next, &call_tolength);
|
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);
|
Bind(&call_tolength);
|
||||||
{
|
{
|
||||||
var_lastindex.Bind(
|
var_lastindex.Bind(
|
||||||
CallBuiltin(Builtins::kToLength, context, regexp_lastindex));
|
CallBuiltin(Builtins::kToLength, context, regexp_lastindex));
|
||||||
Goto(&next);
|
Goto(&next);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind(&next);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bind(&next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the regexp is global or sticky, which determines whether we
|
// Check whether the regexp is global or sticky, which determines whether we
|
||||||
@ -658,7 +665,12 @@ Node* RegExpBuiltinsAssembler::ThrowIfNotJSReceiver(
|
|||||||
return var_value_map.value();
|
return var_value_map.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
|
Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context,
|
||||||
|
Node* const object,
|
||||||
|
Node* const map) {
|
||||||
|
Label out(this);
|
||||||
|
Variable var_result(this, MachineRepresentation::kWord32);
|
||||||
|
|
||||||
Node* const native_context = LoadNativeContext(context);
|
Node* const native_context = LoadNativeContext(context);
|
||||||
Node* const regexp_fun =
|
Node* const regexp_fun =
|
||||||
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
|
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
|
||||||
@ -666,17 +678,33 @@ Node* RegExpBuiltinsAssembler::IsInitialRegExpMap(Node* context, Node* map) {
|
|||||||
LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
LoadObjectField(regexp_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
||||||
Node* const has_initialmap = WordEqual(map, initial_map);
|
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.
|
// RegExp fast path implementations rely on unmodified JSRegExp instances.
|
||||||
// We use a fairly coarse granularity for this and simply check whether both
|
// 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
|
// the regexp itself is unmodified (i.e. its map has not changed), its
|
||||||
// prototype is unmodified.
|
// prototype is unmodified, and lastIndex is a non-negative smi.
|
||||||
void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
|
void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
|
||||||
|
Node* const object,
|
||||||
Node* const map,
|
Node* const map,
|
||||||
Label* const if_isunmodified,
|
Label* const if_isunmodified,
|
||||||
Label* const if_ismodified) {
|
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 native_context = LoadNativeContext(context);
|
||||||
Node* const regexp_fun =
|
Node* const regexp_fun =
|
||||||
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
|
LoadContextElement(native_context, Context::REGEXP_FUNCTION_INDEX);
|
||||||
@ -692,18 +720,21 @@ void RegExpBuiltinsAssembler::BranchIfFastRegExp(Node* const context,
|
|||||||
Node* const proto_has_initialmap =
|
Node* const proto_has_initialmap =
|
||||||
WordEqual(proto_map, initial_proto_initial_map);
|
WordEqual(proto_map, initial_proto_initial_map);
|
||||||
|
|
||||||
// TODO(ishell): Update this check once map changes for constant field
|
GotoIfNot(proto_has_initialmap, if_ismodified);
|
||||||
// tracking are landing.
|
|
||||||
|
|
||||||
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* RegExpBuiltinsAssembler::IsFastRegExp(Node* const context,
|
||||||
Node* const map) {
|
Node* const object,
|
||||||
|
Node* const map) {
|
||||||
Label yup(this), nope(this), out(this);
|
Label yup(this), nope(this), out(this);
|
||||||
Variable var_result(this, MachineRepresentation::kWord32);
|
Variable var_result(this, MachineRepresentation::kWord32);
|
||||||
|
|
||||||
BranchIfFastRegExp(context, map, &yup, &nope);
|
BranchIfFastRegExp(context, object, map, &yup, &nope);
|
||||||
|
|
||||||
Bind(&yup);
|
Bind(&yup);
|
||||||
var_result.Bind(Int32Constant(1));
|
var_result.Bind(Int32Constant(1));
|
||||||
@ -753,7 +784,7 @@ TF_BUILTIN(RegExpPrototypeExec, RegExpBuiltinsAssembler) {
|
|||||||
Node* const string = ToString(context, maybe_string);
|
Node* const string = ToString(context, maybe_string);
|
||||||
|
|
||||||
Label if_isfastpath(this), if_isslowpath(this);
|
Label if_isfastpath(this), if_isslowpath(this);
|
||||||
Branch(IsInitialRegExpMap(context, regexp_map), &if_isfastpath,
|
Branch(IsFastRegExpNoPrototype(context, receiver, regexp_map), &if_isfastpath,
|
||||||
&if_isslowpath);
|
&if_isslowpath);
|
||||||
|
|
||||||
Bind(&if_isfastpath);
|
Bind(&if_isfastpath);
|
||||||
@ -960,7 +991,8 @@ TF_BUILTIN(RegExpPrototypeFlagsGetter, RegExpBuiltinsAssembler) {
|
|||||||
Node* const receiver = maybe_receiver;
|
Node* const receiver = maybe_receiver;
|
||||||
|
|
||||||
Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
|
Label if_isfastpath(this), if_isslowpath(this, Label::kDeferred);
|
||||||
Branch(IsInitialRegExpMap(context, map), &if_isfastpath, &if_isslowpath);
|
Branch(IsFastRegExpNoPrototype(context, receiver, map), &if_isfastpath,
|
||||||
|
&if_isslowpath);
|
||||||
|
|
||||||
Bind(&if_isfastpath);
|
Bind(&if_isfastpath);
|
||||||
Return(FlagsGetter(context, receiver, true));
|
Return(FlagsGetter(context, receiver, true));
|
||||||
@ -1405,8 +1437,6 @@ TF_BUILTIN(RegExpPrototypeUnicodeGetter, RegExpBuiltinsAssembler) {
|
|||||||
// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
|
// ES#sec-regexpexec Runtime Semantics: RegExpExec ( R, S )
|
||||||
Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
|
Node* RegExpBuiltinsAssembler::RegExpExec(Node* context, Node* regexp,
|
||||||
Node* string) {
|
Node* string) {
|
||||||
CSA_ASSERT(this, Word32BinaryNot(IsFastRegExpMap(context, LoadMap(regexp))));
|
|
||||||
|
|
||||||
Variable var_result(this, MachineRepresentation::kTagged);
|
Variable var_result(this, MachineRepresentation::kTagged);
|
||||||
Label out(this);
|
Label out(this);
|
||||||
|
|
||||||
@ -1471,7 +1501,7 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
|
|||||||
Node* const string = ToString(context, maybe_string);
|
Node* const string = ToString(context, maybe_string);
|
||||||
|
|
||||||
Label fast_path(this), slow_path(this);
|
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);
|
Bind(&fast_path);
|
||||||
{
|
{
|
||||||
@ -1800,12 +1830,24 @@ void RegExpBuiltinsAssembler::RegExpPrototypeMatchBody(Node* const context,
|
|||||||
Node* const match_length = LoadStringLength(match);
|
Node* const match_length = LoadStringLength(match);
|
||||||
GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
|
GotoIfNot(SmiEqual(match_length, smi_zero), &loop);
|
||||||
|
|
||||||
Node* const last_index =
|
Node* last_index = LoadLastIndex(context, regexp, is_fastpath);
|
||||||
CallBuiltin(Builtins::kToLength, context,
|
if (is_fastpath) {
|
||||||
LoadLastIndex(context, regexp, is_fastpath));
|
CSA_ASSERT(this, TaggedIsPositiveSmi(last_index));
|
||||||
|
} else {
|
||||||
|
last_index = CallBuiltin(Builtins::kToLength, context, last_index);
|
||||||
|
}
|
||||||
|
|
||||||
Node* const new_last_index =
|
Node* const new_last_index =
|
||||||
AdvanceStringIndex(string, last_index, is_unicode);
|
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);
|
StoreLastIndex(context, regexp, new_last_index, is_fastpath);
|
||||||
|
|
||||||
Goto(&loop);
|
Goto(&loop);
|
||||||
@ -1839,7 +1881,7 @@ TF_BUILTIN(RegExpPrototypeMatch, RegExpBuiltinsAssembler) {
|
|||||||
Node* const string = ToString(context, maybe_string);
|
Node* const string = ToString(context, maybe_string);
|
||||||
|
|
||||||
Label fast_path(this), slow_path(this);
|
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);
|
Bind(&fast_path);
|
||||||
RegExpPrototypeMatchBody(context, receiver, string, true);
|
RegExpPrototypeMatchBody(context, receiver, string, true);
|
||||||
@ -1961,7 +2003,7 @@ TF_BUILTIN(RegExpPrototypeSearch, RegExpBuiltinsAssembler) {
|
|||||||
Node* const string = ToString(context, maybe_string);
|
Node* const string = ToString(context, maybe_string);
|
||||||
|
|
||||||
Label fast_path(this), slow_path(this);
|
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);
|
Bind(&fast_path);
|
||||||
RegExpPrototypeSearchBodyFast(context, receiver, string);
|
RegExpPrototypeSearchBodyFast(context, receiver, string);
|
||||||
@ -2219,7 +2261,7 @@ TF_BUILTIN(RegExpSplit, RegExpBuiltinsAssembler) {
|
|||||||
Node* const maybe_limit = Parameter(Descriptor::kLimit);
|
Node* const maybe_limit = Parameter(Descriptor::kLimit);
|
||||||
Node* const context = Parameter(Descriptor::kContext);
|
Node* const context = Parameter(Descriptor::kContext);
|
||||||
|
|
||||||
CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
|
CSA_ASSERT(this, IsFastRegExp(context, regexp, LoadMap(regexp)));
|
||||||
CSA_ASSERT(this, IsString(string));
|
CSA_ASSERT(this, IsString(string));
|
||||||
|
|
||||||
// TODO(jgruber): Even if map checks send us to the fast path, we still need
|
// TODO(jgruber): Even if map checks send us to the fast path, we still need
|
||||||
@ -2273,7 +2315,7 @@ TF_BUILTIN(RegExpPrototypeSplit, RegExpBuiltinsAssembler) {
|
|||||||
Node* const string = ToString(context, maybe_string);
|
Node* const string = ToString(context, maybe_string);
|
||||||
|
|
||||||
Label stub(this), runtime(this, Label::kDeferred);
|
Label stub(this), runtime(this, Label::kDeferred);
|
||||||
BranchIfFastRegExp(context, map, &stub, &runtime);
|
BranchIfFastRegExp(context, receiver, map, &stub, &runtime);
|
||||||
|
|
||||||
Bind(&stub);
|
Bind(&stub);
|
||||||
Return(CallBuiltin(Builtins::kRegExpSplit, context, receiver, string,
|
Return(CallBuiltin(Builtins::kRegExpSplit, context, receiver, string,
|
||||||
@ -2599,7 +2641,7 @@ TF_BUILTIN(RegExpReplace, RegExpBuiltinsAssembler) {
|
|||||||
Node* const replace_value = Parameter(Descriptor::kReplaceValue);
|
Node* const replace_value = Parameter(Descriptor::kReplaceValue);
|
||||||
Node* const context = Parameter(Descriptor::kContext);
|
Node* const context = Parameter(Descriptor::kContext);
|
||||||
|
|
||||||
CSA_ASSERT(this, IsFastRegExpMap(context, LoadMap(regexp)));
|
CSA_ASSERT(this, IsFastRegExp(context, regexp, LoadMap(regexp)));
|
||||||
CSA_ASSERT(this, IsString(string));
|
CSA_ASSERT(this, IsString(string));
|
||||||
|
|
||||||
Label checkreplacestring(this), if_iscallable(this),
|
Label checkreplacestring(this), if_iscallable(this),
|
||||||
@ -2688,7 +2730,7 @@ TF_BUILTIN(RegExpPrototypeReplace, RegExpBuiltinsAssembler) {
|
|||||||
|
|
||||||
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
|
// Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
|
||||||
Label stub(this), runtime(this, Label::kDeferred);
|
Label stub(this), runtime(this, Label::kDeferred);
|
||||||
BranchIfFastRegExp(context, map, &stub, &runtime);
|
BranchIfFastRegExp(context, receiver, map, &stub, &runtime);
|
||||||
|
|
||||||
Bind(&stub);
|
Bind(&stub);
|
||||||
Return(CallBuiltin(Builtins::kRegExpReplace, context, receiver, string,
|
Return(CallBuiltin(Builtins::kRegExpReplace, context, receiver, string,
|
||||||
|
@ -15,8 +15,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
|||||||
explicit RegExpBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
explicit RegExpBuiltinsAssembler(compiler::CodeAssemblerState* state)
|
||||||
: CodeStubAssembler(state) {}
|
: CodeStubAssembler(state) {}
|
||||||
|
|
||||||
void BranchIfFastRegExp(Node* const context, Node* const map,
|
void BranchIfFastRegExp(Node* const context, Node* const object,
|
||||||
Label* const if_isunmodified,
|
Node* const map, Label* const if_isunmodified,
|
||||||
Label* const if_ismodified);
|
Label* const if_ismodified);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -58,9 +58,13 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
|
|||||||
char const* method_name);
|
char const* method_name);
|
||||||
|
|
||||||
// Analogous to BranchIfFastRegExp, for use in asserts.
|
// Analogous to BranchIfFastRegExp, for use in asserts.
|
||||||
Node* IsFastRegExpMap(Node* const context, Node* const map);
|
Node* IsFastRegExp(Node* const context, Node* const object, Node* const map);
|
||||||
|
|
||||||
|
// Performs fast path checks on the given object itself, but omits prototype
|
||||||
|
// checks.
|
||||||
|
Node* IsFastRegExpNoPrototype(Node* const context, Node* const object,
|
||||||
|
Node* const map);
|
||||||
|
|
||||||
Node* IsInitialRegExpMap(Node* context, Node* map);
|
|
||||||
void BranchIfFastRegExpResult(Node* context, Node* map,
|
void BranchIfFastRegExpResult(Node* context, Node* map,
|
||||||
Label* if_isunmodified, Label* if_ismodified);
|
Label* if_isunmodified, Label* if_ismodified);
|
||||||
|
|
||||||
|
@ -969,7 +969,7 @@ void StringBuiltinsAssembler::MaybeCallFunctionAtSymbol(
|
|||||||
Label stub_call(this), slow_lookup(this);
|
Label stub_call(this), slow_lookup(this);
|
||||||
|
|
||||||
RegExpBuiltinsAssembler regexp_asm(state());
|
RegExpBuiltinsAssembler regexp_asm(state());
|
||||||
regexp_asm.BranchIfFastRegExp(context, object_map, &stub_call,
|
regexp_asm.BranchIfFastRegExp(context, object, object_map, &stub_call,
|
||||||
&slow_lookup);
|
&slow_lookup);
|
||||||
|
|
||||||
Bind(&stub_call);
|
Bind(&stub_call);
|
||||||
|
@ -145,7 +145,14 @@ bool RegExpUtils::IsUnmodifiedRegExp(Isolate* isolate, Handle<Object> obj) {
|
|||||||
if (!proto->IsJSReceiver()) return false;
|
if (!proto->IsJSReceiver()) return false;
|
||||||
|
|
||||||
Handle<Map> initial_proto_initial_map = isolate->regexp_prototype_map();
|
Handle<Map> initial_proto_initial_map = isolate->regexp_prototype_map();
|
||||||
return (JSReceiver::cast(proto)->map() == *initial_proto_initial_map);
|
if (JSReceiver::cast(proto)->map() != *initial_proto_initial_map) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The smi check is required to omit ToLength(lastIndex) calls with possible
|
||||||
|
// user-code execution on the fast path.
|
||||||
|
Object* last_index = JSRegExp::cast(recv)->LastIndex();
|
||||||
|
return last_index->IsSmi() && Smi::cast(last_index)->value() >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RegExpUtils::AdvanceStringIndex(Isolate* isolate, Handle<String> string,
|
int RegExpUtils::AdvanceStringIndex(Isolate* isolate, Handle<String> string,
|
||||||
|
26
test/mjsunit/regress/regress-708247.js
Normal file
26
test/mjsunit/regress/regress-708247.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
// Flags: --predictable
|
||||||
|
|
||||||
|
const str = '2016-01-02';
|
||||||
|
|
||||||
|
function t() {
|
||||||
|
var re;
|
||||||
|
function toDictMode() {
|
||||||
|
for (var i = 0; i < 100; i++) { // Loop is required.
|
||||||
|
re.x = 42;
|
||||||
|
delete re.x;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
re = /-/g; // Needs to be global to trigger lastIndex accesses.
|
||||||
|
re.lastIndex = { valueOf : toDictMode };
|
||||||
|
return re.exec(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var q = 0; q < 10000; q++) {
|
||||||
|
t(); // Needs repetitions to trigger a crash.
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user