Commit 9367f80f authored by Igor Sheludko's avatar Igor Sheludko Committed by Commit Bot

[builtins] Implement fast path of Object.assign using CSA.

Bug: v8:5988
Change-Id: I2e90ed8df6b966e04299774e50aeb2913a8c1922
Reviewed-on: https://chromium-review.googlesource.com/999603
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52610}
parent 8cc5a723
......@@ -762,7 +762,7 @@ namespace internal {
/* Object */ \
/* ES #sec-object-constructor */ \
TFJ(ObjectConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
CPP(ObjectAssign) \
TFJ(ObjectAssign, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES #sec-object.create */ \
TFJ(ObjectCreate, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
CPP(ObjectDefineGetter) \
......
......@@ -6,6 +6,8 @@
#include "src/builtins/builtins.h"
#include "src/code-stub-assembler.h"
#include "src/heap/factory-inl.h"
#include "src/ic/accessor-assembler.h"
#include "src/ic/keyed-store-generic.h"
#include "src/objects/property-descriptor-object.h"
#include "src/objects/shared-function-info.h"
......@@ -38,6 +40,16 @@ class ObjectBuiltinsAssembler : public CodeStubAssembler {
Node* GetAccessorOrUndefined(Node* accessor, Label* if_bailout);
Node* IsSpecialReceiverMap(SloppyTNode<Map> map);
TNode<Word32T> IsStringWrapperElementsKind(TNode<Map> map);
// Checks that |map| has only simple properties, returns bitfield3.
TNode<Uint32T> EnsureOnlyHasSimpleProperties(TNode<Map> map,
TNode<Int32T> instance_type,
Label* bailout);
void ObjectAssignFast(TNode<Context> context, TNode<JSReceiver> to,
TNode<Object> from, Label* slow);
};
class ObjectEntriesValuesBuiltinsAssembler : public ObjectBuiltinsAssembler {
......@@ -49,8 +61,6 @@ class ObjectEntriesValuesBuiltinsAssembler : public ObjectBuiltinsAssembler {
protected:
enum CollectType { kEntries, kValues };
TNode<Word32T> IsStringWrapperElementsKind(TNode<Map> map);
TNode<BoolT> IsPropertyEnumerable(TNode<Uint32T> details);
TNode<BoolT> IsPropertyKindAccessor(TNode<Uint32T> kind);
......@@ -153,8 +163,7 @@ Node* ObjectBuiltinsAssembler::IsSpecialReceiverMap(SloppyTNode<Map> map) {
return is_special;
}
TNode<Word32T>
ObjectEntriesValuesBuiltinsAssembler::IsStringWrapperElementsKind(
TNode<Word32T> ObjectBuiltinsAssembler::IsStringWrapperElementsKind(
TNode<Map> map) {
Node* kind = LoadMapElementsKind(map);
return Word32Or(
......@@ -472,6 +481,219 @@ TF_BUILTIN(ObjectPrototypeHasOwnProperty, ObjectBuiltinsAssembler) {
Return(CallRuntime(Runtime::kObjectHasOwnProperty, context, object, key));
}
// ES #sec-object.assign
TF_BUILTIN(ObjectAssign, ObjectBuiltinsAssembler) {
TNode<IntPtrT> argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
TNode<Object> target = args.GetOptionalArgumentValue(0);
// 1. Let to be ? ToObject(target).
TNode<JSReceiver> to = ToObject(context, target);
Label done(this);
// 2. If only one argument was passed, return to.
GotoIf(UintPtrLessThanOrEqual(argc, IntPtrConstant(1)), &done);
// 3. Let sources be the List of argument values starting with the
// second argument.
// 4. For each element nextSource of sources, in ascending index order,
args.ForEach(
[=](Node* next_source_) {
TNode<Object> next_source = CAST(next_source_);
Label slow(this), cont(this);
ObjectAssignFast(context, to, next_source, &slow);
Goto(&cont);
BIND(&slow);
{
CallRuntime(Runtime::kSetDataProperties, context, to, next_source);
Goto(&cont);
}
BIND(&cont);
},
IntPtrConstant(1));
Goto(&done);
// 5. Return to.
BIND(&done);
args.PopAndReturn(to);
}
TNode<Uint32T> ObjectBuiltinsAssembler::EnsureOnlyHasSimpleProperties(
TNode<Map> map, TNode<Int32T> instance_type, Label* bailout) {
GotoIf(IsCustomElementsReceiverInstanceType(instance_type), bailout);
TNode<Uint32T> bit_field3 = LoadMapBitField3(map);
GotoIf(IsSetWord32(bit_field3, Map::IsDictionaryMapBit::kMask |
Map::HasHiddenPrototypeBit::kMask),
bailout);
return bit_field3;
}
// This function mimics what FastAssign() function does for C++ implementation.
void ObjectBuiltinsAssembler::ObjectAssignFast(TNode<Context> context,
TNode<JSReceiver> to,
TNode<Object> from,
Label* slow) {
Label done(this);
// Non-empty strings are the only non-JSReceivers that need to be handled
// explicitly by Object.assign.
GotoIf(TaggedIsSmi(from), &done);
TNode<Map> from_map = LoadMap(CAST(from));
TNode<Int32T> from_instance_type = LoadMapInstanceType(from_map);
{
Label cont(this);
GotoIf(IsJSReceiverInstanceType(from_instance_type), &cont);
GotoIfNot(IsStringInstanceType(from_instance_type), &done);
{
Branch(SmiEqual(LoadStringLengthAsSmi(CAST(from)), SmiConstant(0)), &done,
slow);
}
BIND(&cont);
}
// If the target is deprecated, the object will be updated on first store. If
// the source for that store equals the target, this will invalidate the
// cached representation of the source. Handle this case in runtime.
TNode<Map> to_map = LoadMap(to);
GotoIf(IsDeprecatedMap(to_map), slow);
GotoIfNot(IsJSObjectInstanceType(from_instance_type), slow);
TNode<Uint32T> from_bit_field3 =
EnsureOnlyHasSimpleProperties(from_map, from_instance_type, slow);
GotoIfNot(IsEmptyFixedArray(LoadElements(CAST(from))), slow);
TNode<DescriptorArray> from_descriptors = LoadMapDescriptors(from_map);
TNode<Uint32T> nof_descriptors =
DecodeWord32<Map::NumberOfOwnDescriptorsBits>(from_bit_field3);
TVARIABLE(BoolT, var_stable, Int32TrueConstant());
VariableList list({&var_stable}, zone());
DescriptorArrayForEach(
list, Unsigned(Int32Constant(0)), nof_descriptors,
[=, &var_stable](TNode<UintPtrT> descriptor_key_index) {
TNode<Name> next_key =
CAST(LoadFixedArrayElement(from_descriptors, descriptor_key_index));
TVARIABLE(Object, var_value, SmiConstant(0));
Label do_store(this), next_iteration(this);
{
TVARIABLE(Map, var_from_map);
TVARIABLE(HeapObject, var_meta_storage);
TVARIABLE(IntPtrT, var_entry);
TVARIABLE(Uint32T, var_details);
Label if_found(this);
Label if_found_fast(this), if_found_dict(this);
Label if_stable(this), if_not_stable(this);
Branch(var_stable.value(), &if_stable, &if_not_stable);
BIND(&if_stable);
{
// Directly decode from the descriptor array if |from| did not
// change shape.
var_from_map = from_map;
var_meta_storage = from_descriptors;
var_entry = Signed(descriptor_key_index);
Goto(&if_found_fast);
}
BIND(&if_not_stable);
{
// If the map did change, do a slower lookup. We are still
// guaranteed that the object has a simple shape, and that the key
// is a name.
var_from_map = LoadMap(CAST(from));
TryLookupPropertyInSimpleObject(
CAST(from), var_from_map.value(), next_key, &if_found_fast,
&if_found_dict, &var_meta_storage, &var_entry, &next_iteration);
}
BIND(&if_found_fast);
{
Node* descriptors = var_meta_storage.value();
Node* name_index = var_entry.value();
// Skip non-enumerable properties.
var_details =
LoadDetailsByKeyIndex<DescriptorArray>(descriptors, name_index);
GotoIf(IsSetWord32(var_details.value(),
PropertyDetails::kAttributesDontEnumMask),
&next_iteration);
LoadPropertyFromFastObject(from, var_from_map.value(), descriptors,
name_index, var_details.value(),
&var_value);
Goto(&if_found);
}
BIND(&if_found_dict);
{
Node* dictionary = var_meta_storage.value();
Node* entry = var_entry.value();
TNode<Uint32T> details =
LoadDetailsByKeyIndex<NameDictionary>(dictionary, entry);
// Skip non-enumerable properties.
GotoIf(
IsSetWord32(details, PropertyDetails::kAttributesDontEnumMask),
&next_iteration);
var_details = details;
var_value = LoadValueByKeyIndex<NameDictionary>(dictionary, entry);
Goto(&if_found);
}
// Here we have details and value which could be an accessor.
BIND(&if_found);
{
Label slow_load(this, Label::kDeferred);
var_value =
CallGetterIfAccessor(var_value.value(), var_details.value(),
context, from, &slow_load, kCallJSGetter);
Goto(&do_store);
BIND(&slow_load);
{
var_value =
CallRuntime(Runtime::kGetProperty, context, from, next_key);
Goto(&do_store);
}
}
}
// Store property to target object.
BIND(&do_store);
{
KeyedStoreGenericGenerator::SetProperty(state(), context, to,
next_key, var_value.value(),
LanguageMode::kStrict);
// Check if the |from| object is still stable, i.e. we can proceed
// using property details from preloaded |from_descriptors|.
var_stable = Select<BoolT>(
var_stable.value(),
[=] { return WordEqual(LoadMap(CAST(from)), from_map); },
[=] { return Int32FalseConstant(); });
Goto(&next_iteration);
}
BIND(&next_iteration);
});
Goto(&done);
BIND(&done);
}
// ES #sec-object.keys
TF_BUILTIN(ObjectKeys, ObjectBuiltinsAssembler) {
Node* object = Parameter(Descriptor::kObject);
......
......@@ -18,29 +18,6 @@ namespace internal {
// -----------------------------------------------------------------------------
// ES6 section 19.1 Object Objects
// ES6 19.1.2.1 Object.assign
BUILTIN(ObjectAssign) {
HandleScope scope(isolate);
Handle<Object> target = args.atOrUndefined(isolate, 1);
// 1. Let to be ? ToObject(target).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, target,
Object::ToObject(isolate, target));
Handle<JSReceiver> to = Handle<JSReceiver>::cast(target);
// 2. If only one argument was passed, return to.
if (args.length() == 2) return *to;
// 3. Let sources be the List of argument values starting with the
// second argument.
// 4. For each element nextSource of sources, in ascending index order,
for (int i = 2; i < args.length(); ++i) {
Handle<Object> next_source = args.at(i);
MAYBE_RETURN(JSReceiver::SetOrCopyDataProperties(isolate, to, next_source),
isolate->heap()->exception());
}
// 5. Return to.
return *to;
}
// ES6 section 19.1.3.4 Object.prototype.propertyIsEnumerable ( V )
BUILTIN(ObjectPrototypePropertyIsEnumerable) {
HandleScope scope(isolate);
......
......@@ -7014,6 +7014,27 @@ void CodeStubAssembler::LookupBinary(TNode<Name> unique_name,
}
}
void CodeStubAssembler::DescriptorArrayForEach(
VariableList& variable_list, TNode<Uint32T> start_descriptor,
TNode<Uint32T> end_descriptor, const ForEachDescriptorBodyFunction& body) {
TNode<IntPtrT> start_index =
IntPtrAdd(IntPtrConstant(DescriptorArray::ToKeyIndex(0)),
EntryIndexToIndex<DescriptorArray>(start_descriptor));
TNode<IntPtrT> end_index =
IntPtrAdd(IntPtrConstant(DescriptorArray::ToKeyIndex(0)),
EntryIndexToIndex<DescriptorArray>(end_descriptor));
BuildFastLoop(variable_list, start_index, end_index,
[=](Node* index) {
TNode<UintPtrT> descriptor_key_index =
TNode<UintPtrT>::UncheckedCast(index);
body(descriptor_key_index);
},
DescriptorArray::kEntrySize, INTPTR_PARAMETERS,
IndexAdvanceMode::kPost);
}
void CodeStubAssembler::DescriptorLookup(
SloppyTNode<Name> unique_name, SloppyTNode<DescriptorArray> descriptors,
SloppyTNode<Uint32T> bitfield3, Label* if_found,
......@@ -7448,7 +7469,7 @@ void CodeStubAssembler::TryGetOwnProperty(
if (!var_details) {
var_details = &local_var_details;
}
Label if_found(this, {var_value, var_details});
Label if_found(this);
TryLookupProperty(object, map, instance_type, unique_name, &if_found_fast,
&if_found_dict, &if_found_global, &var_meta_storage,
......
......@@ -2236,6 +2236,14 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<Uint32T> DescriptorArrayGetDetails(TNode<DescriptorArray> descriptors,
TNode<Uint32T> descriptor_number);
typedef std::function<void(TNode<UintPtrT> descriptor_key_index)>
ForEachDescriptorBodyFunction;
void DescriptorArrayForEach(VariableList& variable_list,
TNode<Uint32T> start_descriptor,
TNode<Uint32T> end_descriptor,
const ForEachDescriptorBodyFunction& body);
TNode<Object> CallGetterIfAccessor(Node* value, Node* details, Node* context,
Node* receiver, Label* if_bailout,
GetOwnPropertyMode mode = kCallJSGetter);
......
......@@ -16,7 +16,9 @@
namespace v8 {
namespace internal {
using compiler::Node;
using Node = compiler::Node;
template <class T>
using TNode = compiler::TNode<T>;
class KeyedStoreGenericAssembler : public AccessorAssembler {
public:
......@@ -27,6 +29,12 @@ class KeyedStoreGenericAssembler : public AccessorAssembler {
void StoreIC_Uninitialized();
// Generates code for [[Set]] operation, the |unique_name| is supposed to be
// unique otherwise this code will always go to runtime.
void SetProperty(TNode<Context> context, TNode<JSReceiver> receiver,
TNode<Name> unique_name, TNode<Object> value,
LanguageMode language_mode);
private:
enum UpdateLength {
kDontChangeLength,
......@@ -40,15 +48,18 @@ class KeyedStoreGenericAssembler : public AccessorAssembler {
Node* instance_type, Node* intptr_index,
Node* value, Node* context, Label* slow);
// If language mode is not provided it is deduced from the feedback slot's
// kind.
void EmitGenericPropertyStore(Node* receiver, Node* receiver_map,
const StoreICParameters* p,
ExitPoint* exit_point, Label* slow,
bool assume_strict_language_mode = false);
Maybe<LanguageMode> maybe_language_mode);
void EmitGenericPropertyStore(Node* receiver, Node* receiver_map,
const StoreICParameters* p, Label* slow) {
ExitPoint direct_exit(this);
EmitGenericPropertyStore(receiver, receiver_map, p, &direct_exit, slow);
EmitGenericPropertyStore(receiver, receiver_map, p, &direct_exit, slow,
Nothing<LanguageMode>());
}
void BranchIfPrototypesHaveNonFastElements(Node* receiver_map,
......@@ -97,6 +108,14 @@ void StoreICUninitializedGenerator::Generate(
assembler.StoreIC_Uninitialized();
}
void KeyedStoreGenericGenerator::SetProperty(
compiler::CodeAssemblerState* state, TNode<Context> context,
TNode<JSReceiver> receiver, TNode<Name> name, TNode<Object> value,
LanguageMode language_mode) {
KeyedStoreGenericAssembler assembler(state);
assembler.SetProperty(context, receiver, name, value, language_mode);
}
void KeyedStoreGenericAssembler::BranchIfPrototypesHaveNonFastElements(
Node* receiver_map, Label* non_fast_elements, Label* only_fast_elements) {
VARIABLE(var_map, MachineRepresentation::kTagged);
......@@ -615,7 +634,8 @@ void KeyedStoreGenericAssembler::LookupPropertyOnPrototypeChain(
void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
Node* receiver, Node* receiver_map, const StoreICParameters* p,
ExitPoint* exit_point, Label* slow, bool assume_strict_language_mode) {
ExitPoint* exit_point, Label* slow,
Maybe<LanguageMode> maybe_language_mode) {
VARIABLE(var_accessor_pair, MachineRepresentation::kTagged);
VARIABLE(var_accessor_holder, MachineRepresentation::kTagged);
Label stub_cache(this), fast_properties(this), dictionary_properties(this),
......@@ -805,36 +825,54 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&not_callable);
{
bool handle_strict = true;
Label strict(this);
if (assume_strict_language_mode) {
Goto(&strict);
LanguageMode language_mode;
if (maybe_language_mode.To(&language_mode)) {
if (language_mode == LanguageMode::kStrict) {
Goto(&strict);
} else {
handle_strict = false;
exit_point->Return(p->value);
}
} else {
BranchIfStrictMode(p->vector, p->slot, &strict);
exit_point->Return(p->value);
}
BIND(&strict);
{
ThrowTypeError(p->context, MessageTemplate::kNoSetterInCallback,
p->name, var_accessor_holder.value());
if (handle_strict) {
BIND(&strict);
{
ThrowTypeError(p->context, MessageTemplate::kNoSetterInCallback,
p->name, var_accessor_holder.value());
}
}
}
}
BIND(&readonly);
{
bool handle_strict = true;
Label strict(this);
if (assume_strict_language_mode) {
Goto(&strict);
LanguageMode language_mode;
if (maybe_language_mode.To(&language_mode)) {
if (language_mode == LanguageMode::kStrict) {
Goto(&strict);
} else {
handle_strict = false;
exit_point->Return(p->value);
}
} else {
BranchIfStrictMode(p->vector, p->slot, &strict);
exit_point->Return(p->value);
}
BIND(&strict);
{
Node* type = Typeof(p->receiver);
ThrowTypeError(p->context, MessageTemplate::kStrictReadOnlyProperty,
p->name, type, p->receiver);
if (handle_strict) {
BIND(&strict);
{
Node* type = Typeof(p->receiver);
ThrowTypeError(p->context, MessageTemplate::kStrictReadOnlyProperty,
p->name, type, p->receiver);
}
}
}
}
......@@ -943,5 +981,28 @@ void KeyedStoreGenericAssembler::StoreIC_Uninitialized() {
}
}
void KeyedStoreGenericAssembler::SetProperty(TNode<Context> context,
TNode<JSReceiver> receiver,
TNode<Name> unique_name,
TNode<Object> value,
LanguageMode language_mode) {
StoreICParameters p(context, receiver, unique_name, value, nullptr, nullptr);
Label done(this), slow(this, Label::kDeferred);
ExitPoint exit_point(this, [&](Node* result) { Goto(&done); });
EmitGenericPropertyStore(receiver, LoadMap(receiver), &p, &exit_point, &slow,
Just(language_mode));
BIND(&slow);
{
CallRuntime(Runtime::kSetProperty, context, receiver, unique_name, value,
SmiConstant(language_mode));
Goto(&done);
}
BIND(&done);
}
} // namespace internal
} // namespace v8
......@@ -5,18 +5,24 @@
#ifndef V8_IC_KEYED_STORE_GENERIC_H_
#define V8_IC_KEYED_STORE_GENERIC_H_
#include "src/compiler/code-assembler.h"
#include "src/globals.h"
namespace v8 {
namespace internal {
namespace compiler {
class CodeAssemblerState;
}
class KeyedStoreGenericGenerator {
public:
template <class T>
using TNode = compiler::TNode<T>;
static void Generate(compiler::CodeAssemblerState* state);
// Building block for fast path of Object.assign implementation.
static void SetProperty(compiler::CodeAssemblerState* state,
TNode<Context> context, TNode<JSReceiver> receiver,
TNode<Name> name, TNode<Object> value,
LanguageMode language_mode);
};
class StoreICUninitializedGenerator {
......
......@@ -930,6 +930,22 @@ RUNTIME_FUNCTION(Runtime_DefineGetterPropertyUnchecked) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_SetDataProperties) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSObject, target, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, source, 1);
// 2. If source is undefined or null, let keys be an empty List.
if (source->IsUndefined(isolate) || source->IsNull(isolate)) {
return isolate->heap()->undefined_value();
}
MAYBE_RETURN(JSReceiver::SetOrCopyDataProperties(isolate, target, source),
isolate->heap()->exception());
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_CopyDataProperties) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
......
......@@ -401,6 +401,7 @@ namespace internal {
F(OptimizeObjectForAddingMultipleProperties, 2, 1) \
F(SameValue, 2, 1) \
F(SameValueZero, 2, 1) \
F(SetDataProperties, 2, 1) \
F(SetProperty, 4, 1) \
F(ShrinkPropertyDictionary, 1, 1) \
F(ToFastProperties, 1, 1) \
......
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