Commit b6f7ea58 authored by Caitlin Potter's avatar Caitlin Potter Committed by Commit Bot

[runtime] use new CloneObject bytecode for some ObjectLiteralSpread cases

As discussed in
https://docs.google.com/document/d/1sBdGe8RHgeYP850cKSSgGABTyfMdvaEWLy-vertuTCo/edit?ts=5b3ba5cc#,

this CL introduces a new bytecode (CloneObject), and a new IC type.

In this prototype implementation, the type feedback looks like the
following:

Uninitialized case:
  { uninitialized_sentinel, uninitialized_sentinel }
Monomorphic case:
  { weak 'source' map, strong 'result' map }
Polymorphic case:
  { WeakFixedArray with { weak 'source' map, strong 'result' map }, cleared value }
Megamorphic case:
  { megamorphic_sentinel, cleared_Value }

In the fast case, Object cloning is done by allocating an object with
the saved result map, and a shallow clone of the fast properties from
the source object, as well as cloned fast elements from the source object.
If at any point the fast case can't be taken, the IC transitions to the
slow case and remains there.

This prototype CL does not include any TurboFan optimization, and the
CloneObject operation is merely reduced to a stub call.

It may still be possible to get some further improvements by somehow
incorporating compile-time boilerplate elements into the cloned object,
or simplifying how the boilerplate elements are inserted into the
object.

In terms of performance, we improve the ObjectSpread score in JSTests/ObjectLiteralSpread/
by about 8x, with substantial improvements over the Babel and ObjectAssign scores.

R=gsathya@chromium.org, mvstanton@chromium.org, rmcilroy@chromium.org, neis@chromium.org, bmeurer@chromium.org
BUG=v8:7611

Change-Id: I79e1796eb77016fb4feba0e1d3bb9abb348c183e
Reviewed-on: https://chromium-review.googlesource.com/1127472
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54595}
parent 8fb0db93
......@@ -639,6 +639,7 @@ namespace internal {
TFH(LoadGlobalICInsideTypeof, LoadGlobalWithVector) \
TFH(LoadGlobalICTrampoline, LoadGlobal) \
TFH(LoadGlobalICInsideTypeofTrampoline, LoadGlobal) \
TFH(CloneObjectIC, CloneObjectWithVector) \
\
/* Map */ \
TFS(FindOrderedHashMapEntry, kTable, kKey) \
......
......@@ -35,6 +35,7 @@ IC_BUILTIN(StoreICTrampoline)
IC_BUILTIN(KeyedStoreIC)
IC_BUILTIN(KeyedStoreICTrampoline)
IC_BUILTIN(StoreInArrayLiteralIC)
IC_BUILTIN(CloneObjectIC)
IC_BUILTIN_PARAM(LoadGlobalIC, LoadGlobalIC, NOT_INSIDE_TYPEOF)
IC_BUILTIN_PARAM(LoadGlobalICInsideTypeof, LoadGlobalIC, INSIDE_TYPEOF)
......
......@@ -1989,6 +1989,13 @@ TNode<Object> CodeStubAssembler::LoadPropertyArrayElement(
needs_poisoning));
}
TNode<IntPtrT> CodeStubAssembler::LoadPropertyArrayLength(
TNode<PropertyArray> object) {
TNode<IntPtrT> value =
LoadAndUntagObjectField(object, PropertyArray::kLengthAndHashOffset);
return Signed(DecodeWord<PropertyArray::LengthField>(value));
}
TNode<RawPtrT> CodeStubAssembler::LoadFixedTypedArrayBackingStore(
TNode<FixedTypedArrayBase> typed_array) {
// Backing store = external_pointer + base_pointer.
......
......@@ -1002,6 +1002,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<Object> LoadPropertyArrayElement(SloppyTNode<PropertyArray> object,
SloppyTNode<IntPtrT> index);
TNode<IntPtrT> LoadPropertyArrayLength(TNode<PropertyArray> object);
// Load an array element from a FixedArray / WeakFixedArray, untag it and
// return it as Word32.
......
......@@ -1625,6 +1625,18 @@ void BytecodeGraphBuilder::VisitCreateEmptyObjectLiteral() {
environment()->BindAccumulator(literal);
}
void BytecodeGraphBuilder::VisitCloneObject() {
PrepareEagerCheckpoint();
Node* source =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
int flags = bytecode_iterator().GetFlagOperand(1);
int slot = bytecode_iterator().GetIndexOperand(2);
const Operator* op =
javascript()->CloneObject(CreateVectorSlotPair(slot), flags);
Node* value = NewNode(op, source);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitGetTemplateObject() {
Handle<TemplateObjectDescription> description(
TemplateObjectDescription::cast(
......
......@@ -93,6 +93,12 @@ class BytecodeGraphBuilder {
return MakeNode(op, arraysize(buffer), buffer, false);
}
Node* NewNode(const Operator* op, Node* n1, Node* n2, Node* n3, Node* n4,
Node* n5, Node* n6) {
Node* buffer[] = {n1, n2, n3, n4, n5, n6};
return MakeNode(op, arraysize(buffer), buffer, false);
}
// Helpers to create new control nodes.
Node* NewIfTrue() { return NewNode(common()->IfTrue()); }
Node* NewIfFalse() { return NewNode(common()->IfFalse()); }
......
......@@ -532,6 +532,17 @@ void JSGenericLowering::LowerJSCreateLiteralObject(Node* node) {
}
}
void JSGenericLowering::LowerJSCloneObject(Node* node) {
CloneObjectParameters const& p = CloneObjectParametersOf(node->op());
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kCloneObjectIC);
node->InsertInput(zone(), 1, jsgraph()->SmiConstant(p.flags()));
node->InsertInput(zone(), 2, jsgraph()->SmiConstant(p.feedback().index()));
node->InsertInput(zone(), 3, jsgraph()->HeapConstant(p.feedback().vector()));
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSCreateEmptyLiteralObject(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
......
......@@ -534,6 +534,29 @@ const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op) {
return OpParameter<CreateLiteralParameters>(op);
}
bool operator==(CloneObjectParameters const& lhs,
CloneObjectParameters const& rhs) {
return lhs.feedback() == rhs.feedback() && lhs.flags() == rhs.flags();
}
bool operator!=(CloneObjectParameters const& lhs,
CloneObjectParameters const& rhs) {
return !(lhs == rhs);
}
size_t hash_value(CloneObjectParameters const& p) {
return base::hash_combine(p.feedback(), p.flags());
}
std::ostream& operator<<(std::ostream& os, CloneObjectParameters const& p) {
return os << p.flags();
}
const CloneObjectParameters& CloneObjectParametersOf(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kJSCloneObject);
return OpParameter<CloneObjectParameters>(op);
}
size_t hash_value(ForInMode mode) { return static_cast<uint8_t>(mode); }
std::ostream& operator<<(std::ostream& os, ForInMode mode) {
......@@ -1179,6 +1202,17 @@ const Operator* JSOperatorBuilder::CreateLiteralObject(
parameters); // parameter
}
const Operator* JSOperatorBuilder::CloneObject(VectorSlotPair const& feedback,
int literal_flags) {
CloneObjectParameters parameters(feedback, literal_flags);
return new (zone()) Operator1<CloneObjectParameters>( // --
IrOpcode::kJSCloneObject, // opcode
Operator::kNoProperties, // properties
"JSCloneObject", // name
1, 1, 1, 1, 1, 1, // counts
parameters); // parameter
}
const Operator* JSOperatorBuilder::CreateEmptyLiteralObject() {
return new (zone()) Operator( // --
IrOpcode::kJSCreateEmptyLiteralObject, // opcode
......
......@@ -626,6 +626,28 @@ std::ostream& operator<<(std::ostream&, CreateLiteralParameters const&);
const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op);
class CloneObjectParameters final {
public:
CloneObjectParameters(VectorSlotPair const& feedback, int flags)
: feedback_(feedback), flags_(flags) {}
VectorSlotPair const& feedback() const { return feedback_; }
int flags() const { return flags_; }
private:
VectorSlotPair const feedback_;
int const flags_;
};
bool operator==(CloneObjectParameters const&, CloneObjectParameters const&);
bool operator!=(CloneObjectParameters const&, CloneObjectParameters const&);
size_t hash_value(CloneObjectParameters const&);
std::ostream& operator<<(std::ostream&, CloneObjectParameters const&);
const CloneObjectParameters& CloneObjectParametersOf(const Operator* op);
// Descriptor used by the JSForInPrepare and JSForInNext opcodes.
enum class ForInMode : uint8_t {
kUseEnumCacheKeysAndIndices,
......@@ -716,6 +738,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
Handle<ObjectBoilerplateDescription> constant,
VectorSlotPair const& feedback, int literal_flags,
int number_of_properties);
const Operator* CloneObject(VectorSlotPair const& feedback,
int literal_flags);
const Operator* CreateLiteralRegExp(Handle<String> constant_pattern,
VectorSlotPair const& feedback,
int literal_flags);
......
......@@ -152,6 +152,7 @@
V(JSCreateEmptyLiteralArray) \
V(JSCreateLiteralObject) \
V(JSCreateEmptyLiteralObject) \
V(JSCloneObject) \
V(JSCreateLiteralRegExp)
#define JS_OBJECT_OP_LIST(V) \
......
......@@ -76,6 +76,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateLiteralRegExp:
case IrOpcode::kJSCreateObject:
case IrOpcode::kJSCloneObject:
// Property access operations
case IrOpcode::kJSLoadNamed:
......
......@@ -1214,6 +1214,10 @@ Type Typer::Visitor::TypeJSCreateEmptyLiteralObject(Node* node) {
return Type::OtherObject();
}
Type Typer::Visitor::TypeJSCloneObject(Node* node) {
return Type::OtherObject();
}
Type Typer::Visitor::TypeJSCreateLiteralRegExp(Node* node) {
return Type::OtherObject();
}
......
......@@ -716,6 +716,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
break;
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateEmptyLiteralObject:
case IrOpcode::kJSCloneObject:
case IrOpcode::kJSCreateLiteralRegExp:
// Type is OtherObject.
CheckTypeIs(node, Type::OtherObject());
......
......@@ -67,6 +67,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) {
return 1;
case FeedbackSlotKind::kCall:
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
......@@ -337,6 +338,7 @@ void FeedbackVector::ComputeCounts(int* with_type_info, int* generic,
}
case FeedbackSlotKind::kCreateClosure:
case FeedbackSlotKind::kLiteral:
case FeedbackSlotKind::kCloneObject:
break;
case FeedbackSlotKind::kInvalid:
case FeedbackSlotKind::kKindsNumber:
......
......@@ -170,6 +170,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) {
return "ForIn";
case FeedbackSlotKind::kInstanceOf:
return "InstanceOf";
case FeedbackSlotKind::kCloneObject:
return "CloneObject";
case FeedbackSlotKind::kKindsNumber:
break;
}
......@@ -254,6 +256,7 @@ Handle<FeedbackVector> FeedbackVector::New(Isolate* isolate,
vector->set(index, *uninitialized_sentinel, SKIP_WRITE_BARRIER);
extra_value = Smi::kZero;
break;
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kStoreNamedSloppy:
......@@ -416,6 +419,7 @@ void FeedbackNexus::ConfigureUninitialized() {
SKIP_WRITE_BARRIER);
break;
}
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kCall: {
SetFeedback(*FeedbackVector::UninitializedSentinel(isolate),
SKIP_WRITE_BARRIER);
......@@ -480,6 +484,7 @@ bool FeedbackNexus::Clear() {
case FeedbackSlotKind::kCall:
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kStoreDataPropertyInLiteral:
case FeedbackSlotKind::kCloneObject:
if (!IsCleared()) {
ConfigureUninitialized();
feedback_updated = true;
......@@ -501,6 +506,20 @@ void FeedbackNexus::ConfigurePremonomorphic() {
SKIP_WRITE_BARRIER);
}
bool FeedbackNexus::ConfigureMegamorphic() {
DisallowHeapAllocation no_gc;
Isolate* isolate = GetIsolate();
MaybeObject* sentinel =
MaybeObject::FromObject(*FeedbackVector::MegamorphicSentinel(isolate));
if (GetFeedback() != sentinel) {
SetFeedback(sentinel, SKIP_WRITE_BARRIER);
SetFeedbackExtra(HeapObjectReference::ClearedValue());
return true;
}
return false;
}
bool FeedbackNexus::ConfigureMegamorphic(IcCheckType property_type) {
DisallowHeapAllocation no_gc;
Isolate* isolate = GetIsolate();
......@@ -661,6 +680,23 @@ InlineCacheState FeedbackNexus::StateFromFeedback() const {
return MONOMORPHIC;
}
case FeedbackSlotKind::kCloneObject: {
if (feedback == MaybeObject::FromObject(
*FeedbackVector::UninitializedSentinel(isolate))) {
return UNINITIALIZED;
}
if (feedback == MaybeObject::FromObject(
*FeedbackVector::MegamorphicSentinel(isolate))) {
return MEGAMORPHIC;
}
if (feedback->IsWeakOrClearedHeapObject()) {
return MONOMORPHIC;
}
DCHECK(feedback->ToStrongHeapObject()->IsWeakFixedArray());
return POLYMORPHIC;
}
case FeedbackSlotKind::kInvalid:
case FeedbackSlotKind::kKindsNumber:
UNREACHABLE();
......@@ -703,6 +739,78 @@ void FeedbackNexus::ConfigureHandlerMode(const MaybeObjectHandle& handler) {
SetFeedbackExtra(*handler);
}
void FeedbackNexus::ConfigureCloneObject(Handle<Map> source_map,
Handle<Map> result_map) {
Isolate* isolate = GetIsolate();
MaybeObject* maybe_feedback = GetFeedback();
Handle<HeapObject> feedback(maybe_feedback->IsStrongOrWeakHeapObject()
? maybe_feedback->GetHeapObject()
: nullptr,
isolate);
switch (ic_state()) {
case UNINITIALIZED:
// Cache the first map seen which meets the fast case requirements.
SetFeedback(HeapObjectReference::Weak(*source_map));
SetFeedbackExtra(*result_map);
break;
case MONOMORPHIC:
if (maybe_feedback->IsClearedWeakHeapObject()) {
// Remain in MONOMORPHIC state if previous feedback has been collected.
SetFeedback(HeapObjectReference::Weak(*source_map));
SetFeedbackExtra(*result_map);
} else {
// Transition to POLYMORPHIC.
DCHECK_NE(*source_map, *feedback);
Handle<WeakFixedArray> array =
EnsureArrayOfSize(2 * kCloneObjectPolymorphicEntrySize);
array->Set(0, maybe_feedback);
array->Set(1, GetFeedbackExtra());
array->Set(2, HeapObjectReference::Weak(*source_map));
array->Set(3, MaybeObject::FromObject(*result_map));
SetFeedbackExtra(HeapObjectReference::ClearedValue());
}
break;
case POLYMORPHIC: {
static constexpr int kMaxElements =
IC::kMaxPolymorphicMapCount * kCloneObjectPolymorphicEntrySize;
Handle<WeakFixedArray> array = Handle<WeakFixedArray>::cast(feedback);
int i = 0;
for (; i < array->length(); i += kCloneObjectPolymorphicEntrySize) {
if (array->Get(i)->IsClearedWeakHeapObject()) {
break;
}
DCHECK_NE(array->Get(i)->GetHeapObject(), *source_map);
}
if (i >= array->length()) {
if (i == kMaxElements) {
// Transition to MEGAMORPHIC.
MaybeObject* sentinel = MaybeObject::FromObject(
*FeedbackVector::MegamorphicSentinel(isolate));
SetFeedback(sentinel, SKIP_WRITE_BARRIER);
SetFeedbackExtra(HeapObjectReference::ClearedValue());
break;
}
// Grow polymorphic feedback array.
Handle<WeakFixedArray> new_array = EnsureArrayOfSize(
array->length() + kCloneObjectPolymorphicEntrySize);
for (int j = 0; j < array->length(); ++j) {
new_array->Set(j, array->Get(j));
}
array = new_array;
}
array->Set(i, HeapObjectReference::Weak(*source_map));
array->Set(i + 1, MaybeObject::FromObject(*result_map));
break;
}
default:
UNREACHABLE();
}
}
int FeedbackNexus::GetCallCount() {
DCHECK(IsCallICKind(kind()));
......
......@@ -51,6 +51,7 @@ enum class FeedbackSlotKind {
kLiteral,
kForIn,
kInstanceOf,
kCloneObject,
kKindsNumber // Last value indicating number of kinds.
};
......@@ -107,6 +108,10 @@ inline bool IsTypeProfileKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kTypeProfile;
}
inline bool IsCloneObjectKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kCloneObject;
}
inline TypeofMode GetTypeofModeFromSlotKind(FeedbackSlotKind kind) {
DCHECK(IsLoadGlobalICKind(kind));
return (kind == FeedbackSlotKind::kLoadGlobalInsideTypeof)
......@@ -398,6 +403,10 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec {
FeedbackSlot AddTypeProfileSlot();
FeedbackSlot AddCloneObjectSlot() {
return AddSlot(FeedbackSlotKind::kCloneObject);
}
#ifdef OBJECT_PRINT
// For gdb debugging.
void Print();
......@@ -602,6 +611,9 @@ class FeedbackNexus final {
bool Clear();
void ConfigureUninitialized();
void ConfigurePremonomorphic();
// ConfigureMegamorphic() returns true if the state of the underlying vector
// was changed. Extra feedback is cleared if the 0 parameter version is used.
bool ConfigureMegamorphic();
bool ConfigureMegamorphic(IcCheckType property_type);
inline MaybeObject* GetFeedback() const;
......@@ -654,6 +666,10 @@ class FeedbackNexus final {
int context_slot_index);
void ConfigureHandlerMode(const MaybeObjectHandle& handler);
// For CloneObject ICs
static constexpr int kCloneObjectPolymorphicEntrySize = 2;
void ConfigureCloneObject(Handle<Map> source_map, Handle<Map> result_map);
// Bit positions in a smi that encodes lexical environment variable access.
#define LEXICAL_MODE_BIT_FIELDS(V, _) \
V(ContextIndexBits, unsigned, 12, _) \
......@@ -676,7 +692,6 @@ class FeedbackNexus final {
std::vector<int> GetSourcePositions() const;
std::vector<Handle<String>> GetTypesForSourcePositions(uint32_t pos) const;
protected:
inline void SetFeedback(Object* feedback,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
inline void SetFeedback(MaybeObject* feedback,
......
......@@ -3391,5 +3391,128 @@ void AccessorAssembler::GenerateStoreInArrayLiteralIC() {
StoreInArrayLiteralIC(&p);
}
void AccessorAssembler::GenerateCloneObjectIC() {
typedef CloneObjectWithVectorDescriptor Descriptor;
Node* source = Parameter(Descriptor::kSource);
Node* flags = Parameter(Descriptor::kFlags);
Node* slot = Parameter(Descriptor::kSlot);
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler);
Label miss(this, Label::kDeferred), try_polymorphic(this, Label::kDeferred),
try_megamorphic(this, Label::kDeferred);
CSA_SLOW_ASSERT(this, TaggedIsNotSmi(source));
Node* source_map = LoadMap(UncheckedCast<HeapObject>(source));
GotoIf(IsDeprecatedMap(source_map), &miss);
TNode<MaybeObject> feedback = TryMonomorphicCase(
slot, vector, source_map, &if_handler, &var_handler, &try_polymorphic);
BIND(&if_handler);
{
Comment("CloneObjectIC_if_handler");
// Handlers for the CloneObjectIC stub are weak references to the Map of
// a result object.
TNode<Map> result_map = CAST(var_handler.value());
TVARIABLE(Object, var_properties, EmptyFixedArrayConstant());
TVARIABLE(FixedArrayBase, var_elements, EmptyFixedArrayConstant());
Label allocate_object(this);
GotoIf(IsNullOrUndefined(source), &allocate_object);
CSA_SLOW_ASSERT(this, IsJSObjectMap(result_map));
// The IC fast case should only be taken if the result map a compatible
// elements kind with the source object.
TNode<FixedArrayBase> source_elements = LoadElements(source);
auto flags = ExtractFixedArrayFlag::kAllFixedArraysDontCopyCOW;
var_elements = CAST(CloneFixedArray(source_elements, flags));
// Copy the PropertyArray backing store. The source PropertyArray must be
// either an Smi, or a PropertyArray.
// FIXME: Make a CSA macro for this
TNode<Object> source_properties =
LoadObjectField(source, JSObject::kPropertiesOrHashOffset);
{
GotoIf(TaggedIsSmi(source_properties), &allocate_object);
GotoIf(IsEmptyFixedArray(source_properties), &allocate_object);
// This IC requires that the source object has fast properties
CSA_SLOW_ASSERT(this, IsPropertyArray(CAST(source_properties)));
TNode<IntPtrT> length = LoadPropertyArrayLength(
UncheckedCast<PropertyArray>(source_properties));
GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &allocate_object);
auto mode = INTPTR_PARAMETERS;
var_properties = CAST(AllocatePropertyArray(length, mode));
CopyPropertyArrayValues(source_properties, var_properties.value(), length,
SKIP_WRITE_BARRIER, mode);
}
Goto(&allocate_object);
BIND(&allocate_object);
TNode<JSObject> object = UncheckedCast<JSObject>(AllocateJSObjectFromMap(
result_map, var_properties.value(), var_elements.value()));
ReturnIf(IsNullOrUndefined(source), object);
// Lastly, clone any in-object properties.
// Determine the inobject property capacity of both objects, and copy the
// smaller number into the resulting object.
Node* source_start = LoadMapInobjectPropertiesStartInWords(source_map);
Node* source_size = LoadMapInstanceSizeInWords(source_map);
Node* result_start = LoadMapInobjectPropertiesStartInWords(result_map);
Node* field_offset_difference =
TimesPointerSize(IntPtrSub(result_start, source_start));
BuildFastLoop(source_start, source_size,
[=](Node* field_index) {
Node* field_offset = TimesPointerSize(field_index);
Node* field = LoadObjectField(source, field_offset);
Node* result_offset =
IntPtrAdd(field_offset, field_offset_difference);
StoreObjectFieldNoWriteBarrier(object, result_offset,
field);
},
1, INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
Return(object);
}
BIND(&try_polymorphic);
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
{
Comment("CloneObjectIC_try_polymorphic");
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic);
HandlePolymorphicCase(source_map, CAST(strong_feedback), &if_handler,
&var_handler, &miss, 2);
}
BIND(&try_megamorphic);
{
Comment("CloneObjectIC_try_megamorphic");
CSA_ASSERT(
this,
Word32Or(WordEqual(strong_feedback,
LoadRoot(Heap::kuninitialized_symbolRootIndex)),
WordEqual(strong_feedback,
LoadRoot(Heap::kmegamorphic_symbolRootIndex))));
GotoIfNot(WordEqual(strong_feedback,
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
&miss);
TailCallRuntime(Runtime::kCloneObjectIC_Slow, context, source, flags);
}
BIND(&miss);
{
Comment("CloneObjectIC_miss");
Node* map_or_result = CallRuntime(Runtime::kCloneObjectIC_Miss, context,
source, flags, slot, vector);
var_handler = UncheckedCast<MaybeObject>(map_or_result);
GotoIf(IsMap(map_or_result), &if_handler);
CSA_ASSERT(this, IsJSObject(map_or_result));
Return(map_or_result);
}
}
} // namespace internal
} // namespace v8
......@@ -39,6 +39,7 @@ class AccessorAssembler : public CodeStubAssembler {
void GenerateStoreICTrampoline();
void GenerateStoreGlobalIC();
void GenerateStoreGlobalICTrampoline();
void GenerateCloneObjectIC();
void GenerateLoadGlobalIC(TypeofMode typeof_mode);
void GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode);
......
......@@ -8,6 +8,7 @@
#include "src/api-arguments-inl.h"
#include "src/api.h"
#include "src/arguments.h"
#include "src/ast/ast.h"
#include "src/base/bits.h"
#include "src/conversions.h"
#include "src/execution.h"
......@@ -2446,6 +2447,132 @@ RUNTIME_FUNCTION(Runtime_ElementsTransitionAndStoreIC_Miss) {
}
}
static bool CanFastCloneObject(Handle<Map> map) {
DisallowHeapAllocation no_gc;
if (map->IsNullOrUndefinedMap()) return true;
if (!map->IsJSObjectMap() ||
!IsSmiOrObjectElementsKind(map->elements_kind()) ||
!map->OnlyHasSimpleProperties()) {
return false;
}
DescriptorArray* descriptors = map->instance_descriptors();
for (int i = 0; i < map->NumberOfOwnDescriptors(); i++) {
PropertyDetails details = descriptors->GetDetails(i);
Name* key = descriptors->GetKey(i);
if (details.kind() != kData || !details.IsEnumerable() ||
key->IsPrivateField()) {
return false;
}
}
return true;
}
static Handle<Map> FastCloneObjectMap(Isolate* isolate,
Handle<HeapObject> source, int flags) {
Handle<Map> source_map(source->map(), isolate);
SLOW_DCHECK(source->IsNullOrUndefined() || CanFastCloneObject(source_map));
Handle<JSFunction> constructor(isolate->native_context()->object_function(),
isolate);
DCHECK(constructor->has_initial_map());
Handle<Map> initial_map(constructor->initial_map(), isolate);
Handle<Map> map = initial_map;
if (source_map->IsJSObjectMap() && source_map->GetInObjectProperties() !=
initial_map->GetInObjectProperties()) {
int inobject_properties = source_map->GetInObjectProperties();
int instance_size =
JSObject::kHeaderSize + kPointerSize * inobject_properties;
int unused = source_map->UnusedInObjectProperties();
DCHECK(instance_size <= JSObject::kMaxInstanceSize);
map = Map::CopyInitialMap(isolate, map, instance_size, inobject_properties,
unused);
}
if (flags & ObjectLiteral::kHasNullPrototype) {
if (map.is_identical_to(initial_map)) {
map = Map::Copy(isolate, map, "ObjectWithNullProto");
}
Map::SetPrototype(isolate, map, isolate->factory()->null_value());
}
if (source->IsNullOrUndefined() || !source_map->NumberOfOwnDescriptors()) {
return map;
}
if (map.is_identical_to(initial_map)) {
map = Map::Copy(isolate, map, "InitializeClonedDescriptors");
}
Handle<DescriptorArray> source_descriptors(source_map->instance_descriptors(),
isolate);
int size = source_map->NumberOfOwnDescriptors();
int slack = 0;
Handle<DescriptorArray> descriptors = DescriptorArray::CopyForFastObjectClone(
isolate, source_descriptors, size, slack);
Handle<LayoutDescriptor> layout =
LayoutDescriptor::New(isolate, map, descriptors, size);
map->InitializeDescriptors(*descriptors, *layout);
map->CopyUnusedPropertyFields(*source_map);
return map;
}
static MaybeHandle<JSObject> CloneObjectSlowPath(Isolate* isolate,
Handle<HeapObject> source,
int flags) {
Handle<JSObject> new_object;
if (flags & ObjectLiteral::kHasNullPrototype) {
new_object = isolate->factory()->NewJSObjectWithNullProto();
} else {
Handle<JSFunction> constructor(isolate->native_context()->object_function(),
isolate);
new_object = isolate->factory()->NewJSObject(constructor);
}
if (source->IsNullOrUndefined()) {
return new_object;
}
MAYBE_RETURN(JSReceiver::SetOrCopyDataProperties(isolate, new_object, source,
nullptr, false),
MaybeHandle<JSObject>());
return new_object;
}
RUNTIME_FUNCTION(Runtime_CloneObjectIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Handle<HeapObject> source = args.at<HeapObject>(0);
int flags = args.smi_at(1);
FeedbackSlot slot = FeedbackVector::ToSlot(args.smi_at(2));
Handle<FeedbackVector> vector = args.at<FeedbackVector>(3);
FeedbackNexus nexus(vector, slot);
Handle<Map> source_map(source->map(), isolate);
if (!CanFastCloneObject(source_map) || nexus.IsMegamorphic()) {
// Migrate to slow mode if needed.
nexus.ConfigureMegamorphic();
RETURN_RESULT_OR_FAILURE(isolate,
CloneObjectSlowPath(isolate, source, flags));
}
Handle<Map> result_map = FastCloneObjectMap(isolate, source, flags);
nexus.ConfigureCloneObject(source_map, result_map);
return *result_map;
}
RUNTIME_FUNCTION(Runtime_CloneObjectIC_Slow) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
Handle<JSReceiver> source = args.at<JSReceiver>(0);
int flags = args.smi_at(1);
RETURN_RESULT_OR_FAILURE(isolate,
CloneObjectSlowPath(isolate, source, flags));
}
RUNTIME_FUNCTION(Runtime_StoreCallbackProperty) {
Handle<JSObject> receiver = args.at<JSObject>(0);
......
......@@ -291,5 +291,10 @@ void WasmGrowMemoryDescriptor::InitializePlatformSpecific(
DefaultInitializePlatformSpecific(data, kParameterCount);
}
void CloneObjectWithVectorDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
} // namespace internal
} // namespace v8
......@@ -74,6 +74,7 @@ namespace internal {
V(FrameDropperTrampoline) \
V(RunMicrotasks) \
V(WasmGrowMemory) \
V(CloneObjectWithVector) \
BUILTIN_LIST_TFS(V)
class V8_EXPORT_PRIVATE CallInterfaceDescriptorData {
......@@ -1021,6 +1022,17 @@ class WasmGrowMemoryDescriptor final : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(WasmGrowMemoryDescriptor, CallInterfaceDescriptor)
};
class CloneObjectWithVectorDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kSource, kFlags, kSlot, kVector)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::TaggedPointer(), // result 1
MachineType::AnyTagged(), // kSource
MachineType::TaggedSigned(), // kFlags
MachineType::TaggedSigned(), // kSlot
MachineType::AnyTagged()) // kVector
DECLARE_DESCRIPTOR(CloneObjectWithVectorDescriptor, CallInterfaceDescriptor)
};
#define DEFINE_TFS_BUILTIN_DESCRIPTOR(Name, ...) \
class Name##Descriptor : public CallInterfaceDescriptor { \
public: \
......
......@@ -986,6 +986,13 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CreateEmptyObjectLiteral() {
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CloneObject(Register source,
int flags,
int feedback_slot) {
OutputCloneObject(source, flags, feedback_slot);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::GetTemplateObject(
size_t template_object_description_entry, int feedback_slot) {
OutputGetTemplateObject(template_object_description_entry, feedback_slot);
......
......@@ -238,6 +238,8 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final {
int literal_index, int flags,
Register output);
BytecodeArrayBuilder& CreateEmptyObjectLiteral();
BytecodeArrayBuilder& CloneObject(Register source, int flags,
int feedback_slot);
// Gets or creates the template for a TemplateObjectDescription which will
// be inserted at constant pool index |template_object_description_entry|.
......
......@@ -2108,42 +2108,70 @@ void BytecodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
return;
}
int literal_index = feedback_index(feedback_spec()->AddLiteralSlot());
// Deep-copy the literal boilerplate.
uint8_t flags = CreateObjectLiteralFlags::Encode(
expr->ComputeFlags(), expr->IsFastCloningSupported());
Register literal = register_allocator()->NewRegister();
size_t entry;
// If constant properties is an empty fixed array, use a cached empty fixed
// array to ensure it's only added to the constant pool once.
if (expr->properties_count() == 0) {
entry = builder()->EmptyObjectBoilerplateDescriptionConstantPoolEntry();
// Create literal object.
int property_index = 0;
bool is_spread =
expr->properties()->first()->kind() == ObjectLiteral::Property::SPREAD;
if (is_spread) {
// Avoid the slow path for spreads in the following common cases:
// 1) `let obj = { ...source }`
// 2) `let obj = { ...source, override: 1 }`
// 3) `let obj = { ...source, ...overrides }`
RegisterAllocationScope register_scope(this);
Expression* property = expr->properties()->first()->value();
Register from_value = VisitForRegisterValue(property);
BytecodeLabels clone_object(zone());
builder()->JumpIfUndefined(clone_object.New());
builder()->JumpIfNull(clone_object.New());
builder()->ToObject(from_value);
clone_object.Bind(builder());
int clone_index = feedback_index(feedback_spec()->AddCloneObjectSlot());
builder()->CloneObject(from_value, flags, clone_index);
builder()->StoreAccumulatorInRegister(literal);
property_index++;
// FIXME: incorporate compile-time constants following the initial spread
// into the CloneObject opcode, to be included in the final value.
} else {
entry = builder()->AllocateDeferredConstantPoolEntry();
object_literals_.push_back(std::make_pair(expr, entry));
size_t entry;
// If constant properties is an empty fixed array, use a cached empty fixed
// array to ensure it's only added to the constant pool once.
if (expr->properties_count() == 0) {
entry = builder()->EmptyObjectBoilerplateDescriptionConstantPoolEntry();
} else {
entry = builder()->AllocateDeferredConstantPoolEntry();
object_literals_.push_back(std::make_pair(expr, entry));
}
// TODO(cbruni): Directly generate runtime call for literals we cannot
// optimize once the CreateShallowObjectLiteral stub is in sync with the TF
// optimizations.
int literal_index = feedback_index(feedback_spec()->AddLiteralSlot());
builder()->CreateObjectLiteral(entry, literal_index, flags, literal);
}
// TODO(cbruni): Directly generate runtime call for literals we cannot
// optimize once the CreateShallowObjectLiteral stub is in sync with the TF
// optimizations.
builder()->CreateObjectLiteral(entry, literal_index, flags, literal);
// Store computed values into the literal.
int property_index = 0;
AccessorTable accessor_table(zone());
for (; property_index < expr->properties()->length(); property_index++) {
ObjectLiteral::Property* property = expr->properties()->at(property_index);
if (property->is_computed_name()) break;
if (property->IsCompileTimeValue()) continue;
if (!is_spread && property->IsCompileTimeValue()) continue;
RegisterAllocationScope inner_register_scope(this);
Literal* key = property->key()->AsLiteral();
switch (property->kind()) {
case ObjectLiteral::Property::SPREAD:
case ObjectLiteral::Property::CONSTANT:
UNREACHABLE();
case ObjectLiteral::Property::CONSTANT:
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
DCHECK(!property->value()->IsCompileTimeValue());
DCHECK(is_spread || !property->value()->IsCompileTimeValue());
V8_FALLTHROUGH;
case ObjectLiteral::Property::COMPUTED: {
// It is safe to use [[Put]] here because the boilerplate already
......
......@@ -251,6 +251,8 @@ namespace interpreter {
V(CreateObjectLiteral, AccumulatorUse::kNone, OperandType::kIdx, \
OperandType::kIdx, OperandType::kFlag8, OperandType::kRegOut) \
V(CreateEmptyObjectLiteral, AccumulatorUse::kWrite) \
V(CloneObject, AccumulatorUse::kWrite, OperandType::kReg, \
OperandType::kFlag8, OperandType::kIdx) \
\
/* Tagged templates */ \
V(GetTemplateObject, AccumulatorUse::kWrite, OperandType::kIdx, \
......
......@@ -2437,6 +2437,26 @@ IGNITION_HANDLER(CreateEmptyObjectLiteral, InterpreterAssembler) {
Dispatch();
}
// CloneObject <source_idx> <flags> <feedback_slot>
//
// Allocates a new JSObject with each enumerable own property copied from
// {source}, converting getters into data properties.
IGNITION_HANDLER(CloneObject, InterpreterAssembler) {
Node* source = LoadRegisterAtOperandIndex(0);
Node* bytecode_flags = BytecodeOperandFlag(1);
Node* raw_flags =
DecodeWordFromWord32<CreateObjectLiteralFlags::FlagsBits>(bytecode_flags);
Node* smi_flags = SmiTag(raw_flags);
Node* raw_slot = BytecodeOperandIdx(2);
Node* smi_slot = SmiTag(raw_slot);
Node* feedback_vector = LoadFeedbackVector();
Node* context = GetContext();
Node* result = CallBuiltin(Builtins::kCloneObjectIC, context, source,
smi_flags, smi_slot, feedback_vector);
SetAccumulator(result);
Dispatch();
}
// GetTemplateObject <descriptor_idx> <literal_idx>
//
// Creates the template to pass for tagged templates and returns it in the
......
......@@ -1096,7 +1096,8 @@ void FeedbackNexus::Print(std::ostream& os) { // NOLINT
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kStoreDataPropertyInLiteral:
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral: {
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kCloneObject: {
os << ICState2String(StateFromFeedback());
break;
}
......
......@@ -10179,6 +10179,38 @@ Handle<DescriptorArray> DescriptorArray::CopyUpToAddAttributes(
return descriptors;
}
// Create a new descriptor array with only enumerable, configurable, writeable
// data properties, but identical field locations.
Handle<DescriptorArray> DescriptorArray::CopyForFastObjectClone(
Isolate* isolate, Handle<DescriptorArray> src, int enumeration_index,
int slack) {
if (enumeration_index + slack == 0) {
return isolate->factory()->empty_descriptor_array();
}
int size = enumeration_index;
Handle<DescriptorArray> descriptors =
DescriptorArray::Allocate(isolate, size, slack);
for (int i = 0; i < size; ++i) {
Name* key = src->GetKey(i);
PropertyDetails details = src->GetDetails(i);
SLOW_DCHECK(!key->IsPrivateField() && details.IsEnumerable() &&
details.kind() == kData);
// Ensure the ObjectClone property details are NONE, and that all source
// details did not contain DONT_ENUM.
PropertyDetails new_details(
kData, NONE, details.location(), kDefaultFieldConstness,
details.representation(), details.field_index());
descriptors->Set(i, key, src->GetValue(i), new_details);
}
descriptors->Sort();
return descriptors;
}
bool DescriptorArray::IsEqualUpTo(DescriptorArray* desc, int nof_descriptors) {
for (int i = 0; i < nof_descriptors; i++) {
......
......@@ -107,6 +107,10 @@ class DescriptorArray : public WeakFixedArray {
Isolate* isolate, Handle<DescriptorArray> desc, int enumeration_index,
PropertyAttributes attributes, int slack = 0);
static Handle<DescriptorArray> CopyForFastObjectClone(
Isolate* isolate, Handle<DescriptorArray> desc, int enumeration_index,
int slack = 0);
// Sort the instance descriptors by the hash codes of their keys.
void Sort();
......
......@@ -298,6 +298,17 @@ int Map::UnusedPropertyFields() const {
return unused;
}
int Map::UnusedInObjectProperties() const {
// Like Map::UnusedPropertyFields(), but returns 0 for out of object
// properties.
int value = used_or_unused_instance_size_in_words();
DCHECK_IMPLIES(!IsJSObjectMap(), value == 0);
if (value >= JSObject::kFieldsAdded) {
return instance_size_in_words() - value;
}
return 0;
}
int Map::used_or_unused_instance_size_in_words() const {
return RELAXED_READ_BYTE_FIELD(this, kUsedOrUnusedInstanceSizeInWordsOffset);
}
......@@ -503,6 +514,17 @@ bool Map::CanTransition() const {
bool Map::IsBooleanMap() const {
return this == GetReadOnlyRoots().boolean_map();
}
bool Map::IsNullMap() const { return this == GetReadOnlyRoots().null_map(); }
bool Map::IsUndefinedMap() const {
return this == GetReadOnlyRoots().undefined_map();
}
bool Map::IsNullOrUndefinedMap() const {
return IsNullMap() || IsUndefinedMap();
}
bool Map::IsPrimitiveMap() const {
return instance_type() <= LAST_PRIMITIVE_TYPE;
}
......
......@@ -212,6 +212,8 @@ class Map : public HeapObject {
// Tells how many unused property fields (in-object or out-of object) are
// available in the instance (only used for JSObject in fast mode).
inline int UnusedPropertyFields() const;
// Tells how many unused in-object property words are present.
inline int UnusedInObjectProperties() const;
// Updates the counters tracking unused fields in the object.
inline void SetInObjectUnusedPropertyFields(int unused_property_fields);
// Updates the counters tracking unused fields in the property array.
......@@ -759,6 +761,9 @@ class Map : public HeapObject {
inline bool CanTransition() const;
inline bool IsBooleanMap() const;
inline bool IsNullMap() const;
inline bool IsUndefinedMap() const;
inline bool IsNullOrUndefinedMap() const;
inline bool IsPrimitiveMap() const;
inline bool IsJSReceiverMap() const;
inline bool IsJSObjectMap() const;
......
......@@ -588,7 +588,9 @@ namespace internal {
F(StoreGlobalIC_Slow, 5, 1) \
F(StoreIC_Miss, 5, 1) \
F(StoreInArrayLiteralIC_Slow, 5, 1) \
F(StorePropertyWithInterceptor, 5, 1)
F(StorePropertyWithInterceptor, 5, 1) \
F(CloneObjectIC_Miss, 4, 1) \
F(CloneObjectIC_Slow, 2, 1)
#define FOR_EACH_INTRINSIC_RETURN_OBJECT(F) \
FOR_EACH_INTRINSIC_ARRAY(F) \
......
// Copyright 2018 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.
(function testDoubleElements() {
function f(src) { return {...src}; }
var src = [1.5];
src[0] = 1;
// Uninitialized
assertEquals({ 0: 1 }, f(src));
src[0] = 1.3;
// Monomorphic
assertEquals({ 0: 1.3 }, f(src));
})();
(function testInObjectProperties() {
function f(src) { return {...src}; }
function C() { this.foo = "foo"; }
var src;
for (var i = 0; i < 10; ++i) {
src = new C();
}
// Uninitialized
assertEquals({ foo: "foo" }, f(src));
// Monomorphic
assertEquals({ foo: "foo" }, f(src));
})();
(function testInObjectProperties2() {
function f(src) { return {...src}; }
function C() {
this.foo = "foo";
this.p0 = "0";
this.p1 = "1";
this.p2 = "2";
this.p3 = "3";
}
var src;
for (var i = 0; i < 10; ++i) {
src = new C();
}
// Uninitialized
assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(src));
// Monomorphic
assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(src));
})();
(function testPolymorphicToMegamorphic() {
function f(src) { return {...src}; }
function C1() {
this.foo = "foo";
this.p0 = "0";
this.p1 = "1";
this.p2 = "2";
this.p3 = "3";
}
function C2() {
this.p0 = "0";
this.p1 = "1";
this[0] = 0;
}
function C3() {
this.x = 774;
this.y = 663;
this.rgb = 0xFF00FF;
}
function C4() {
this.qqq = {};
this.v_1 = [];
this.name = "C4";
this.constructor = C4;
}
// Uninitialized
assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(new C1()));
// Monomorphic
assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(new C1()));
// Polymorphic (2)
assertEquals({ 0: 0, p0: "0", p1: "1" }, f(new C2()));
assertEquals({ 0: 0, p0: "0", p1: "1" }, f(new C2()));
// Polymorphic (3)
assertEquals({ x: 774, y: 663, rgb: 0xFF00FF }, f(new C3()));
assertEquals({ x: 774, y: 663, rgb: 0xFF00FF }, f(new C3()));
// Polymorphic (4)
assertEquals({ qqq: {}, v_1: [], name: "C4", constructor: C4 }, f(new C4()));
assertEquals({ qqq: {}, v_1: [], name: "C4", constructor: C4 }, f(new C4()));
// Megamorphic
assertEquals({ boop: 1 }, f({ boop: 1 }));
})();
......@@ -376,7 +376,8 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.CreateArrayLiteral(0, 0, 0)
.CreateEmptyArrayLiteral(0)
.CreateObjectLiteral(0, 0, 0, reg)
.CreateEmptyObjectLiteral();
.CreateEmptyObjectLiteral()
.CloneObject(reg, 0, 0);
// Emit load and store operations for module variables.
builder.LoadModuleVariable(-1, 42)
......
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