[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:
parent
a4d914c904
commit
31800120cc
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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) \
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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 =
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user