Commit e29fcbee authored by jgruber's avatar jgruber Committed by Commit bot

[regexp] Move RegExp.prototype[@@search] to TF

Implements upcoming changes to @@search according to
https://github.com/tc39/ecma262/pull/627.

This also adds SameValue to CodeStubAssembler and extracts a part of
CSA::TruncateTaggedToFloat64.

BUG=v8:5339

Review-Url: https://codereview.chromium.org/2438683005
Cr-Commit-Position: refs/heads/master@{#41000}
parent 054e1779
......@@ -1869,7 +1869,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
{
Handle<JSFunction> fun = SimpleCreateFunction(
isolate, factory->InternalizeUtf8String("[Symbol.search]"),
Builtins::kRegExpPrototypeSearch, 1, false);
Builtins::kRegExpPrototypeSearch, 1, true);
InstallFunction(prototype, fun, factory->search_symbol(), DONT_ENUM);
}
......
......@@ -170,34 +170,45 @@ BUILTIN(RegExpPrototypeCompile) {
namespace {
compiler::Node* FastLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* regexp) {
// Load the in-object field.
static const int field_offset =
JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
return a->LoadObjectField(regexp, field_offset);
}
compiler::Node* SlowLoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* regexp) {
// Load through the GetProperty stub.
typedef compiler::Node Node;
Node* const name =
a->HeapConstant(a->isolate()->factory()->lastIndex_string());
Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
return a->CallStub(getproperty_callable, context, regexp, name);
}
compiler::Node* LoadLastIndex(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* has_initialmap,
compiler::Node* regexp) {
typedef CodeStubAssembler::Variable Variable;
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
Variable var_value(a, MachineRepresentation::kTagged);
Label out(a), if_unmodified(a), if_modified(a, Label::kDeferred);
Label out(a), if_unmodified(a), if_modified(a);
a->Branch(has_initialmap, &if_unmodified, &if_modified);
a->Bind(&if_unmodified);
{
// Load the in-object field.
static const int field_offset =
JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
var_value.Bind(a->LoadObjectField(regexp, field_offset));
var_value.Bind(FastLoadLastIndex(a, context, regexp));
a->Goto(&out);
}
a->Bind(&if_modified);
{
// Load through the GetProperty stub.
Node* const name =
a->HeapConstant(a->isolate()->factory()->lastIndex_string());
Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
var_value.Bind(a->CallStub(getproperty_callable, context, regexp, name));
var_value.Bind(SlowLoadLastIndex(a, context, regexp));
a->Goto(&out);
}
......@@ -215,13 +226,25 @@ void FastStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
a->StoreObjectField(regexp, field_offset, value);
}
void SlowStoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* regexp, compiler::Node* value) {
// Store through runtime.
// TODO(ishell): Use SetPropertyStub here once available.
typedef compiler::Node Node;
Node* const name =
a->HeapConstant(a->isolate()->factory()->lastIndex_string());
Node* const language_mode = a->SmiConstant(Smi::FromInt(STRICT));
a->CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
language_mode);
}
void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
compiler::Node* has_initialmap, compiler::Node* regexp,
compiler::Node* value) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
Label out(a), if_unmodified(a), if_modified(a, Label::kDeferred);
Label out(a), if_unmodified(a), if_modified(a);
a->Branch(has_initialmap, &if_unmodified, &if_modified);
a->Bind(&if_unmodified);
......@@ -232,13 +255,7 @@ void StoreLastIndex(CodeStubAssembler* a, compiler::Node* context,
a->Bind(&if_modified);
{
// Store through runtime.
// TODO(ishell): Use SetPropertyStub here once available.
Node* const name =
a->HeapConstant(a->isolate()->factory()->lastIndex_string());
Node* const language_mode = a->SmiConstant(Smi::FromInt(STRICT));
a->CallRuntime(Runtime::kSetProperty, context, regexp, name, value,
language_mode);
SlowStoreLastIndex(a, context, regexp, value);
a->Goto(&out);
}
......@@ -1142,57 +1159,128 @@ BUILTIN(RegExpPrototypeMatch) {
return *isolate->factory()->NewJSArrayWithElements(elems);
}
// ES#sec-regexp.prototype-@@search
// RegExp.prototype [ @@search ] ( string )
BUILTIN(RegExpPrototypeSearch) {
HandleScope scope(isolate);
CHECK_RECEIVER(JSReceiver, recv, "RegExp.prototype.@@search");
namespace {
Handle<Object> string_obj = args.atOrUndefined(isolate, 1);
void Generate_RegExpPrototypeSearchBody(CodeStubAssembler* a,
compiler::Node* const receiver,
compiler::Node* const string,
compiler::Node* const context,
bool is_fastpath) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
Handle<String> string;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, string,
Object::ToString(isolate, string_obj));
Isolate* const isolate = a->isolate();
Node* const smi_zero = a->SmiConstant(Smi::kZero);
Handle<Object> previous_last_index_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, previous_last_index_obj,
RegExpUtils::GetLastIndex(isolate, recv));
// Grab the initial value of last index.
Node* const previous_last_index =
is_fastpath ? FastLoadLastIndex(a, context, receiver)
: SlowLoadLastIndex(a, context, receiver);
if (!previous_last_index_obj->SameValue(Smi::kZero)) {
RETURN_FAILURE_ON_EXCEPTION(isolate,
RegExpUtils::SetLastIndex(isolate, recv, 0));
// Ensure last index is 0.
if (is_fastpath) {
FastStoreLastIndex(a, context, receiver, smi_zero);
} else {
Label next(a);
a->GotoIf(a->SameValue(previous_last_index, smi_zero, context), &next);
SlowStoreLastIndex(a, context, receiver, smi_zero);
a->Goto(&next);
a->Bind(&next);
}
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
RegExpUtils::RegExpExec(isolate, recv, string,
isolate->factory()->undefined_value()));
Handle<Object> current_last_index_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, current_last_index_obj,
RegExpUtils::GetLastIndex(isolate, recv));
const bool is_last_index_unchanged =
current_last_index_obj->SameValue(*previous_last_index_obj);
if (!is_last_index_unchanged) {
if (previous_last_index_obj->IsSmi()) {
RETURN_FAILURE_ON_EXCEPTION(
isolate,
RegExpUtils::SetLastIndex(
isolate, recv, Smi::cast(*previous_last_index_obj)->value()));
} else {
RETURN_FAILURE_ON_EXCEPTION(
isolate,
Object::SetProperty(recv, isolate->factory()->lastIndex_string(),
previous_last_index_obj, STRICT));
// Call exec.
Node* const match_indices =
is_fastpath ? RegExpPrototypeExecInternal(a, context, receiver, string)
: RegExpExec(a, context, receiver, string);
// Reset last index if necessary.
if (is_fastpath) {
FastStoreLastIndex(a, context, receiver, previous_last_index);
} else {
Label next(a);
Node* const current_last_index = SlowLoadLastIndex(a, context, receiver);
a->GotoIf(a->SameValue(current_last_index, previous_last_index, context),
&next);
SlowStoreLastIndex(a, context, receiver, previous_last_index);
a->Goto(&next);
a->Bind(&next);
}
// Return -1 if no match was found.
{
Label next(a);
a->GotoUnless(a->WordEqual(match_indices, a->NullConstant()), &next);
a->Return(a->SmiConstant(-1));
a->Bind(&next);
}
// Return the index of the match.
{
Label fast_result(a), slow_result(a, Label::kDeferred);
Node* const native_context = a->LoadNativeContext(context);
Node* const initial_regexp_result_map =
a->LoadContextElement(native_context, Context::REGEXP_RESULT_MAP_INDEX);
Node* const match_indices_map = a->LoadMap(match_indices);
a->Branch(a->WordEqual(match_indices_map, initial_regexp_result_map),
&fast_result, &slow_result);
a->Bind(&fast_result);
{
Node* const index =
a->LoadObjectField(match_indices, JSRegExpResult::kIndexOffset,
MachineType::AnyTagged());
a->Return(index);
}
a->Bind(&slow_result);
{
Node* const name = a->HeapConstant(isolate->factory()->index_string());
Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
Node* const index =
a->CallStub(getproperty_callable, context, match_indices, name);
a->Return(index);
}
}
}
} // namespace
// ES#sec-regexp.prototype-@@search
// RegExp.prototype [ @@search ] ( string )
void Builtins::Generate_RegExpPrototypeSearch(CodeStubAssembler* a) {
typedef CodeStubAssembler::Label Label;
typedef compiler::Node Node;
if (result->IsNull(isolate)) return Smi::FromInt(-1);
Isolate* const isolate = a->isolate();
Node* const maybe_receiver = a->Parameter(0);
Node* const maybe_string = a->Parameter(1);
Node* const context = a->Parameter(4);
RETURN_RESULT_OR_FAILURE(
isolate, Object::GetProperty(result, isolate->factory()->index_string()));
// Ensure {maybe_receiver} is a JSReceiver.
Node* const map =
ThrowIfNotJSReceiver(a, isolate, context, maybe_receiver,
MessageTemplate::kIncompatibleMethodReceiver,
"RegExp.prototype.@@search");
Node* const receiver = maybe_receiver;
// Convert {maybe_string} to a String.
Node* const string = a->ToString(context, maybe_string);
Label fast_path(a), slow_path(a);
BranchIfFastPath(a, context, map, &fast_path, &slow_path);
a->Bind(&fast_path);
Generate_RegExpPrototypeSearchBody(a, receiver, string, context, true);
a->Bind(&slow_path);
Generate_RegExpPrototypeSearchBody(a, receiver, string, context, false);
}
namespace {
......
......@@ -610,7 +610,7 @@ namespace internal {
CPP(RegExpPrototypeMatch) \
TFJ(RegExpPrototypeMultilineGetter, 0) \
TFJ(RegExpPrototypeReplace, 2) \
CPP(RegExpPrototypeSearch) \
TFJ(RegExpPrototypeSearch, 1) \
CPP(RegExpPrototypeSourceGetter) \
CPP(RegExpPrototypeSpeciesGetter) \
CPP(RegExpPrototypeSplit) \
......
......@@ -2271,6 +2271,40 @@ void CodeStubAssembler::InitializeAllocationMemento(
}
}
Node* CodeStubAssembler::TryTaggedToFloat64(Node* value,
Label* if_valueisnotnumber) {
Label out(this);
Variable var_result(this, MachineRepresentation::kFloat64);
// Check if the {value} is a Smi or a HeapObject.
Label if_valueissmi(this), if_valueisnotsmi(this);
Branch(TaggedIsSmi(value), &if_valueissmi, &if_valueisnotsmi);
Bind(&if_valueissmi);
{
// Convert the Smi {value}.
var_result.Bind(SmiToFloat64(value));
Goto(&out);
}
Bind(&if_valueisnotsmi);
{
// Check if {value} is a HeapNumber.
Label if_valueisheapnumber(this);
Branch(IsHeapNumberMap(LoadMap(value)), &if_valueisheapnumber,
if_valueisnotnumber);
Bind(&if_valueisheapnumber);
{
// Load the floating point value.
var_result.Bind(LoadHeapNumberValue(value));
Goto(&out);
}
}
Bind(&out);
return var_result.value();
}
Node* CodeStubAssembler::TruncateTaggedToFloat64(Node* context, Node* value) {
// We might need to loop once due to ToNumber conversion.
Variable var_value(this, MachineRepresentation::kTagged),
......@@ -2280,42 +2314,23 @@ Node* CodeStubAssembler::TruncateTaggedToFloat64(Node* context, Node* value) {
Goto(&loop);
Bind(&loop);
{
Label if_valueisnotnumber(this, Label::kDeferred);
// Load the current {value}.
value = var_value.value();
// Check if the {value} is a Smi or a HeapObject.
Label if_valueissmi(this), if_valueisnotsmi(this);
Branch(TaggedIsSmi(value), &if_valueissmi, &if_valueisnotsmi);
Bind(&if_valueissmi);
{
// Convert the Smi {value}.
var_result.Bind(SmiToFloat64(value));
Goto(&done_loop);
}
// Convert {value} to Float64 if it is a number and convert it to a number
// otherwise.
Node* const result = TryTaggedToFloat64(value, &if_valueisnotnumber);
var_result.Bind(result);
Goto(&done_loop);
Bind(&if_valueisnotsmi);
Bind(&if_valueisnotnumber);
{
// Check if {value} is a HeapNumber.
Label if_valueisheapnumber(this),
if_valueisnotheapnumber(this, Label::kDeferred);
Branch(WordEqual(LoadMap(value), HeapNumberMapConstant()),
&if_valueisheapnumber, &if_valueisnotheapnumber);
Bind(&if_valueisheapnumber);
{
// Load the floating point value.
var_result.Bind(LoadHeapNumberValue(value));
Goto(&done_loop);
}
Bind(&if_valueisnotheapnumber);
{
// Convert the {value} to a Number first.
Callable callable = CodeFactory::NonNumberToNumber(isolate());
var_value.Bind(CallStub(callable, context, value));
Goto(&loop);
}
// Convert the {value} to a Number first.
Callable callable = CodeFactory::NonNumberToNumber(isolate());
var_value.Bind(CallStub(callable, context, value));
Goto(&loop);
}
}
Bind(&done_loop);
......@@ -6574,24 +6589,7 @@ Node* CodeStubAssembler::PrepareValueForWrite(Node* value,
Representation representation,
Label* bailout) {
if (representation.IsDouble()) {
Variable var_value(this, MachineRepresentation::kFloat64);
Label if_smi(this), if_heap_object(this), done(this);
Branch(TaggedIsSmi(value), &if_smi, &if_heap_object);
Bind(&if_smi);
{
var_value.Bind(SmiToFloat64(value));
Goto(&done);
}
Bind(&if_heap_object);
{
GotoUnless(
Word32Equal(LoadInstanceType(value), Int32Constant(HEAP_NUMBER_TYPE)),
bailout);
var_value.Bind(LoadHeapNumberValue(value));
Goto(&done);
}
Bind(&done);
value = var_value.value();
value = TryTaggedToFloat64(value, bailout);
} else if (representation.IsHeapObject()) {
// Field type is checked by the handler, here we only check if the value
// is a heap object.
......@@ -8576,6 +8574,95 @@ compiler::Node* CodeStubAssembler::StrictEqual(ResultMode mode,
return result.value();
}
// ECMA#sec-samevalue
// This algorithm differs from the Strict Equality Comparison Algorithm in its
// treatment of signed zeroes and NaNs.
compiler::Node* CodeStubAssembler::SameValue(compiler::Node* lhs,
compiler::Node* rhs,
compiler::Node* context) {
Variable var_result(this, MachineType::PointerRepresentation());
Label strict_equal(this), out(this);
Node* const int_false = IntPtrConstant(0);
Node* const int_true = IntPtrConstant(1);
Label if_equal(this), if_notequal(this);
Branch(WordEqual(lhs, rhs), &if_equal, &if_notequal);
Bind(&if_equal);
{
// This covers the case when {lhs} == {rhs}. We can simply return true
// because SameValue considers two NaNs to be equal.
var_result.Bind(int_true);
Goto(&out);
}
Bind(&if_notequal);
{
// This covers the case when {lhs} != {rhs}. We only handle numbers here
// and defer to StrictEqual for the rest.
Node* const lhs_float = TryTaggedToFloat64(lhs, &strict_equal);
Node* const rhs_float = TryTaggedToFloat64(rhs, &strict_equal);
Label if_lhsisnan(this), if_lhsnotnan(this);
BranchIfFloat64IsNaN(lhs_float, &if_lhsisnan, &if_lhsnotnan);
Bind(&if_lhsisnan);
{
// Return true iff {rhs} is NaN.
Node* const result =
Select(Float64Equal(rhs_float, rhs_float), int_false, int_true,
MachineType::PointerRepresentation());
var_result.Bind(result);
Goto(&out);
}
Bind(&if_lhsnotnan);
{
Label if_floatisequal(this), if_floatnotequal(this);
Branch(Float64Equal(lhs_float, rhs_float), &if_floatisequal,
&if_floatnotequal);
Bind(&if_floatisequal);
{
// We still need to handle the case when {lhs} and {rhs} are -0.0 and
// 0.0 (or vice versa). Compare the high word to
// distinguish between the two.
Node* const lhs_hi_word = Float64ExtractHighWord32(lhs_float);
Node* const rhs_hi_word = Float64ExtractHighWord32(rhs_float);
// If x is +0 and y is -0, return false.
// If x is -0 and y is +0, return false.
Node* const result = Word32Equal(lhs_hi_word, rhs_hi_word);
var_result.Bind(result);
Goto(&out);
}
Bind(&if_floatnotequal);
{
var_result.Bind(int_false);
Goto(&out);
}
}
}
Bind(&strict_equal);
{
Node* const is_equal = StrictEqual(kDontNegateResult, lhs, rhs, context);
Node* const result = WordEqual(is_equal, TrueConstant());
var_result.Bind(result);
Goto(&out);
}
Bind(&out);
return var_result.value();
}
compiler::Node* CodeStubAssembler::ForInFilter(compiler::Node* key,
compiler::Node* object,
compiler::Node* context) {
......
......@@ -578,6 +578,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
int base_allocation_size,
compiler::Node* allocation_site);
compiler::Node* TryTaggedToFloat64(compiler::Node* value,
Label* if_valueisnotnumber);
compiler::Node* TruncateTaggedToFloat64(compiler::Node* context,
compiler::Node* value);
compiler::Node* TruncateTaggedToWord32(compiler::Node* context,
......@@ -1104,6 +1106,14 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
compiler::Node* StrictEqual(ResultMode mode, compiler::Node* lhs,
compiler::Node* rhs, compiler::Node* context);
// ECMA#sec-samevalue
// Similar to StrictEqual except that NaNs are treated as equal and minus zero
// differs from positive zero.
// Unlike Equal and StrictEqual, returns a value suitable for use in Branch
// instructions, e.g. Branch(SameValue(...), &label).
compiler::Node* SameValue(compiler::Node* lhs, compiler::Node* rhs,
compiler::Node* context);
compiler::Node* HasProperty(
compiler::Node* object, compiler::Node* key, compiler::Node* context,
Runtime::FunctionId fallback_runtime_function_id = Runtime::kHasProperty);
......
......@@ -748,3 +748,19 @@ RegExp.prototype.exec = RegExpPrototypeExec;
var re = /./;
re.lastIndex = { [Symbol.toPrimitive]: 42 };
try { "abc".search(re); } catch (_) {} // Ensure we don't crash.
// Test lastIndex values of -0.0 and NaN (since @@search uses SameValue).
var re = /./;
re.exec = function(str) { assertEquals(0, re.lastIndex); return []; }
re.lastIndex = -0.0;
assertEquals(-0, re.lastIndex);
"abc".search(re);
assertEquals(-0, re.lastIndex);
var re = /./;
re.exec = function(str) { assertEquals(0, re.lastIndex); return []; }
re.lastIndex = NaN;
assertEquals(NaN, re.lastIndex);
"abc".search(re);
assertEquals(NaN, re.lastIndex);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment