Commit c8c75805 authored by bmeurer's avatar bmeurer Committed by Commit bot

[builtins] Migrate Object.keys to CodeStubAssembler builtin.

Migrate the Object.keys builtin to the CodeStubAssembler and
use the enum cache backing store whenever it is available. This
gives a nice speedup of 1.5x to 2x when using Object.keys on fast-mode
objects that have (or can have) an enum cache.

R=cbruni@chromium.org
BUG=v8:5269,v8:6405

Review-Url: https://codereview.chromium.org/2853393002
Cr-Commit-Position: refs/heads/master@{#45361}
parent 15805b2d
......@@ -1311,7 +1311,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
native_context()->set_object_is_sealed(*object_is_sealed);
Handle<JSFunction> object_keys = SimpleInstallFunction(
object_function, "keys", Builtins::kObjectKeys, 1, false);
object_function, "keys", Builtins::kObjectKeys, 1, true);
native_context()->set_object_keys(*object_keys);
SimpleInstallFunction(object_function, factory->entries_string(),
Builtins::kObjectEntries, 1, false);
......
......@@ -682,7 +682,7 @@ namespace internal {
CPP(ObjectIsExtensible) \
CPP(ObjectIsFrozen) \
CPP(ObjectIsSealed) \
CPP(ObjectKeys) \
TFJ(ObjectKeys, 1, kObject) \
CPP(ObjectLookupGetter) \
CPP(ObjectLookupSetter) \
CPP(ObjectPreventExtensions) \
......
......@@ -104,6 +104,88 @@ TF_BUILTIN(ObjectHasOwnProperty, ObjectBuiltinsAssembler) {
Return(CallRuntime(Runtime::kObjectHasOwnProperty, context, object, key));
}
// ES #sec-object.keys
TF_BUILTIN(ObjectKeys, ObjectBuiltinsAssembler) {
Node* object = Parameter(Descriptor::kObject);
Node* context = Parameter(Descriptor::kContext);
VARIABLE(var_length, MachineRepresentation::kTagged);
VARIABLE(var_elements, MachineRepresentation::kTagged);
Label if_empty(this, Label::kDeferred), if_fast(this),
if_slow(this, Label::kDeferred), if_join(this);
// Check if the {object} has a usable enum cache.
GotoIf(TaggedIsSmi(object), &if_slow);
Node* object_map = LoadMap(object);
Node* object_bit_field3 = LoadMapBitField3(object_map);
Node* object_enum_length =
DecodeWordFromWord32<Map::EnumLengthBits>(object_bit_field3);
GotoIf(
WordEqual(object_enum_length, IntPtrConstant(kInvalidEnumCacheSentinel)),
&if_slow);
// Ensure that the {object} doesn't have any elements.
CSA_ASSERT(this, IsJSObjectMap(object_map));
Node* object_elements = LoadObjectField(object, JSObject::kElementsOffset);
GotoIfNot(IsEmptyFixedArray(object_elements), &if_slow);
Branch(WordEqual(object_enum_length, IntPtrConstant(0)), &if_empty, &if_fast);
BIND(&if_fast);
{
// The {object} has a usable enum cache, use that.
Node* object_descriptors = LoadMapDescriptors(object_map);
Node* object_enum_cache_bridge = LoadObjectField(
object_descriptors, DescriptorArray::kEnumCacheBridgeOffset);
Node* object_enum_cache = LoadObjectField(
object_enum_cache_bridge, DescriptorArray::kEnumCacheBridgeCacheOffset);
// Allocate a JSArray and copy the elements from the {object_enum_cache}.
Node* array = nullptr;
Node* elements = nullptr;
Node* native_context = LoadNativeContext(context);
Node* array_map = LoadJSArrayElementsMap(FAST_ELEMENTS, native_context);
Node* array_length = SmiTag(object_enum_length);
std::tie(array, elements) = AllocateUninitializedJSArrayWithElements(
FAST_ELEMENTS, array_map, array_length, nullptr, object_enum_length,
INTPTR_PARAMETERS);
StoreMapNoWriteBarrier(elements, Heap::kFixedArrayMapRootIndex);
StoreObjectFieldNoWriteBarrier(elements, FixedArray::kLengthOffset,
array_length);
CopyFixedArrayElements(FAST_ELEMENTS, object_enum_cache, elements,
object_enum_length, SKIP_WRITE_BARRIER);
Return(array);
}
BIND(&if_empty);
{
// The {object} doesn't have any enumerable keys.
var_length.Bind(SmiConstant(0));
var_elements.Bind(EmptyFixedArrayConstant());
Goto(&if_join);
}
BIND(&if_slow);
{
// Let the runtime compute the elements.
Node* elements = CallRuntime(Runtime::kObjectKeys, context, object);
var_length.Bind(LoadObjectField(elements, FixedArray::kLengthOffset));
var_elements.Bind(elements);
Goto(&if_join);
}
BIND(&if_join);
{
// Wrap the elements into a proper JSArray and return that.
Node* native_context = LoadNativeContext(context);
Node* array_map = LoadJSArrayElementsMap(FAST_ELEMENTS, native_context);
Node* array = AllocateUninitializedJSArrayWithoutElements(
FAST_ELEMENTS, array_map, var_length.value(), nullptr);
StoreObjectFieldNoWriteBarrier(array, JSArray::kElementsOffset,
var_elements.value());
Return(array);
}
}
// ES6 #sec-object.prototype.tostring
TF_BUILTIN(ObjectProtoToString, ObjectBuiltinsAssembler) {
Label return_undefined(this, Label::kDeferred),
......
......@@ -437,41 +437,6 @@ BUILTIN(ObjectIsSealed) {
return isolate->heap()->ToBoolean(result.FromJust());
}
// ES6 section 19.1.2.14 Object.keys ( O )
BUILTIN(ObjectKeys) {
HandleScope scope(isolate);
Handle<Object> object = args.atOrUndefined(isolate, 1);
Handle<JSReceiver> receiver;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver,
Object::ToObject(isolate, object));
Handle<FixedArray> keys;
int enum_length = receiver->map()->EnumLength();
if (enum_length != kInvalidEnumCacheSentinel &&
JSObject::cast(*receiver)->elements() ==
isolate->heap()->empty_fixed_array()) {
DCHECK(receiver->IsJSObject());
DCHECK(!JSObject::cast(*receiver)->HasNamedInterceptor());
DCHECK(!JSObject::cast(*receiver)->IsAccessCheckNeeded());
DCHECK(!receiver->map()->has_hidden_prototype());
DCHECK(JSObject::cast(*receiver)->HasFastProperties());
if (enum_length == 0) {
keys = isolate->factory()->empty_fixed_array();
} else {
Handle<FixedArray> cache(
receiver->map()->instance_descriptors()->GetEnumCache());
keys = isolate->factory()->CopyFixedArrayUpTo(cache, enum_length);
}
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, keys,
KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly,
ENUMERABLE_STRINGS,
GetKeysConversion::kConvertToString));
}
return *isolate->factory()->NewJSArrayWithElements(keys, FAST_ELEMENTS);
}
BUILTIN(ObjectValues) {
HandleScope scope(isolate);
Handle<Object> object = args.atOrUndefined(isolate, 1);
......
......@@ -3176,20 +3176,23 @@ Node* CodeStubAssembler::IsJSReceiverInstanceType(Node* instance_type) {
Int32Constant(FIRST_JS_RECEIVER_TYPE));
}
Node* CodeStubAssembler::IsJSReceiverMap(Node* map) {
return IsJSReceiverInstanceType(LoadMapInstanceType(map));
}
Node* CodeStubAssembler::IsJSReceiver(Node* object) {
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
return IsJSReceiverInstanceType(LoadInstanceType(object));
return IsJSReceiverMap(LoadMap(object));
}
Node* CodeStubAssembler::IsJSReceiverMap(Node* map) {
Node* CodeStubAssembler::IsJSObjectMap(Node* map) {
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
return IsJSReceiverInstanceType(LoadMapInstanceType(map));
CSA_ASSERT(this, IsMap(map));
return Int32GreaterThanOrEqual(LoadMapInstanceType(map),
Int32Constant(FIRST_JS_OBJECT_TYPE));
}
Node* CodeStubAssembler::IsJSObject(Node* object) {
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
return Int32GreaterThanOrEqual(LoadInstanceType(object),
Int32Constant(FIRST_JS_RECEIVER_TYPE));
return IsJSObjectMap(LoadMap(object));
}
Node* CodeStubAssembler::IsJSGlobalProxy(Node* object) {
......
......@@ -743,6 +743,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsConsStringInstanceType(Node* instance_type);
Node* IsIndirectStringInstanceType(Node* instance_type);
Node* IsString(Node* object);
Node* IsJSObjectMap(Node* map);
Node* IsJSObject(Node* object);
Node* IsJSGlobalProxy(Node* object);
Node* IsJSReceiverInstanceType(Node* instance_type);
......
......@@ -198,6 +198,26 @@ Maybe<bool> Runtime::DeleteObjectProperty(Isolate* isolate,
return JSReceiver::DeleteProperty(&it, language_mode);
}
// ES #sec-object.keys
RUNTIME_FUNCTION(Runtime_ObjectKeys) {
HandleScope scope(isolate);
Handle<Object> object = args.at(0);
// Convert the {object} to a proper {receiver}.
Handle<JSReceiver> receiver;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver,
Object::ToObject(isolate, object));
// Collect the own keys for the {receiver}.
Handle<FixedArray> keys;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, keys,
KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly,
ENUMERABLE_STRINGS,
GetKeysConversion::kConvertToString));
return *keys;
}
// ES6 19.1.3.2
RUNTIME_FUNCTION(Runtime_ObjectHasOwnProperty) {
HandleScope scope(isolate);
......
......@@ -391,6 +391,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_OBJECT(F) \
F(AddDictionaryProperty, 3, 1) \
F(GetPrototype, 1, 1) \
F(ObjectKeys, 1, 1) \
F(ObjectHasOwnProperty, 2, 1) \
F(ObjectCreate, 2, 1) \
F(InternalSetPrototype, 2, 1) \
......
// 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: --allow-natives-syntax
// Ensure that mutation of the Object.keys result doesn't affect the
// enumeration cache for fast-mode objects.
(function() {
const a = {x:1, y:2};
let k = Object.keys(a);
%HeapObjectVerify(k);
assertEquals(2, k.length);
assertEquals("x", k[0]);
assertEquals("y", k[1]);
k[0] = "y";
k[1] = "x";
k = Object.keys(a);
assertEquals(2, k.length);
assertEquals("x", k[0]);
assertEquals("y", k[1]);
})();
// Ensure that the copy-on-write keys are handled properly, even in
// the presence of Symbols.
(function() {
const s = Symbol();
const a = {[s]: 1};
let k = Object.keys(a);
%HeapObjectVerify(k);
assertEquals(0, k.length);
k.shift();
assertEquals(0, k.length);
})();
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