[builtins] Speed-up Object.prototype.toString.

The @@toStringTag lookup in Object.prototype.toString causes quite a
lot of overhead and oftentimes dominates the builtin performance. These
lookups are almost always negative, especially for primitive values,
and Object.prototype.toString is often used to implement predicates
(like in Node core or in AngularJS), so having a way to skip the
negative lookup yields big performance gains.

This CL introduces a "MayHaveInterestingSymbols" bit on every map,
which says whether instances with this map may have an interesting
symbol. Currently only @@toStringTag is considered an interesting
symbol, but we can extend that in the future.

In the Object.prototype.toString we can use the interesting symbols
bit to do a quick check on the prototype chain to see if there are
any maps that might have the @@toStringTag, and if not, we can just
immediately return the result, which is very fast because it's derived
from the instance type. This also avoids the ToObject conversions for
primitive values, which is important, since this causes unnecessary
GC traffic and in for example AngularJS, strings are also often probed
via the Object.prototype.toString based predicates.

This boosts Speedometer/AngularJS by over 3% and Speedometer overall
by up to 1%. On the microbenchmark from the similar SpiderMonkey bug
(https://bugzilla.mozilla.org/show_bug.cgi?id=1369042), we go from
roughly 450ms to 70ms, which corresponds to a 6.5x improvement.

```
function f() {
    var res = "";
    var a = [1, 2, 3];
    var toString = Object.prototype.toString;
    var t = new Date;
    for (var i = 0; i < 5000000; i++)
	res = toString.call(a);
    print(new Date - t);
    return res;
}
f();
```

The design document at https://goo.gl/e8CruQ has some additional
data points.

TBR=ulan@chromium.org

Bug: v8:6654
Change-Id: I31932cf41ecddad079d294e2c322a852af0ed244
Reviewed-on: https://chromium-review.googlesource.com/593620
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47034}
This commit is contained in:
Benedikt Meurer 2017-08-01 10:11:14 +02:00 committed by Commit Bot
parent a4d914c904
commit 31800120cc
17 changed files with 336 additions and 163 deletions

View File

@ -132,6 +132,7 @@ void EnableAccessChecks(Isolate* isolate, Handle<JSObject> object) {
// Copy map so it won't interfere constructor's initial map.
Handle<Map> new_map = Map::Copy(old_map, "EnableAccessChecks");
new_map->set_is_access_check_needed(true);
new_map->set_may_have_interesting_symbols(true);
JSObject::MigrateToMap(object, new_map);
}
@ -558,6 +559,7 @@ MaybeHandle<JSObject> ApiNatives::InstantiateRemoteObject(
HOLEY_SMI_ELEMENTS);
object_map->SetConstructor(*constructor);
object_map->set_is_access_check_needed(true);
object_map->set_may_have_interesting_symbols(true);
Handle<JSObject> object = isolate->factory()->NewJSObjectFromMap(object_map);
JSObject::ForceSetPrototype(object, isolate->factory()->null_value());
@ -709,11 +711,13 @@ Handle<JSFunction> ApiNatives::CreateApiFunction(
// Mark as needs_access_check if needed.
if (obj->needs_access_check()) {
map->set_is_access_check_needed(true);
map->set_may_have_interesting_symbols(true);
}
// Set interceptor information in the map.
if (!obj->named_property_handler()->IsUndefined(isolate)) {
map->set_has_named_interceptor();
map->set_may_have_interesting_symbols(true);
}
if (!obj->indexed_property_handler()->IsUndefined(isolate)) {
map->set_has_indexed_interceptor();

View File

@ -959,6 +959,7 @@ void Genesis::CreateJSProxyMaps() {
Handle<Map> proxy_map =
factory()->NewMap(JS_PROXY_TYPE, JSProxy::kSize, PACKED_ELEMENTS);
proxy_map->set_dictionary_map(true);
proxy_map->set_may_have_interesting_symbols(true);
native_context()->set_proxy_map(*proxy_map);
Handle<Map> proxy_callable_map = Map::Copy(proxy_map, "callable Proxy");
@ -1111,6 +1112,8 @@ Handle<JSGlobalObject> Genesis::CreateNewGlobals(
js_global_object_function->initial_map()->set_is_prototype_map(true);
js_global_object_function->initial_map()->set_dictionary_map(true);
js_global_object_function->initial_map()->set_may_have_interesting_symbols(
true);
Handle<JSGlobalObject> global_object =
factory()->NewJSGlobalObject(js_global_object_function);
@ -1135,6 +1138,7 @@ Handle<JSGlobalObject> Genesis::CreateNewGlobals(
global_proxy_function->shared()->set_instance_class_name(*global_name);
global_proxy_function->initial_map()->set_is_access_check_needed(true);
global_proxy_function->initial_map()->set_has_hidden_prototype(true);
global_proxy_function->initial_map()->set_may_have_interesting_symbols(true);
native_context()->set_global_proxy_function(*global_proxy_function);
// Set global_proxy.__proto__ to js_global after ConfigureGlobalObjects
@ -1384,7 +1388,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kObjectPrototypePropertyIsEnumerable, 1, false);
Handle<JSFunction> object_to_string = SimpleInstallFunction(
isolate->initial_object_prototype(), factory->toString_string(),
Builtins::kObjectProtoToString, 0, true);
Builtins::kObjectPrototypeToString, 0, true);
native_context()->set_object_to_string(*object_to_string);
Handle<JSFunction> object_value_of = SimpleInstallFunction(
isolate->initial_object_prototype(), "valueOf",
@ -5289,6 +5293,7 @@ Genesis::Genesis(Isolate* isolate,
JS_GLOBAL_PROXY_TYPE, proxy_size, HOLEY_SMI_ELEMENTS);
global_proxy_map->set_is_access_check_needed(true);
global_proxy_map->set_has_hidden_prototype(true);
global_proxy_map->set_may_have_interesting_symbols(true);
// A remote global proxy has no native context.
global_proxy->set_native_context(heap()->null_value());

View File

@ -737,7 +737,7 @@ namespace internal {
CPP(ObjectLookupSetter) \
CPP(ObjectPreventExtensions) \
/* ES6 #sec-object.prototype.tostring */ \
TFJ(ObjectProtoToString, 0) \
TFJ(ObjectPrototypeToString, 0) \
/* ES6 #sec-object.prototype.valueof */ \
TFJ(ObjectPrototypeValueOf, 0) \
TFJ(ObjectPrototypeIsPrototypeOf, 1, kValue) \

View File

@ -20,23 +20,9 @@ class ObjectBuiltinsAssembler : public CodeStubAssembler {
: CodeStubAssembler(state) {}
protected:
void IsString(Node* object, Label* if_string, Label* if_notstring);
void ReturnToStringFormat(Node* context, Node* string);
};
void ObjectBuiltinsAssembler::IsString(Node* object, Label* if_string,
Label* if_notstring) {
Label if_notsmi(this);
Branch(TaggedIsSmi(object), if_notstring, &if_notsmi);
BIND(&if_notsmi);
{
Node* instance_type = LoadInstanceType(object);
Branch(IsStringInstanceType(instance_type), if_string, if_notstring);
}
}
void ObjectBuiltinsAssembler::ReturnToStringFormat(Node* context,
Node* string) {
Node* lhs = StringConstant("[object ");
@ -243,165 +229,259 @@ TF_BUILTIN(ObjectPrototypeIsPrototypeOf, ObjectBuiltinsAssembler) {
Return(FalseConstant());
}
// ES6 #sec-object.prototype.tostring
TF_BUILTIN(ObjectProtoToString, ObjectBuiltinsAssembler) {
Label return_undefined(this, Label::kDeferred),
return_null(this, Label::kDeferred),
return_arguments(this, Label::kDeferred), return_array(this),
return_api(this, Label::kDeferred), return_object(this),
return_regexp(this), return_function(this), return_error(this),
return_date(this), return_jsvalue(this),
return_jsproxy(this, Label::kDeferred);
Label if_isproxy(this, Label::kDeferred);
Label checkstringtag(this);
Label if_tostringtag(this), if_notostringtag(this);
// ES #sec-object.prototype.tostring
TF_BUILTIN(ObjectPrototypeToString, ObjectBuiltinsAssembler) {
Label checkstringtag(this), if_apiobject(this, Label::kDeferred),
if_arguments(this), if_array(this), if_boolean(this), if_date(this),
if_error(this), if_function(this), if_number(this, Label::kDeferred),
if_object(this), if_primitive(this), if_proxy(this, Label::kDeferred),
if_regexp(this), if_string(this), if_symbol(this, Label::kDeferred),
if_value(this);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* context = Parameter(Descriptor::kContext);
GotoIf(WordEqual(receiver, UndefinedConstant()), &return_undefined);
// This is arranged to check the likely cases first.
VARIABLE(var_default, MachineRepresentation::kTagged);
VARIABLE(var_holder, MachineRepresentation::kTagged, receiver);
GotoIf(TaggedIsSmi(receiver), &if_number);
Node* receiver_map = LoadMap(receiver);
Node* receiver_instance_type = LoadMapInstanceType(receiver_map);
GotoIf(IsPrimitiveInstanceType(receiver_instance_type), &if_primitive);
const struct {
InstanceType value;
Label* label;
} kJumpTable[] = {{JS_OBJECT_TYPE, &if_object},
{JS_ARRAY_TYPE, &if_array},
{JS_FUNCTION_TYPE, &if_function},
{JS_REGEXP_TYPE, &if_regexp},
{JS_ARGUMENTS_TYPE, &if_arguments},
{JS_DATE_TYPE, &if_date},
{JS_BOUND_FUNCTION_TYPE, &if_function},
{JS_API_OBJECT_TYPE, &if_apiobject},
{JS_SPECIAL_API_OBJECT_TYPE, &if_apiobject},
{JS_PROXY_TYPE, &if_proxy},
{JS_ERROR_TYPE, &if_error},
{JS_VALUE_TYPE, &if_value}};
size_t const kNumCases = arraysize(kJumpTable);
Label* case_labels[kNumCases];
int32_t case_values[kNumCases];
for (size_t i = 0; i < kNumCases; ++i) {
case_labels[i] = kJumpTable[i].label;
case_values[i] = kJumpTable[i].value;
}
Switch(receiver_instance_type, &if_object, case_values, case_labels,
arraysize(case_values));
GotoIf(WordEqual(receiver, NullConstant()), &return_null);
receiver = CallBuiltin(Builtins::kToObject, context, receiver);
Node* receiver_instance_type = LoadInstanceType(receiver);
// for proxies, check IsArray before getting @@toStringTag
VARIABLE(var_proxy_is_array, MachineRepresentation::kTagged);
var_proxy_is_array.Bind(BooleanConstant(false));
Branch(Word32Equal(receiver_instance_type, Int32Constant(JS_PROXY_TYPE)),
&if_isproxy, &checkstringtag);
BIND(&if_isproxy);
BIND(&if_apiobject);
{
// This can throw
var_proxy_is_array.Bind(
CallRuntime(Runtime::kArrayIsArray, context, receiver));
// Lookup the @@toStringTag property on the {receiver}.
VARIABLE(var_tag, MachineRepresentation::kTagged,
GetProperty(context, receiver,
isolate()->factory()->to_string_tag_symbol()));
Label if_tagisnotstring(this), if_tagisstring(this);
GotoIf(TaggedIsSmi(var_tag.value()), &if_tagisnotstring);
Branch(IsString(var_tag.value()), &if_tagisstring, &if_tagisnotstring);
BIND(&if_tagisnotstring);
{
var_tag.Bind(
CallStub(Builtins::CallableFor(isolate(), Builtins::kClassOf),
context, receiver));
Goto(&if_tagisstring);
}
BIND(&if_tagisstring);
ReturnToStringFormat(context, var_tag.value());
}
BIND(&if_arguments);
{
var_default.Bind(LoadRoot(Heap::karguments_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_array);
{
var_default.Bind(LoadRoot(Heap::karray_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_boolean);
{
Node* native_context = LoadNativeContext(context);
Node* boolean_constructor =
LoadContextElement(native_context, Context::BOOLEAN_FUNCTION_INDEX);
Node* boolean_initial_map = LoadObjectField(
boolean_constructor, JSFunction::kPrototypeOrInitialMapOffset);
Node* boolean_prototype =
LoadObjectField(boolean_initial_map, Map::kPrototypeOffset);
var_default.Bind(LoadRoot(Heap::kboolean_to_stringRootIndex));
var_holder.Bind(boolean_prototype);
Goto(&checkstringtag);
}
BIND(&if_date);
{
var_default.Bind(LoadRoot(Heap::kdate_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_error);
{
var_default.Bind(LoadRoot(Heap::kerror_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_function);
{
var_default.Bind(LoadRoot(Heap::kfunction_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_number);
{
Node* native_context = LoadNativeContext(context);
Node* number_constructor =
LoadContextElement(native_context, Context::NUMBER_FUNCTION_INDEX);
Node* number_initial_map = LoadObjectField(
number_constructor, JSFunction::kPrototypeOrInitialMapOffset);
Node* number_prototype =
LoadObjectField(number_initial_map, Map::kPrototypeOffset);
var_default.Bind(LoadRoot(Heap::knumber_to_stringRootIndex));
var_holder.Bind(number_prototype);
Goto(&checkstringtag);
}
BIND(&if_object);
{
CSA_ASSERT(this, IsJSReceiver(receiver));
var_default.Bind(LoadRoot(Heap::kobject_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_primitive);
{
Label return_null(this), return_undefined(this);
GotoIf(IsStringInstanceType(receiver_instance_type), &if_string);
GotoIf(IsBooleanMap(receiver_map), &if_boolean);
GotoIf(IsHeapNumberMap(receiver_map), &if_number);
GotoIf(IsSymbolMap(receiver_map), &if_symbol);
Branch(IsUndefined(receiver), &return_undefined, &return_null);
BIND(&return_undefined);
Return(LoadRoot(Heap::kundefined_to_stringRootIndex));
BIND(&return_null);
Return(LoadRoot(Heap::knull_to_stringRootIndex));
}
BIND(&if_proxy);
{
// If {receiver} is a proxy for a JSArray, we default to "[object Array]",
// otherwise we default to "[object Object]" or "[object Function]" here,
// depending on whether the {receiver} is callable. The order matters here,
// i.e. we need to execute the %ArrayIsArray check before the [[Get]] below,
// as the exception is observable.
Node* receiver_is_array =
CallRuntime(Runtime::kArrayIsArray, context, receiver);
Node* builtin_tag = SelectTaggedConstant(
IsTrue(receiver_is_array), LoadRoot(Heap::kArray_stringRootIndex),
SelectTaggedConstant(IsCallableMap(receiver_map),
LoadRoot(Heap::kFunction_stringRootIndex),
LoadRoot(Heap::kObject_stringRootIndex)));
// Lookup the @@toStringTag property on the {receiver}.
VARIABLE(var_tag, MachineRepresentation::kTagged,
GetProperty(context, receiver,
isolate()->factory()->to_string_tag_symbol()));
Label if_tagisnotstring(this), if_tagisstring(this);
GotoIf(TaggedIsSmi(var_tag.value()), &if_tagisnotstring);
Branch(IsString(var_tag.value()), &if_tagisstring, &if_tagisnotstring);
BIND(&if_tagisnotstring);
{
var_tag.Bind(builtin_tag);
Goto(&if_tagisstring);
}
BIND(&if_tagisstring);
ReturnToStringFormat(context, var_tag.value());
}
BIND(&if_regexp);
{
var_default.Bind(LoadRoot(Heap::kregexp_to_stringRootIndex));
Goto(&checkstringtag);
}
BIND(&if_string);
{
Node* native_context = LoadNativeContext(context);
Node* string_constructor =
LoadContextElement(native_context, Context::STRING_FUNCTION_INDEX);
Node* string_initial_map = LoadObjectField(
string_constructor, JSFunction::kPrototypeOrInitialMapOffset);
Node* string_prototype =
LoadObjectField(string_initial_map, Map::kPrototypeOffset);
var_default.Bind(LoadRoot(Heap::kstring_to_stringRootIndex));
var_holder.Bind(string_prototype);
Goto(&checkstringtag);
}
BIND(&if_symbol);
{
Node* native_context = LoadNativeContext(context);
Node* symbol_constructor =
LoadContextElement(native_context, Context::SYMBOL_FUNCTION_INDEX);
Node* symbol_initial_map = LoadObjectField(
symbol_constructor, JSFunction::kPrototypeOrInitialMapOffset);
Node* symbol_prototype =
LoadObjectField(symbol_initial_map, Map::kPrototypeOffset);
var_default.Bind(LoadRoot(Heap::kobject_to_stringRootIndex));
var_holder.Bind(symbol_prototype);
Goto(&checkstringtag);
}
BIND(&if_value);
{
Node* receiver_value = LoadJSValueValue(receiver);
GotoIf(TaggedIsSmi(receiver_value), &if_number);
Node* receiver_value_map = LoadMap(receiver_value);
GotoIf(IsHeapNumberMap(receiver_value_map), &if_number);
GotoIf(IsBooleanMap(receiver_value_map), &if_boolean);
Branch(IsSymbolMap(receiver_value_map), &if_symbol, &if_string);
}
BIND(&checkstringtag);
{
Node* to_string_tag_symbol =
HeapConstant(isolate()->factory()->to_string_tag_symbol());
GetPropertyStub stub(isolate());
Callable get_property =
Callable(stub.GetCode(), stub.GetCallInterfaceDescriptor());
Node* to_string_tag_value =
CallStub(get_property, context, receiver, to_string_tag_symbol);
IsString(to_string_tag_value, &if_tostringtag, &if_notostringtag);
BIND(&if_tostringtag);
ReturnToStringFormat(context, to_string_tag_value);
}
BIND(&if_notostringtag);
{
size_t const kNumCases = 11;
Label* case_labels[kNumCases];
int32_t case_values[kNumCases];
case_labels[0] = &return_api;
case_values[0] = JS_API_OBJECT_TYPE;
case_labels[1] = &return_api;
case_values[1] = JS_SPECIAL_API_OBJECT_TYPE;
case_labels[2] = &return_arguments;
case_values[2] = JS_ARGUMENTS_TYPE;
case_labels[3] = &return_array;
case_values[3] = JS_ARRAY_TYPE;
case_labels[4] = &return_function;
case_values[4] = JS_BOUND_FUNCTION_TYPE;
case_labels[5] = &return_function;
case_values[5] = JS_FUNCTION_TYPE;
case_labels[6] = &return_error;
case_values[6] = JS_ERROR_TYPE;
case_labels[7] = &return_date;
case_values[7] = JS_DATE_TYPE;
case_labels[8] = &return_regexp;
case_values[8] = JS_REGEXP_TYPE;
case_labels[9] = &return_jsvalue;
case_values[9] = JS_VALUE_TYPE;
case_labels[10] = &return_jsproxy;
case_values[10] = JS_PROXY_TYPE;
Switch(receiver_instance_type, &return_object, case_values, case_labels,
arraysize(case_values));
BIND(&return_undefined);
Return(HeapConstant(isolate()->factory()->undefined_to_string()));
BIND(&return_null);
Return(HeapConstant(isolate()->factory()->null_to_string()));
BIND(&return_arguments);
Return(HeapConstant(isolate()->factory()->arguments_to_string()));
BIND(&return_array);
Return(HeapConstant(isolate()->factory()->array_to_string()));
BIND(&return_function);
Return(HeapConstant(isolate()->factory()->function_to_string()));
BIND(&return_error);
Return(HeapConstant(isolate()->factory()->error_to_string()));
BIND(&return_date);
Return(HeapConstant(isolate()->factory()->date_to_string()));
BIND(&return_regexp);
Return(HeapConstant(isolate()->factory()->regexp_to_string()));
BIND(&return_api);
// Check if all relevant maps (including the prototype maps) don't
// have any interesting symbols (i.e. that none of them have the
// @@toStringTag property).
Label loop(this, &var_holder), return_default(this),
return_generic(this, Label::kDeferred);
Goto(&loop);
BIND(&loop);
{
Node* class_name = CallRuntime(Runtime::kClassOf, context, receiver);
ReturnToStringFormat(context, class_name);
Node* holder = var_holder.value();
GotoIf(IsNull(holder), &return_default);
Node* holder_map = LoadMap(holder);
Node* holder_bit_field3 = LoadMapBitField3(holder_map);
GotoIf(IsSetWord32<Map::MayHaveInterestingSymbols>(holder_bit_field3),
&return_generic);
var_holder.Bind(LoadMapPrototype(holder_map));
Goto(&loop);
}
BIND(&return_jsvalue);
BIND(&return_generic);
{
Label return_boolean(this), return_number(this), return_string(this);
Node* value = LoadJSValueValue(receiver);
GotoIf(TaggedIsSmi(value), &return_number);
Node* instance_type = LoadInstanceType(value);
GotoIf(IsStringInstanceType(instance_type), &return_string);
GotoIf(Word32Equal(instance_type, Int32Constant(HEAP_NUMBER_TYPE)),
&return_number);
GotoIf(Word32Equal(instance_type, Int32Constant(ODDBALL_TYPE)),
&return_boolean);
CSA_ASSERT(this, Word32Equal(instance_type, Int32Constant(SYMBOL_TYPE)));
Goto(&return_object);
BIND(&return_string);
Return(HeapConstant(isolate()->factory()->string_to_string()));
BIND(&return_number);
Return(HeapConstant(isolate()->factory()->number_to_string()));
BIND(&return_boolean);
Return(HeapConstant(isolate()->factory()->boolean_to_string()));
Node* tag = GetProperty(
context, CallBuiltin(Builtins::kToObject, context, receiver),
LoadRoot(Heap::kto_string_tag_symbolRootIndex));
GotoIf(TaggedIsSmi(tag), &return_default);
GotoIfNot(IsString(tag), &return_default);
ReturnToStringFormat(context, tag);
}
BIND(&return_jsproxy);
{
GotoIf(WordEqual(var_proxy_is_array.value(), BooleanConstant(true)),
&return_array);
Node* map = LoadMap(receiver);
// Return object if the proxy {receiver} is not callable.
Branch(IsCallableMap(map), &return_function, &return_object);
}
// Default
BIND(&return_object);
Return(HeapConstant(isolate()->factory()->object_to_string()));
BIND(&return_default);
Return(var_default.value());
}
}

View File

@ -3646,6 +3646,11 @@ Node* CodeStubAssembler::IsSymbol(Node* object) {
return IsSymbolMap(LoadMap(object));
}
Node* CodeStubAssembler::IsPrimitiveInstanceType(Node* instance_type) {
return Int32LessThanOrEqual(instance_type,
Int32Constant(LAST_PRIMITIVE_TYPE));
}
Node* CodeStubAssembler::IsPrivateSymbol(Node* object) {
return Select(
IsSymbol(object),

View File

@ -860,6 +860,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsName(Node* object);
Node* IsNativeContext(Node* object);
Node* IsOneByteStringInstanceType(Node* instance_type);
Node* IsPrimitiveInstanceType(Node* instance_type);
Node* IsPrivateSymbol(Node* object);
Node* IsPropertyArray(Node* object);
Node* IsPropertyCell(Node* object);

View File

@ -478,7 +478,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kObjectHasOwnProperty:
case Builtins::kObjectPrototypeIsPrototypeOf:
case Builtins::kObjectPrototypePropertyIsEnumerable:
case Builtins::kObjectProtoToString:
case Builtins::kObjectPrototypeToString:
// Array builtins.
case Builtins::kArrayCode:
case Builtins::kArrayIndexOf:

View File

@ -1901,6 +1901,7 @@ Handle<JSGlobalObject> Factory::NewJSGlobalObject(
// Create a new map for the global object.
Handle<Map> new_map = Map::CopyDropDescriptors(map);
new_map->set_may_have_interesting_symbols(true);
new_map->set_dictionary_map(true);
// Set up the global object as a normalized object.
@ -2422,6 +2423,7 @@ Handle<JSGlobalProxy> Factory::NewUninitializedJSGlobalProxy(int size) {
Handle<Map> map = NewMap(JS_GLOBAL_PROXY_TYPE, size);
// Maintain invariant expected from any JSGlobalProxy.
map->set_is_access_check_needed(true);
map->set_may_have_interesting_symbols(true);
CALL_HEAP_FUNCTION(
isolate(), isolate()->heap()->AllocateJSObjectFromMap(*map, NOT_TENURED),
JSGlobalProxy);

View File

@ -2594,7 +2594,6 @@ bool Heap::CreateInitialMaps() {
ALLOCATE_VARSIZE_MAP(FIXED_ARRAY_TYPE, script_context_table)
ALLOCATE_VARSIZE_MAP(FIXED_ARRAY_TYPE, native_context)
native_context_map()->set_dictionary_map(true);
native_context_map()->set_visitor_id(kVisitNativeContext);
ALLOCATE_MAP(SHARED_FUNCTION_INFO_TYPE, SharedFunctionInfo::kAlignedSize,
@ -2922,6 +2921,9 @@ void Heap::CreateInitialObjects() {
roots_[k##name##RootIndex] = *name;
WELL_KNOWN_SYMBOL_LIST(SYMBOL_INIT)
#undef SYMBOL_INIT
// Mark "Interesting Symbols" appropriately.
to_string_tag_symbol->set_is_interesting_symbol(true);
}
Handle<NameDictionary> empty_property_dictionary =

View File

@ -438,6 +438,18 @@ void Map::MapVerify() {
SLOW_DCHECK(TransitionsAccessor(this, &no_gc).IsConsistentWithBackPointers());
SLOW_DCHECK(!FLAG_unbox_double_fields ||
layout_descriptor()->IsConsistentWithMap(this));
if (!may_have_interesting_symbols()) {
CHECK(!has_named_interceptor());
CHECK(!is_dictionary_map());
CHECK(!is_access_check_needed());
DescriptorArray* const descriptors = instance_descriptors();
for (int i = 0; i < NumberOfOwnDescriptors(); ++i) {
CHECK(!descriptors->GetKey(i)->IsInterestingSymbol());
}
}
CHECK_IMPLIES(has_named_interceptor(), may_have_interesting_symbols());
CHECK_IMPLIES(is_dictionary_map(), may_have_interesting_symbols());
CHECK_IMPLIES(is_access_check_needed(), may_have_interesting_symbols());
}

View File

@ -3500,6 +3500,14 @@ bool Map::new_target_is_base() const {
return NewTargetIsBase::decode(bit_field3());
}
void Map::set_may_have_interesting_symbols(bool value) {
set_bit_field3(MayHaveInterestingSymbols::update(bit_field3(), value));
}
bool Map::may_have_interesting_symbols() const {
return MayHaveInterestingSymbols::decode(bit_field3());
}
void Map::set_construction_counter(int value) {
set_bit_field3(ConstructionCounter::update(bit_field3(), value));
}
@ -4224,6 +4232,11 @@ void Map::AppendDescriptor(Descriptor* desc) {
descriptors->Append(desc);
SetNumberOfOwnDescriptors(number_of_own_descriptors + 1);
// Properly mark the map if the {desc} is an "interesting symbol".
if (desc->GetKey()->IsInterestingSymbol()) {
set_may_have_interesting_symbols(true);
}
// This function does not support appending double field descriptors and
// it should never try to (otherwise, layout descriptor must be updated too).
#ifdef DEBUG

View File

@ -607,6 +607,7 @@ void Map::MapPrint(std::ostream& os) { // NOLINT
if (has_hidden_prototype()) os << "\n - has_hidden_prototype";
if (has_named_interceptor()) os << "\n - named_interceptor";
if (has_indexed_interceptor()) os << "\n - indexed_interceptor";
if (may_have_interesting_symbols()) os << "\n - may_have_interesting_symbols";
if (is_undetectable()) os << "\n - undetectable";
if (is_callable()) os << "\n - callable";
if (is_constructor()) os << "\n - constructor";

View File

@ -6084,6 +6084,8 @@ void JSObject::MigrateSlowToFast(Handle<JSObject> object,
// Allocate new map.
Handle<Map> new_map = Map::CopyDropDescriptors(old_map);
new_map->set_may_have_interesting_symbols(new_map->has_named_interceptor() ||
new_map->is_access_check_needed());
new_map->set_dictionary_map(false);
NotifyMapChange(old_map, new_map, isolate);
@ -6134,6 +6136,11 @@ void JSObject::MigrateSlowToFast(Handle<JSObject> object,
CHECK(k->IsUniqueName());
Handle<Name> key(k, isolate);
// Properly mark the {new_map} if the {key} is an "interesting symbol".
if (key->IsInterestingSymbol()) {
new_map->set_may_have_interesting_symbols(true);
}
Object* value = dictionary->ValueAt(index);
PropertyDetails details = dictionary->DetailsAt(index);
@ -8879,6 +8886,7 @@ Handle<Map> Map::CopyNormalized(Handle<Map> map,
result->set_dictionary_map(true);
result->set_migration_target(false);
result->set_may_have_interesting_symbols(true);
result->set_construction_counter(kNoSlackTracking);
#ifdef VERIFY_HEAP
@ -8988,6 +8996,11 @@ Handle<Map> Map::ShareDescriptor(Handle<Map> map,
Handle<Map> result = CopyDropDescriptors(map);
Handle<Name> name = descriptor->GetKey();
// Properly mark the {result} if the {name} is an "interesting symbol".
if (name->IsInterestingSymbol()) {
result->set_may_have_interesting_symbols(true);
}
// Ensure there's space for the new descriptor in the shared descriptor array.
if (descriptors->NumberOfSlackDescriptors() == 0) {
int old_size = descriptors->number_of_descriptors();
@ -9085,13 +9098,18 @@ Handle<Map> Map::CopyReplaceDescriptors(
Handle<Map> result = CopyDropDescriptors(map);
// Properly mark the {result} if the {name} is an "interesting symbol".
Handle<Name> name;
if (maybe_name.ToHandle(&name) && name->IsInterestingSymbol()) {
result->set_may_have_interesting_symbols(true);
}
if (!map->is_prototype_map()) {
if (flag == INSERT_TRANSITION &&
TransitionsAccessor(map).CanHaveMoreTransitions()) {
result->InitializeDescriptors(*descriptors, *layout_descriptor);
Handle<Name> name;
CHECK(maybe_name.ToHandle(&name));
DCHECK(!maybe_name.is_null());
ConnectTransition(map, result, name, simple_flag);
} else {
descriptors->GeneralizeAllFields();
@ -9195,6 +9213,9 @@ void Map::InstallDescriptors(Handle<Map> parent, Handle<Map> child,
}
Handle<Name> name = handle(descriptors->GetKey(new_descriptor));
if (name->IsInterestingSymbol()) {
child->set_may_have_interesting_symbols(true);
}
ConnectTransition(parent, child, name, SIMPLE_PROPERTY_TRANSITION);
}

View File

@ -143,7 +143,7 @@ class Map : public HeapObject {
class IsMigrationTarget : public BitField<bool, 25, 1> {};
class ImmutablePrototype : public BitField<bool, 26, 1> {};
class NewTargetIsBase : public BitField<bool, 27, 1> {};
// Bit 28 is free.
class MayHaveInterestingSymbols : public BitField<bool, 28, 1> {};
// Keep this bit field at the very end for better code in
// Builtins::kJSConstructStubGeneric stub.
@ -219,6 +219,13 @@ class Map : public HeapObject {
inline void set_is_constructor(bool value);
inline bool is_constructor() const;
// Tells whether the instance with this map may have properties for
// interesting symbols on it.
// An "interesting symbol" is one for which Name::IsInterestingSymbol()
// returns true, i.e. a well-known symbol like @@toStringTag.
inline void set_may_have_interesting_symbols(bool value);
inline bool may_have_interesting_symbols() const;
// Tells whether the instance with this map has a hidden prototype.
inline void set_has_hidden_prototype(bool value);
inline bool has_hidden_prototype() const;

View File

@ -21,6 +21,7 @@ SMI_ACCESSORS(Symbol, flags, kFlagsOffset)
BOOL_ACCESSORS(Symbol, flags, is_private, kPrivateBit)
BOOL_ACCESSORS(Symbol, flags, is_well_known_symbol, kWellKnownSymbolBit)
BOOL_ACCESSORS(Symbol, flags, is_public, kPublicBit)
BOOL_ACCESSORS(Symbol, flags, is_interesting_symbol, kInterestingSymbolBit)
TYPE_CHECKER(Symbol, SYMBOL_TYPE)
@ -78,6 +79,10 @@ uint32_t Name::Hash() {
return String::cast(this)->ComputeAndSetHash();
}
bool Name::IsInterestingSymbol() const {
return IsSymbol() && Symbol::cast(this)->is_interesting_symbol();
}
bool Name::IsPrivate() {
return this->IsSymbol() && Symbol::cast(this)->is_private();
}

View File

@ -34,6 +34,13 @@ class Name : public HeapObject {
// Conversion.
inline bool AsArrayIndex(uint32_t* index);
// An "interesting symbol" is a well-known symbol, like @@toStringTag,
// that's often looked up on random objects but is usually not present.
// We optimize this by setting a flag on the object's map when such
// symbol properties are added, so we can optimize lookups on objects
// that don't have the flag.
inline bool IsInterestingSymbol() const;
// If the name is private, it can only name own properties.
inline bool IsPrivate();
@ -145,6 +152,12 @@ class Symbol : public Name {
// a load.
DECL_BOOLEAN_ACCESSORS(is_well_known_symbol)
// [is_interesting_symbol]: Whether this is an "interesting symbol", which
// is a well-known symbol like @@toStringTag that's often looked up on
// random objects but is usually not present. See Name::IsInterestingSymbol()
// for a detailed description.
DECL_BOOLEAN_ACCESSORS(is_interesting_symbol)
// [is_public]: Whether this is a symbol created by Symbol.for. Calling
// Symbol.keyFor on such a symbol simply needs to return the attached name.
DECL_BOOLEAN_ACCESSORS(is_public)
@ -164,6 +177,7 @@ class Symbol : public Name {
static const int kPrivateBit = 0;
static const int kWellKnownSymbolBit = 1;
static const int kPublicBit = 2;
static const int kInterestingSymbolBit = 3;
typedef FixedBodyDescriptor<kNameOffset, kFlagsOffset, kSize> BodyDescriptor;
// No weak fields.

View File

@ -864,6 +864,7 @@ TEST(TryHasOwnProperty) {
function->initial_map()->set_instance_type(JS_GLOBAL_OBJECT_TYPE);
function->initial_map()->set_is_prototype_map(true);
function->initial_map()->set_dictionary_map(true);
function->initial_map()->set_may_have_interesting_symbols(true);
Handle<JSObject> object = factory->NewJSGlobalObject(function);
AddProperties(object, names, arraysize(names));