Reland: Reimplement Object.entries/values as CSA to optimize performance.
Original CL is https://chromium-review.googlesource.com/c/v8/v8/+/810504
Reverted issue is https://bugs.chromium.org/p/chromium/issues/detail?id=804159
Fix Object.entries descriptor array value index.
This reverts commit e5ecb24859
.
Bug: v8:6804, chromium:804159
Change-Id: I73a5a5f670c5b36e0c5cc7984d5979ecec43d969
Reviewed-on: https://chromium-review.googlesource.com/892684
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51170}
This commit is contained in:
parent
49bce85225
commit
4455377fca
1
AUTHORS
1
AUTHORS
@ -136,6 +136,7 @@ Sanjoy Das <sanjoy@playingwithpointers.com>
|
||||
Seo Sanghyeon <sanxiyn@gmail.com>
|
||||
Stefan Penner <stefan.penner@gmail.com>
|
||||
Sylvestre Ledru <sledru@mozilla.com>
|
||||
Taketoshi Aono <brn@b6n.ch>
|
||||
Tiancheng "Timothy" Gu <timothygu99@gmail.com>
|
||||
Tobias Burnus <burnus@net-b.de>
|
||||
Victor Costan <costan@gmail.com>
|
||||
|
@ -1508,9 +1508,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
object_function, "keys", Builtins::kObjectKeys, 1, true);
|
||||
native_context()->set_object_keys(*object_keys);
|
||||
SimpleInstallFunction(object_function, factory->entries_string(),
|
||||
Builtins::kObjectEntries, 1, false);
|
||||
Builtins::kObjectEntries, 1, true);
|
||||
SimpleInstallFunction(object_function, factory->values_string(),
|
||||
Builtins::kObjectValues, 1, false);
|
||||
Builtins::kObjectValues, 1, true);
|
||||
|
||||
SimpleInstallFunction(isolate->initial_object_prototype(),
|
||||
"__defineGetter__", Builtins::kObjectDefineGetter, 2,
|
||||
|
@ -758,7 +758,7 @@ namespace internal {
|
||||
CPP(ObjectDefineProperties) \
|
||||
CPP(ObjectDefineProperty) \
|
||||
CPP(ObjectDefineSetter) \
|
||||
CPP(ObjectEntries) \
|
||||
TFJ(ObjectEntries, 1, kObject) \
|
||||
CPP(ObjectFreeze) \
|
||||
TFJ(ObjectGetOwnPropertyDescriptor, \
|
||||
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
||||
@ -788,7 +788,7 @@ namespace internal {
|
||||
/* ES #sec-object.prototype.tolocalestring */ \
|
||||
TFJ(ObjectPrototypeToLocaleString, 0) \
|
||||
CPP(ObjectSeal) \
|
||||
CPP(ObjectValues) \
|
||||
TFJ(ObjectValues, 1, kObject) \
|
||||
\
|
||||
/* instanceof */ \
|
||||
TFC(OrdinaryHasInstance, Compare, 1) \
|
||||
|
@ -16,6 +16,8 @@ namespace internal {
|
||||
// ES6 section 19.1 Object Objects
|
||||
|
||||
typedef compiler::Node Node;
|
||||
template <class T>
|
||||
using TNode = CodeStubAssembler::TNode<T>;
|
||||
|
||||
class ObjectBuiltinsAssembler : public CodeStubAssembler {
|
||||
public:
|
||||
@ -34,6 +36,46 @@ class ObjectBuiltinsAssembler : public CodeStubAssembler {
|
||||
Node* ConstructDataDescriptor(Node* context, Node* value, Node* writable,
|
||||
Node* enumerable, Node* configurable);
|
||||
Node* GetAccessorOrUndefined(Node* accessor, Label* if_bailout);
|
||||
|
||||
Node* IsSpecialReceiverMap(SloppyTNode<Map> map);
|
||||
};
|
||||
|
||||
class ObjectEntriesValuesBuiltinsAssembler : public ObjectBuiltinsAssembler {
|
||||
public:
|
||||
explicit ObjectEntriesValuesBuiltinsAssembler(
|
||||
compiler::CodeAssemblerState* state)
|
||||
: ObjectBuiltinsAssembler(state) {}
|
||||
|
||||
protected:
|
||||
enum CollectType { kEntries, kValues };
|
||||
|
||||
TNode<Word32T> IsStringWrapperElementsKind(TNode<Map> map);
|
||||
|
||||
TNode<BoolT> IsPropertyEnumerable(TNode<Uint32T> details);
|
||||
|
||||
TNode<BoolT> IsPropertyKindAccessor(TNode<Uint32T> kind);
|
||||
|
||||
TNode<BoolT> IsPropertyKindData(TNode<Uint32T> kind);
|
||||
|
||||
TNode<Uint32T> HasHiddenPrototype(TNode<Map> map);
|
||||
|
||||
TNode<Uint32T> LoadPropertyKind(TNode<Uint32T> details) {
|
||||
return DecodeWord32<PropertyDetails::KindField>(details);
|
||||
}
|
||||
|
||||
void GetOwnValuesOrEntries(TNode<Context> context, TNode<Object> maybe_object,
|
||||
CollectType collect_type);
|
||||
|
||||
void GotoIfMapHasSlowProperties(TNode<Map> map, Label* if_slow);
|
||||
|
||||
TNode<JSArray> FastGetOwnValuesOrEntries(
|
||||
TNode<Context> context, TNode<JSObject> object,
|
||||
Label* if_call_runtime_with_fast_path, Label* if_no_properties,
|
||||
CollectType collect_type);
|
||||
|
||||
TNode<JSArray> FinalizeValuesOrEntriesJSArray(
|
||||
TNode<Context> context, TNode<FixedArray> values_or_entries,
|
||||
TNode<IntPtrT> size, TNode<Map> array_map, Label* if_empty);
|
||||
};
|
||||
|
||||
void ObjectBuiltinsAssembler::ReturnToStringFormat(Node* context,
|
||||
@ -97,6 +139,250 @@ Node* ObjectBuiltinsAssembler::ConstructDataDescriptor(Node* context,
|
||||
return js_desc;
|
||||
}
|
||||
|
||||
Node* ObjectBuiltinsAssembler::IsSpecialReceiverMap(SloppyTNode<Map> map) {
|
||||
CSA_SLOW_ASSERT(this, IsMap(map));
|
||||
Node* is_special = IsSpecialReceiverInstanceType(LoadMapInstanceType(map));
|
||||
uint32_t mask =
|
||||
Map::HasNamedInterceptorBit::kMask | Map::IsAccessCheckNeededBit::kMask;
|
||||
USE(mask);
|
||||
// Interceptors or access checks imply special receiver.
|
||||
CSA_ASSERT(this,
|
||||
SelectConstant(IsSetWord32(LoadMapBitField(map), mask), is_special,
|
||||
Int32Constant(1), MachineRepresentation::kWord32));
|
||||
return is_special;
|
||||
}
|
||||
|
||||
TNode<Word32T>
|
||||
ObjectEntriesValuesBuiltinsAssembler::IsStringWrapperElementsKind(
|
||||
TNode<Map> map) {
|
||||
Node* kind = LoadMapElementsKind(map);
|
||||
return Word32Or(
|
||||
Word32Equal(kind, Int32Constant(FAST_STRING_WRAPPER_ELEMENTS)),
|
||||
Word32Equal(kind, Int32Constant(SLOW_STRING_WRAPPER_ELEMENTS)));
|
||||
}
|
||||
|
||||
TNode<BoolT> ObjectEntriesValuesBuiltinsAssembler::IsPropertyEnumerable(
|
||||
TNode<Uint32T> details) {
|
||||
TNode<Uint32T> attributes =
|
||||
DecodeWord32<PropertyDetails::AttributesField>(details);
|
||||
return IsNotSetWord32(attributes, PropertyAttributes::DONT_ENUM);
|
||||
}
|
||||
|
||||
TNode<BoolT> ObjectEntriesValuesBuiltinsAssembler::IsPropertyKindAccessor(
|
||||
TNode<Uint32T> kind) {
|
||||
return Word32Equal(kind, Int32Constant(PropertyKind::kAccessor));
|
||||
}
|
||||
|
||||
TNode<BoolT> ObjectEntriesValuesBuiltinsAssembler::IsPropertyKindData(
|
||||
TNode<Uint32T> kind) {
|
||||
return Word32Equal(kind, Int32Constant(PropertyKind::kData));
|
||||
}
|
||||
|
||||
TNode<Uint32T> ObjectEntriesValuesBuiltinsAssembler::HasHiddenPrototype(
|
||||
TNode<Map> map) {
|
||||
TNode<Uint32T> bit_field3 = LoadMapBitField3(map);
|
||||
return DecodeWord32<Map::HasHiddenPrototypeBit>(bit_field3);
|
||||
}
|
||||
|
||||
void ObjectEntriesValuesBuiltinsAssembler::GetOwnValuesOrEntries(
|
||||
TNode<Context> context, TNode<Object> maybe_object,
|
||||
CollectType collect_type) {
|
||||
TNode<JSObject> object = TNode<JSObject>::UncheckedCast(
|
||||
CallBuiltin(Builtins::kToObject, context, maybe_object));
|
||||
|
||||
Label if_call_runtime_with_fast_path(this, Label::kDeferred),
|
||||
if_call_runtime(this, Label::kDeferred),
|
||||
if_no_properties(this, Label::kDeferred);
|
||||
|
||||
TNode<Map> map = LoadMap(object);
|
||||
GotoIfNot(IsJSObjectMap(map), &if_call_runtime);
|
||||
GotoIfMapHasSlowProperties(map, &if_call_runtime);
|
||||
|
||||
TNode<FixedArrayBase> elements = LoadElements(object);
|
||||
// If the object has elements, we treat it as slow case.
|
||||
// So, we go to runtime call.
|
||||
GotoIfNot(IsEmptyFixedArray(elements), &if_call_runtime_with_fast_path);
|
||||
|
||||
TNode<JSArray> result = FastGetOwnValuesOrEntries(
|
||||
context, object, &if_call_runtime_with_fast_path, &if_no_properties,
|
||||
collect_type);
|
||||
Return(result);
|
||||
|
||||
BIND(&if_no_properties);
|
||||
{
|
||||
Node* native_context = LoadNativeContext(context);
|
||||
Node* array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
|
||||
Node* empty_array = AllocateJSArray(PACKED_ELEMENTS, array_map,
|
||||
IntPtrConstant(0), SmiConstant(0));
|
||||
Return(empty_array);
|
||||
}
|
||||
|
||||
BIND(&if_call_runtime_with_fast_path);
|
||||
{
|
||||
// In slow case, we simply call runtime.
|
||||
if (collect_type == CollectType::kEntries) {
|
||||
Return(CallRuntime(Runtime::kObjectEntries, context, object));
|
||||
} else {
|
||||
DCHECK(collect_type == CollectType::kValues);
|
||||
Return(CallRuntime(Runtime::kObjectValues, context, object));
|
||||
}
|
||||
}
|
||||
|
||||
BIND(&if_call_runtime);
|
||||
{
|
||||
// In slow case, we simply call runtime.
|
||||
if (collect_type == CollectType::kEntries) {
|
||||
Return(CallRuntime(Runtime::kObjectEntriesSkipFastPath, context, object));
|
||||
} else {
|
||||
DCHECK(collect_type == CollectType::kValues);
|
||||
Return(CallRuntime(Runtime::kObjectValuesSkipFastPath, context, object));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectEntriesValuesBuiltinsAssembler::GotoIfMapHasSlowProperties(
|
||||
TNode<Map> map, Label* if_slow) {
|
||||
GotoIf(IsStringWrapperElementsKind(map), if_slow);
|
||||
GotoIf(IsSpecialReceiverMap(map), if_slow);
|
||||
GotoIf(HasHiddenPrototype(map), if_slow);
|
||||
GotoIf(IsDictionaryMap(map), if_slow);
|
||||
}
|
||||
|
||||
TNode<JSArray> ObjectEntriesValuesBuiltinsAssembler::FastGetOwnValuesOrEntries(
|
||||
TNode<Context> context, TNode<JSObject> object,
|
||||
Label* if_call_runtime_with_fast_path, Label* if_no_properties,
|
||||
CollectType collect_type) {
|
||||
Node* native_context = LoadNativeContext(context);
|
||||
TNode<Map> array_map =
|
||||
LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
|
||||
TNode<Map> map = LoadMap(object);
|
||||
TNode<Uint32T> bit_field3 = LoadMapBitField3(map);
|
||||
|
||||
Label if_has_enum_cache(this), if_not_has_enum_cache(this),
|
||||
collect_entries(this);
|
||||
Node* object_enum_length =
|
||||
DecodeWordFromWord32<Map::EnumLengthBits>(bit_field3);
|
||||
Node* has_enum_cache = WordNotEqual(
|
||||
object_enum_length, IntPtrConstant(kInvalidEnumCacheSentinel));
|
||||
|
||||
// In case, we found enum_cache in object,
|
||||
// we use it as array_length becuase it has same size for
|
||||
// Object.(entries/values) result array object length.
|
||||
// So object_enum_length use less memory space than
|
||||
// NumberOfOwnDescriptorsBits value.
|
||||
// And in case, if enum_cache_not_found,
|
||||
// we call runtime and initialize enum_cache for subsequent call of
|
||||
// CSA fast path.
|
||||
Branch(has_enum_cache, &if_has_enum_cache, if_call_runtime_with_fast_path);
|
||||
|
||||
BIND(&if_has_enum_cache);
|
||||
{
|
||||
GotoIf(WordEqual(object_enum_length, IntPtrConstant(0)), if_no_properties);
|
||||
TNode<FixedArray> values_or_entries = TNode<FixedArray>::UncheckedCast(
|
||||
AllocateFixedArray(PACKED_ELEMENTS, object_enum_length,
|
||||
INTPTR_PARAMETERS, kAllowLargeObjectAllocation));
|
||||
|
||||
// If in case we have enum_cache,
|
||||
// we can't detect accessor of object until loop through descritpros.
|
||||
// So if object might have accessor,
|
||||
// we will remain invalid addresses of FixedArray.
|
||||
// Because in that case, we need to jump to runtime call.
|
||||
// So the array filled by the-hole even if enum_cache exists.
|
||||
FillFixedArrayWithValue(PACKED_ELEMENTS, values_or_entries,
|
||||
IntPtrConstant(0), object_enum_length,
|
||||
Heap::kTheHoleValueRootIndex);
|
||||
|
||||
TVARIABLE(IntPtrT, var_result_index, IntPtrConstant(0));
|
||||
TVARIABLE(IntPtrT, var_descriptor_number, IntPtrConstant(0));
|
||||
Variable* vars[] = {&var_descriptor_number, &var_result_index};
|
||||
// Let desc be ? O.[[GetOwnProperty]](key).
|
||||
TNode<DescriptorArray> descriptors = LoadMapDescriptors(map);
|
||||
Label loop(this, 2, vars), after_loop(this), loop_condition(this);
|
||||
Branch(IntPtrEqual(var_descriptor_number, object_enum_length), &after_loop,
|
||||
&loop);
|
||||
|
||||
// We dont use BuildFastLoop.
|
||||
// Instead, we use hand-written loop
|
||||
// because of we need to use 'continue' functionality.
|
||||
BIND(&loop);
|
||||
{
|
||||
// Currently, we will not invoke getters,
|
||||
// so, map will not be changed.
|
||||
CSA_ASSERT(this, WordEqual(map, LoadMap(object)));
|
||||
TNode<Uint32T> descriptor_index = TNode<Uint32T>::UncheckedCast(
|
||||
TruncateWordToWord32(var_descriptor_number));
|
||||
Node* next_key = DescriptorArrayGetKey(descriptors, descriptor_index);
|
||||
|
||||
// Skip Symbols.
|
||||
GotoIf(IsSymbol(next_key), &loop_condition);
|
||||
|
||||
TNode<Uint32T> details = TNode<Uint32T>::UncheckedCast(
|
||||
DescriptorArrayGetDetails(descriptors, descriptor_index));
|
||||
TNode<Uint32T> kind = LoadPropertyKind(details);
|
||||
|
||||
// If property is accessor, we escape fast path and call runtime.
|
||||
GotoIf(IsPropertyKindAccessor(kind), if_call_runtime_with_fast_path);
|
||||
CSA_ASSERT(this, IsPropertyKindData(kind));
|
||||
|
||||
// If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
GotoIfNot(IsPropertyEnumerable(details), &loop_condition);
|
||||
|
||||
VARIABLE(var_property_value, MachineRepresentation::kTagged,
|
||||
UndefinedConstant());
|
||||
Node* descriptor_name_index = DescriptorArrayToKeyIndex(
|
||||
TruncateWordToWord32(var_descriptor_number));
|
||||
|
||||
// Let value be ? Get(O, key).
|
||||
LoadPropertyFromFastObject(object, map, descriptors,
|
||||
descriptor_name_index, details,
|
||||
&var_property_value);
|
||||
|
||||
// If kind is "value", append value to properties.
|
||||
Node* value = var_property_value.value();
|
||||
|
||||
if (collect_type == CollectType::kEntries) {
|
||||
// Let entry be CreateArrayFromList(« key, value »).
|
||||
Node* array = nullptr;
|
||||
Node* elements = nullptr;
|
||||
std::tie(array, elements) = AllocateUninitializedJSArrayWithElements(
|
||||
PACKED_ELEMENTS, array_map, SmiConstant(2), nullptr,
|
||||
IntPtrConstant(2));
|
||||
StoreFixedArrayElement(elements, 0, next_key, SKIP_WRITE_BARRIER);
|
||||
StoreFixedArrayElement(elements, 1, value, SKIP_WRITE_BARRIER);
|
||||
value = array;
|
||||
}
|
||||
|
||||
StoreFixedArrayElement(values_or_entries, var_result_index, value);
|
||||
Increment(&var_result_index, 1);
|
||||
Goto(&loop_condition);
|
||||
|
||||
BIND(&loop_condition);
|
||||
{
|
||||
Increment(&var_descriptor_number, 1);
|
||||
Branch(IntPtrEqual(var_descriptor_number, object_enum_length),
|
||||
&after_loop, &loop);
|
||||
}
|
||||
}
|
||||
BIND(&after_loop);
|
||||
return FinalizeValuesOrEntriesJSArray(context, values_or_entries,
|
||||
var_result_index, array_map,
|
||||
if_no_properties);
|
||||
}
|
||||
}
|
||||
|
||||
TNode<JSArray>
|
||||
ObjectEntriesValuesBuiltinsAssembler::FinalizeValuesOrEntriesJSArray(
|
||||
TNode<Context> context, TNode<FixedArray> result, TNode<IntPtrT> size,
|
||||
TNode<Map> array_map, Label* if_empty) {
|
||||
CSA_ASSERT(this, IsJSArrayMap(array_map));
|
||||
|
||||
GotoIf(IntPtrEqual(size, IntPtrConstant(0)), if_empty);
|
||||
Node* array = AllocateUninitializedJSArrayWithoutElements(
|
||||
array_map, SmiTag(size), nullptr);
|
||||
StoreObjectField(array, JSArray::kElementsOffset, result);
|
||||
return TNode<JSArray>::UncheckedCast(array);
|
||||
}
|
||||
|
||||
TF_BUILTIN(ObjectPrototypeToLocaleString, CodeStubAssembler) {
|
||||
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||
TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
|
||||
@ -266,6 +552,22 @@ TF_BUILTIN(ObjectKeys, ObjectBuiltinsAssembler) {
|
||||
}
|
||||
}
|
||||
|
||||
TF_BUILTIN(ObjectValues, ObjectEntriesValuesBuiltinsAssembler) {
|
||||
TNode<JSObject> object =
|
||||
TNode<JSObject>::UncheckedCast(Parameter(Descriptor::kObject));
|
||||
TNode<Context> context =
|
||||
TNode<Context>::UncheckedCast(Parameter(Descriptor::kContext));
|
||||
GetOwnValuesOrEntries(context, object, CollectType::kValues);
|
||||
}
|
||||
|
||||
TF_BUILTIN(ObjectEntries, ObjectEntriesValuesBuiltinsAssembler) {
|
||||
TNode<JSObject> object =
|
||||
TNode<JSObject>::UncheckedCast(Parameter(Descriptor::kObject));
|
||||
TNode<Context> context =
|
||||
TNode<Context>::UncheckedCast(Parameter(Descriptor::kContext));
|
||||
GetOwnValuesOrEntries(context, object, CollectType::kEntries);
|
||||
}
|
||||
|
||||
// ES #sec-object.prototype.isprototypeof
|
||||
TF_BUILTIN(ObjectPrototypeIsPrototypeOf, ObjectBuiltinsAssembler) {
|
||||
Node* receiver = Parameter(Descriptor::kReceiver);
|
||||
|
@ -395,31 +395,6 @@ BUILTIN(ObjectIsSealed) {
|
||||
return isolate->heap()->ToBoolean(result.FromJust());
|
||||
}
|
||||
|
||||
BUILTIN(ObjectValues) {
|
||||
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> values;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, values, JSReceiver::GetOwnValues(receiver, ENUMERABLE_STRINGS));
|
||||
return *isolate->factory()->NewJSArrayWithElements(values);
|
||||
}
|
||||
|
||||
BUILTIN(ObjectEntries) {
|
||||
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> entries;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, entries,
|
||||
JSReceiver::GetOwnEntries(receiver, ENUMERABLE_STRINGS));
|
||||
return *isolate->factory()->NewJSArrayWithElements(entries);
|
||||
}
|
||||
|
||||
BUILTIN(ObjectGetOwnPropertyDescriptors) {
|
||||
HandleScope scope(isolate);
|
||||
Handle<Object> object = args.atOrUndefined(isolate, 1);
|
||||
|
@ -4111,19 +4111,6 @@ Node* CodeStubAssembler::InstanceTypeEqual(Node* instance_type, int type) {
|
||||
return Word32Equal(instance_type, Int32Constant(type));
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::IsSpecialReceiverMap(Node* map) {
|
||||
CSA_SLOW_ASSERT(this, IsMap(map));
|
||||
Node* is_special = IsSpecialReceiverInstanceType(LoadMapInstanceType(map));
|
||||
uint32_t mask =
|
||||
Map::HasNamedInterceptorBit::kMask | Map::IsAccessCheckNeededBit::kMask;
|
||||
USE(mask);
|
||||
// Interceptors or access checks imply special receiver.
|
||||
CSA_ASSERT(this,
|
||||
SelectConstant(IsSetWord32(LoadMapBitField(map), mask), is_special,
|
||||
Int32Constant(1), MachineRepresentation::kWord32));
|
||||
return is_special;
|
||||
}
|
||||
|
||||
TNode<BoolT> CodeStubAssembler::IsDictionaryMap(SloppyTNode<Map> map) {
|
||||
CSA_SLOW_ASSERT(this, IsMap(map));
|
||||
Node* bit_field3 = LoadMapBitField3(map);
|
||||
@ -6515,36 +6502,38 @@ Node* CodeStubAssembler::DescriptorArrayNumberOfEntries(Node* descriptors) {
|
||||
descriptors, IntPtrConstant(DescriptorArray::kDescriptorLengthIndex));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Node* DescriptorNumberToIndex(CodeStubAssembler* a, Node* descriptor_number) {
|
||||
Node* descriptor_size = a->Int32Constant(DescriptorArray::kEntrySize);
|
||||
Node* index = a->Int32Mul(descriptor_number, descriptor_size);
|
||||
return a->ChangeInt32ToIntPtr(index);
|
||||
Node* CodeStubAssembler::DescriptorNumberToIndex(
|
||||
SloppyTNode<Uint32T> descriptor_number) {
|
||||
Node* descriptor_size = Int32Constant(DescriptorArray::kEntrySize);
|
||||
Node* index = Int32Mul(descriptor_number, descriptor_size);
|
||||
return ChangeInt32ToIntPtr(index);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Node* CodeStubAssembler::DescriptorArrayToKeyIndex(Node* descriptor_number) {
|
||||
return IntPtrAdd(IntPtrConstant(DescriptorArray::ToKeyIndex(0)),
|
||||
DescriptorNumberToIndex(this, descriptor_number));
|
||||
DescriptorNumberToIndex(descriptor_number));
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::DescriptorArrayGetSortedKeyIndex(
|
||||
Node* descriptors, Node* descriptor_number) {
|
||||
const int details_offset = DescriptorArray::ToDetailsIndex(0) * kPointerSize;
|
||||
Node* details = LoadAndUntagToWord32FixedArrayElement(
|
||||
descriptors, DescriptorNumberToIndex(this, descriptor_number),
|
||||
details_offset);
|
||||
Node* details = DescriptorArrayGetDetails(
|
||||
TNode<DescriptorArray>::UncheckedCast(descriptors),
|
||||
TNode<Uint32T>::UncheckedCast(descriptor_number));
|
||||
return DecodeWord32<PropertyDetails::DescriptorPointer>(details);
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::DescriptorArrayGetKey(Node* descriptors,
|
||||
Node* descriptor_number) {
|
||||
const int key_offset = DescriptorArray::ToKeyIndex(0) * kPointerSize;
|
||||
return LoadFixedArrayElement(descriptors,
|
||||
DescriptorNumberToIndex(this, descriptor_number),
|
||||
key_offset);
|
||||
return LoadFixedArrayElement(
|
||||
descriptors, DescriptorNumberToIndex(descriptor_number), key_offset);
|
||||
}
|
||||
|
||||
TNode<Uint32T> CodeStubAssembler::DescriptorArrayGetDetails(
|
||||
TNode<DescriptorArray> descriptors, TNode<Uint32T> descriptor_number) {
|
||||
const int details_offset = DescriptorArray::ToDetailsIndex(0) * kPointerSize;
|
||||
return TNode<Uint32T>::UncheckedCast(LoadAndUntagToWord32FixedArrayElement(
|
||||
descriptors, DescriptorNumberToIndex(descriptor_number), details_offset));
|
||||
}
|
||||
|
||||
void CodeStubAssembler::DescriptorLookupBinary(Node* unique_name,
|
||||
@ -6743,12 +6732,22 @@ void CodeStubAssembler::LoadPropertyFromFastObject(Node* object, Node* map,
|
||||
Variable* var_value) {
|
||||
DCHECK_EQ(MachineRepresentation::kWord32, var_details->rep());
|
||||
DCHECK_EQ(MachineRepresentation::kTagged, var_value->rep());
|
||||
Comment("[ LoadPropertyFromFastObject");
|
||||
|
||||
Node* details =
|
||||
LoadDetailsByKeyIndex<DescriptorArray>(descriptors, name_index);
|
||||
var_details->Bind(details);
|
||||
|
||||
LoadPropertyFromFastObject(object, map, descriptors, name_index, details,
|
||||
var_value);
|
||||
}
|
||||
|
||||
void CodeStubAssembler::LoadPropertyFromFastObject(Node* object, Node* map,
|
||||
Node* descriptors,
|
||||
Node* name_index,
|
||||
Node* details,
|
||||
Variable* var_value) {
|
||||
Comment("[ LoadPropertyFromFastObject");
|
||||
|
||||
Node* location = DecodeWord32<PropertyDetails::LocationField>(details);
|
||||
|
||||
Label if_in_field(this), if_in_descriptor(this), done(this);
|
||||
|
@ -1598,6 +1598,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
|
||||
Node* name_index, Variable* var_details,
|
||||
Variable* var_value);
|
||||
|
||||
void LoadPropertyFromFastObject(Node* object, Node* map, Node* descriptors,
|
||||
Node* name_index, Node* details,
|
||||
Variable* var_value);
|
||||
|
||||
void LoadPropertyFromNameDictionary(Node* dictionary, Node* entry,
|
||||
Variable* var_details,
|
||||
Variable* var_value);
|
||||
@ -1924,11 +1928,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
|
||||
void DescriptorLookupBinary(Node* unique_name, Node* descriptors, Node* nof,
|
||||
Label* if_found, Variable* var_name_index,
|
||||
Label* if_not_found);
|
||||
Node* DescriptorNumberToIndex(SloppyTNode<Uint32T> descriptor_number);
|
||||
// Implements DescriptorArray::ToKeyIndex.
|
||||
// Returns an untagged IntPtr.
|
||||
Node* DescriptorArrayToKeyIndex(Node* descriptor_number);
|
||||
// Implements DescriptorArray::GetKey.
|
||||
Node* DescriptorArrayGetKey(Node* descriptors, Node* descriptor_number);
|
||||
// Implements DescriptorArray::GetKey.
|
||||
TNode<Uint32T> DescriptorArrayGetDetails(TNode<DescriptorArray> descriptors,
|
||||
TNode<Uint32T> descriptor_number);
|
||||
|
||||
Node* CallGetterIfAccessor(Node* value, Node* details, Node* context,
|
||||
Node* receiver, Label* if_bailout,
|
||||
|
@ -343,7 +343,11 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
|
||||
V(AllocateSeqOneByteString) \
|
||||
V(AllocateSeqTwoByteString) \
|
||||
V(ObjectCreate) \
|
||||
V(ObjectEntries) \
|
||||
V(ObjectEntriesSkipFastPath) \
|
||||
V(ObjectHasOwnProperty) \
|
||||
V(ObjectValues) \
|
||||
V(ObjectValuesSkipFastPath) \
|
||||
V(ArrayIndexOf) \
|
||||
V(ArrayIncludes_Slow) \
|
||||
V(ArrayIsArray) \
|
||||
|
@ -8794,9 +8794,10 @@ MUST_USE_RESULT Maybe<bool> FastGetOwnValuesOrEntries(
|
||||
MaybeHandle<FixedArray> GetOwnValuesOrEntries(Isolate* isolate,
|
||||
Handle<JSReceiver> object,
|
||||
PropertyFilter filter,
|
||||
bool try_fast_path,
|
||||
bool get_entries) {
|
||||
Handle<FixedArray> values_or_entries;
|
||||
if (filter == ENUMERABLE_STRINGS) {
|
||||
if (try_fast_path && filter == ENUMERABLE_STRINGS) {
|
||||
Maybe<bool> fast_values_or_entries = FastGetOwnValuesOrEntries(
|
||||
isolate, object, get_entries, &values_or_entries);
|
||||
if (fast_values_or_entries.IsNothing()) return MaybeHandle<FixedArray>();
|
||||
@ -8849,13 +8850,17 @@ MaybeHandle<FixedArray> GetOwnValuesOrEntries(Isolate* isolate,
|
||||
}
|
||||
|
||||
MaybeHandle<FixedArray> JSReceiver::GetOwnValues(Handle<JSReceiver> object,
|
||||
PropertyFilter filter) {
|
||||
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter, false);
|
||||
PropertyFilter filter,
|
||||
bool try_fast_path) {
|
||||
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter,
|
||||
try_fast_path, false);
|
||||
}
|
||||
|
||||
MaybeHandle<FixedArray> JSReceiver::GetOwnEntries(Handle<JSReceiver> object,
|
||||
PropertyFilter filter) {
|
||||
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter, true);
|
||||
PropertyFilter filter,
|
||||
bool try_fast_path) {
|
||||
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter,
|
||||
try_fast_path, true);
|
||||
}
|
||||
|
||||
bool Map::DictionaryElementsInPrototypeChainOnly() {
|
||||
|
@ -2187,10 +2187,12 @@ class JSReceiver: public HeapObject {
|
||||
Handle<JSReceiver> object);
|
||||
|
||||
MUST_USE_RESULT static MaybeHandle<FixedArray> GetOwnValues(
|
||||
Handle<JSReceiver> object, PropertyFilter filter);
|
||||
Handle<JSReceiver> object, PropertyFilter filter,
|
||||
bool try_fast_path = true);
|
||||
|
||||
MUST_USE_RESULT static MaybeHandle<FixedArray> GetOwnEntries(
|
||||
Handle<JSReceiver> object, PropertyFilter filter);
|
||||
Handle<JSReceiver> object, PropertyFilter filter,
|
||||
bool try_fast_path = true);
|
||||
|
||||
static const int kHashMask = PropertyArray::HashField::kMask;
|
||||
|
||||
|
@ -439,6 +439,61 @@ RUNTIME_FUNCTION(Runtime_OptimizeObjectForAddingMultipleProperties) {
|
||||
return *object;
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ObjectValues) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
|
||||
|
||||
Handle<FixedArray> values;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, values,
|
||||
JSReceiver::GetOwnValues(receiver, PropertyFilter::ENUMERABLE_STRINGS,
|
||||
true));
|
||||
return *isolate->factory()->NewJSArrayWithElements(values);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ObjectValuesSkipFastPath) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
|
||||
|
||||
Handle<FixedArray> value;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, value,
|
||||
JSReceiver::GetOwnValues(receiver, PropertyFilter::ENUMERABLE_STRINGS,
|
||||
false));
|
||||
return *isolate->factory()->NewJSArrayWithElements(value);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ObjectEntries) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
|
||||
|
||||
Handle<FixedArray> entries;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, entries,
|
||||
JSReceiver::GetOwnEntries(receiver, PropertyFilter::ENUMERABLE_STRINGS,
|
||||
true));
|
||||
return *isolate->factory()->NewJSArrayWithElements(entries);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ObjectEntriesSkipFastPath) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(1, args.length());
|
||||
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
|
||||
|
||||
Handle<FixedArray> entries;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, entries,
|
||||
JSReceiver::GetOwnEntries(receiver, PropertyFilter::ENUMERABLE_STRINGS,
|
||||
false));
|
||||
return *isolate->factory()->NewJSArrayWithElements(entries);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GetProperty) {
|
||||
HandleScope scope(isolate);
|
||||
|
@ -378,6 +378,10 @@ namespace internal {
|
||||
F(ObjectCreate, 2, 1) \
|
||||
F(InternalSetPrototype, 2, 1) \
|
||||
F(OptimizeObjectForAddingMultipleProperties, 2, 1) \
|
||||
F(ObjectValues, 1, 1) \
|
||||
F(ObjectValuesSkipFastPath, 1, 1) \
|
||||
F(ObjectEntries, 1, 1) \
|
||||
F(ObjectEntriesSkipFastPath, 1, 1) \
|
||||
F(GetProperty, 2, 1) \
|
||||
F(KeyedGetProperty, 2, 1) \
|
||||
F(AddNamedProperty, 4, 1) \
|
||||
|
@ -19,7 +19,7 @@ function TestMeta() {
|
||||
TestMeta();
|
||||
|
||||
|
||||
function TestBasic() {
|
||||
function TestBasic(withWarmup) {
|
||||
var x = 16;
|
||||
var O = {
|
||||
d: 1,
|
||||
@ -33,22 +33,29 @@ function TestBasic() {
|
||||
O.a = 2;
|
||||
O.b = 4;
|
||||
Object.defineProperty(O, "HIDDEN", { enumerable: false, value: NaN });
|
||||
assertEquals([
|
||||
if (withWarmup) {
|
||||
for (const key in O) {}
|
||||
}
|
||||
O.c = 6;
|
||||
const resultEntries = [
|
||||
["0", 123],
|
||||
["256", "ducks"],
|
||||
["1000", 456],
|
||||
["d", 1],
|
||||
["c", 3],
|
||||
["c", 6],
|
||||
["0x100", "quack"],
|
||||
["a", 2],
|
||||
["b", 4]
|
||||
], Object.entries(O));
|
||||
];
|
||||
assertEquals(resultEntries, Object.entries(O));
|
||||
assertEquals(resultEntries, Object.entries(O));
|
||||
assertEquals(Object.entries(O), Object.keys(O).map(key => [key, O[key]]));
|
||||
|
||||
assertTrue(Array.isArray(Object.entries({})));
|
||||
assertEquals(0, Object.entries({}).length);
|
||||
}
|
||||
TestBasic();
|
||||
TestBasic(true);
|
||||
|
||||
|
||||
function TestToObject() {
|
||||
@ -59,7 +66,7 @@ function TestToObject() {
|
||||
TestToObject();
|
||||
|
||||
|
||||
function TestOrder() {
|
||||
function TestOrder(withWarmup) {
|
||||
var O = {
|
||||
a: 1,
|
||||
[Symbol.iterator]: null
|
||||
@ -88,6 +95,11 @@ function TestOrder() {
|
||||
}
|
||||
});
|
||||
|
||||
if (withWarmup) {
|
||||
for (const key in P) {}
|
||||
}
|
||||
log = [];
|
||||
|
||||
assertEquals([["456", 123], ["a", 1]], Object.entries(P));
|
||||
assertEquals([
|
||||
"[[OwnPropertyKeys]]",
|
||||
@ -99,9 +111,10 @@ function TestOrder() {
|
||||
], log);
|
||||
}
|
||||
TestOrder();
|
||||
TestOrder(true);
|
||||
|
||||
|
||||
function TestOrderWithDuplicates() {
|
||||
function TestOrderWithDuplicates(withWarmup) {
|
||||
var O = {
|
||||
a: 1,
|
||||
[Symbol.iterator]: null
|
||||
@ -130,6 +143,11 @@ function TestOrderWithDuplicates() {
|
||||
}
|
||||
});
|
||||
|
||||
if (withWarmup) {
|
||||
for (const key in P) {}
|
||||
}
|
||||
log = [];
|
||||
|
||||
assertEquals([
|
||||
["a", 1],
|
||||
["a", 1],
|
||||
@ -151,9 +169,20 @@ function TestOrderWithDuplicates() {
|
||||
], log);
|
||||
}
|
||||
TestOrderWithDuplicates();
|
||||
TestOrderWithDuplicates(true);
|
||||
|
||||
function TestDescriptorProperty() {
|
||||
function f() {};
|
||||
const o = {};
|
||||
o.a = f;
|
||||
|
||||
function TestPropertyFilter() {
|
||||
for (const key in o) {};
|
||||
const entries = Object.entries(o);
|
||||
assertEquals([['a', f]], entries);
|
||||
}
|
||||
TestDescriptorProperty();
|
||||
|
||||
function TestPropertyFilter(withWarmup) {
|
||||
var object = { prop3: 30 };
|
||||
object[2] = 40;
|
||||
object["prop4"] = 50;
|
||||
@ -164,6 +193,10 @@ function TestPropertyFilter() {
|
||||
var sym = Symbol("prop8");
|
||||
object[sym] = 90;
|
||||
|
||||
if (withWarmup) {
|
||||
for (const key in object) {}
|
||||
}
|
||||
|
||||
values = Object.entries(object);
|
||||
assertEquals(5, values.length);
|
||||
assertEquals([
|
||||
@ -175,11 +208,15 @@ function TestPropertyFilter() {
|
||||
], values);
|
||||
}
|
||||
TestPropertyFilter();
|
||||
TestPropertyFilter(true);
|
||||
|
||||
|
||||
function TestWithProxy() {
|
||||
function TestWithProxy(withWarmup) {
|
||||
var obj1 = {prop1:10};
|
||||
var proxy1 = new Proxy(obj1, { });
|
||||
if (withWarmup) {
|
||||
for (const key in proxy1) {}
|
||||
}
|
||||
assertEquals([ [ "prop1", 10 ] ], Object.entries(proxy1));
|
||||
|
||||
var obj2 = {};
|
||||
@ -191,6 +228,9 @@ function TestWithProxy() {
|
||||
return Reflect.getOwnPropertyDescriptor(target, name);
|
||||
}
|
||||
});
|
||||
if (withWarmup) {
|
||||
for (const key in proxy2) {}
|
||||
}
|
||||
assertEquals([ [ "prop2", 20 ], [ "prop3", 30 ] ], Object.entries(proxy2));
|
||||
|
||||
var obj3 = {};
|
||||
@ -206,12 +246,16 @@ function TestWithProxy() {
|
||||
return [ "prop0", "prop1", Symbol("prop2"), Symbol("prop5") ];
|
||||
}
|
||||
});
|
||||
if (withWarmup) {
|
||||
for (const key in proxy3) {}
|
||||
}
|
||||
assertEquals([ [ "prop0", 0 ], [ "prop1", 5 ] ], Object.entries(proxy3));
|
||||
}
|
||||
TestWithProxy();
|
||||
TestWithProxy(true);
|
||||
|
||||
|
||||
function TestMutateDuringEnumeration() {
|
||||
function TestMutateDuringEnumeration(withWarmup) {
|
||||
var aDeletesB = {
|
||||
get a() {
|
||||
delete this.b;
|
||||
@ -219,6 +263,9 @@ function TestMutateDuringEnumeration() {
|
||||
},
|
||||
b: 2
|
||||
};
|
||||
if (withWarmup) {
|
||||
for (const key in aDeletesB) {}
|
||||
}
|
||||
assertEquals([ [ "a", 1 ] ], Object.entries(aDeletesB));
|
||||
|
||||
var aRemovesB = {
|
||||
@ -228,9 +275,15 @@ function TestMutateDuringEnumeration() {
|
||||
},
|
||||
b: 2
|
||||
};
|
||||
if (withWarmup) {
|
||||
for (const key in aRemovesB) {}
|
||||
}
|
||||
assertEquals([ [ "a", 1 ] ], Object.entries(aRemovesB));
|
||||
|
||||
var aAddsB = { get a() { this.b = 2; return 1; } };
|
||||
if (withWarmup) {
|
||||
for (const key in aAddsB) {}
|
||||
}
|
||||
assertEquals([ [ "a", 1 ] ], Object.entries(aAddsB));
|
||||
|
||||
var aMakesBEnumerable = {};
|
||||
@ -243,12 +296,16 @@ function TestMutateDuringEnumeration() {
|
||||
});
|
||||
Object.defineProperty(aMakesBEnumerable, "b", {
|
||||
value: 2, configurable:true, enumerable: false });
|
||||
if (withWarmup) {
|
||||
for (const key in aMakesBEnumerable) {}
|
||||
}
|
||||
assertEquals([ [ "a", 1 ], [ "b", 2 ] ], Object.entries(aMakesBEnumerable));
|
||||
}
|
||||
TestMutateDuringEnumeration();
|
||||
TestMutateDuringEnumeration(true);
|
||||
|
||||
|
||||
(function TestElementKinds() {
|
||||
function TestElementKinds(withWarmup) {
|
||||
var O1 = { name: "1" }, O2 = { name: "2" }, O3 = { name: "3" };
|
||||
var PI = 3.141592653589793;
|
||||
var E = 2.718281828459045;
|
||||
@ -303,13 +360,22 @@ TestMutateDuringEnumeration();
|
||||
}), [["0", "s"], ["1", "t"], ["2", "r"], ["9999", "Y"]] ],
|
||||
};
|
||||
|
||||
if (withWarmup) {
|
||||
for (const key in element_kinds) {}
|
||||
}
|
||||
for (let [kind, [object, expected]] of Object.entries(element_kinds)) {
|
||||
if (withWarmup) {
|
||||
for (const key in object) {}
|
||||
}
|
||||
let result1 = Object.entries(object);
|
||||
%HeapObjectVerify(object);
|
||||
%HeapObjectVerify(result1);
|
||||
assertEquals(expected, result1, `fast Object.entries() with ${kind}`);
|
||||
|
||||
let proxy = new Proxy(object, {});
|
||||
if (withWarmup) {
|
||||
for (const key in proxy) {}
|
||||
}
|
||||
let result2 = Object.entries(proxy);
|
||||
%HeapObjectVerify(result2);
|
||||
assertEquals(result1, result2, `slow Object.entries() with ${kind}`);
|
||||
@ -331,9 +397,15 @@ TestMutateDuringEnumeration();
|
||||
for (let [kind, [object, expected]] of Object.entries(element_kinds)) {
|
||||
if (kind == "FAST_STRING_WRAPPER_ELEMENTS") break;
|
||||
object.__defineGetter__(1, makeFastElements);
|
||||
if (withWarmup) {
|
||||
for (const key in object) {}
|
||||
}
|
||||
let result1 = Object.entries(object).toString();
|
||||
%HeapObjectVerify(object);
|
||||
%HeapObjectVerify(result1);
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
|
||||
TestElementKinds();
|
||||
TestElementKinds(true);
|
||||
|
Loading…
Reference in New Issue
Block a user