Skip to content

Commit

Permalink
[runtime] use new CloneObject bytecode for some ObjectLiteralSpread c…
Browse files Browse the repository at this point in the history
…ases

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: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54595}
  • Loading branch information
caitp authored and Commit Bot committed Jul 20, 2018
1 parent 8fb0db9 commit b6f7ea5
Show file tree
Hide file tree
Showing 34 changed files with 744 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/builtins/builtins-definitions.h
Expand Up @@ -639,6 +639,7 @@ namespace internal {
TFH(LoadGlobalICInsideTypeof, LoadGlobalWithVector) \
TFH(LoadGlobalICTrampoline, LoadGlobal) \
TFH(LoadGlobalICInsideTypeofTrampoline, LoadGlobal) \
TFH(CloneObjectIC, CloneObjectWithVector) \
\
/* Map */ \
TFS(FindOrderedHashMapEntry, kTable, kKey) \
Expand Down
1 change: 1 addition & 0 deletions src/builtins/builtins-ic-gen.cc
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/code-stub-assembler.cc
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/code-stub-assembler.h
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/bytecode-graph-builder.cc
Expand Up @@ -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(
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/bytecode-graph-builder.h
Expand Up @@ -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()); }
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/js-generic-lowering.cc
Expand Up @@ -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.
}
Expand Down
34 changes: 34 additions & 0 deletions src/compiler/js-operator.cc
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/compiler/js-operator.h
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/opcodes.h
Expand Up @@ -152,6 +152,7 @@
V(JSCreateEmptyLiteralArray) \
V(JSCreateLiteralObject) \
V(JSCreateEmptyLiteralObject) \
V(JSCloneObject) \
V(JSCreateLiteralRegExp)

#define JS_OBJECT_OP_LIST(V) \
Expand Down
1 change: 1 addition & 0 deletions src/compiler/operator-properties.cc
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/typer.cc
Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/verifier.cc
Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions src/feedback-vector-inl.h
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
108 changes: 108 additions & 0 deletions src/feedback-vector.cc
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -416,6 +419,7 @@ void FeedbackNexus::ConfigureUninitialized() {
SKIP_WRITE_BARRIER);
break;
}
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kCall: {
SetFeedback(*FeedbackVector::UninitializedSentinel(isolate),
SKIP_WRITE_BARRIER);
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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()));

Expand Down

0 comments on commit b6f7ea5

Please sign in to comment.