-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
optimize object.assign({}, obj) #4817
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,8 +58,8 @@ namespace Js | |
int propertyCount = typeHandler->GetPropertyCount(); | ||
int inlineSlotCapacity = GetTypeHandler()->GetInlineSlotCapacity(); | ||
int inlineSlotCount = min(inlineSlotCapacity, propertyCount); | ||
Var * srcSlots = reinterpret_cast<Var*>(reinterpret_cast<size_t>(instance) + typeHandler->GetOffsetOfInlineSlots()); | ||
Field(Var) * dstSlots = reinterpret_cast<Field(Var)*>(reinterpret_cast<size_t>(this) + typeHandler->GetOffsetOfInlineSlots()); | ||
Field(Var)* srcSlots = instance->GetInlineSlots(); | ||
Field(Var)* dstSlots = this->GetInlineSlots(); | ||
#if !FLOATVAR | ||
ScriptContext * scriptContext = this->GetScriptContext(); | ||
#endif | ||
|
@@ -755,6 +755,94 @@ namespace Js | |
} | ||
} | ||
|
||
Field(Var)* DynamicObject::GetInlineSlots() const | ||
{ | ||
return reinterpret_cast<Field(Var)*>(reinterpret_cast<size_t>(this) + this->GetOffsetOfInlineSlots()); | ||
} | ||
|
||
bool DynamicObject::TryCopy(DynamicObject* from) | ||
{ | ||
// Validate that objects are compatible | ||
if (this->GetTypeHandler()->GetInlineSlotCapacity() != from->GetTypeHandler()->GetInlineSlotCapacity()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth extracting the compatibility checks to its own function? I'm wondering if it would be beneficial for the JIT to be able to generate a fast path for object.assign({}, obj) - in which case, as a simplification, it could call this function as a helper? |
||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: inline slot capacity doesn't match, from: %u, to: %u\n"), | ||
from->GetTypeHandler()->GetInlineSlotCapacity(), | ||
this->GetTypeHandler()->GetInlineSlotCapacity()); | ||
} | ||
return false; | ||
} | ||
if (!from->GetTypeHandler()->IsPathTypeHandler()) | ||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: Don't have PathTypeHandler\n")); | ||
} | ||
return false; | ||
} | ||
if (PathTypeHandlerBase::FromTypeHandler(from->GetTypeHandler())->HasAccessors()) | ||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: type handler has accessors\n")); | ||
} | ||
return false; | ||
} | ||
if (this->GetPrototype() != from->GetPrototype()) | ||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: Protoytypes don't match\n")); | ||
} | ||
return false; | ||
} | ||
if (!from->GetTypeHandler()->AllPropertiesAreEnumerable()) | ||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: from obj has non-enumerable properties\n")); | ||
} | ||
return false; | ||
} | ||
|
||
// Share the type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth adding a comment here that ShareType has side effects so it should be done last #Resolved |
||
if (!from->GetDynamicType()->ShareType()) | ||
{ | ||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy: Can't copy: failed to share type\n")); | ||
} | ||
return false; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add test cases for these ifs and boundary cases for aux and inline slot capacities? #Resolved |
||
|
||
// Update this object | ||
this->ReplaceType(from->GetDynamicType()); | ||
this->InitSlots(this); | ||
const int slotCapacity = this->GetTypeHandler()->GetSlotCapacity(); | ||
const uint16 inlineSlotCapacity = this->GetTypeHandler()->GetInlineSlotCapacity(); | ||
const int auxSlotCapacity = slotCapacity - inlineSlotCapacity; | ||
|
||
if (auxSlotCapacity > 0) | ||
{ | ||
CopyArray(this->auxSlots, auxSlotCapacity, from->auxSlots, auxSlotCapacity); | ||
} | ||
if (inlineSlotCapacity != 0) | ||
{ | ||
Field(Var)* thisInlineSlots = this->GetInlineSlots(); | ||
Field(Var)* fromInlineSlots = from->GetInlineSlots(); | ||
|
||
CopyArray(thisInlineSlots, inlineSlotCapacity, fromInlineSlots, inlineSlotCapacity); | ||
} | ||
|
||
if (PHASE_TRACE1(ObjectCopyPhase)) | ||
{ | ||
Output::Print(_u("ObjectCopy succeeded\n")); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
bool | ||
DynamicObject::GetHasNoEnumerableProperties() | ||
{ | ||
|
@@ -892,10 +980,9 @@ namespace Js | |
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<void*, TTD::NSSnapObjects::SnapObjectType::SnapDynamicObject>(objData, nullptr); | ||
} | ||
|
||
Js::Var const* DynamicObject::GetInlineSlots_TTD() const | ||
Field(Js::Var) const* DynamicObject::GetInlineSlots_TTD() const | ||
{ | ||
return reinterpret_cast<Var const*>( | ||
reinterpret_cast<size_t>(this) + this->GetTypeHandler()->GetOffsetOfInlineSlots()); | ||
return this->GetInlineSlots(); | ||
} | ||
|
||
Js::Var const* DynamicObject::GetAuxSlots_TTD() const | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
ObjectCopy succeeded | ||
ObjectCopy: Can't copy: Don't have PathTypeHandler | ||
ObjectCopy: Can't copy: type handler has accessors | ||
ObjectCopy: Can't copy: type handler has accessors | ||
ObjectCopy: Can't copy: Protoytypes don't match | ||
ObjectCopy: Can't copy: from obj has non-enumerable properties | ||
ObjectCopy succeeded | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
//------------------------------------------------------------------------------------------------------- | ||
// Copyright (C) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. | ||
//------------------------------------------------------------------------------------------------------- | ||
|
||
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); | ||
|
||
var tests = [ | ||
{ | ||
name: "simple copy", | ||
body: function () | ||
{ | ||
let orig = {}; | ||
orig.a = 1; | ||
orig.b = "asdf"; | ||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, orig.b); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "non-path type handler", | ||
body: function () | ||
{ | ||
let orig = {}; | ||
orig.a = 1; | ||
orig.b = "asdf"; | ||
delete orig.a; | ||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, orig.b); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "has getter", | ||
body: function () | ||
{ | ||
let orig = {}; | ||
orig.a = 1; | ||
Object.defineProperty(orig, 'b', { | ||
get: function() { return "asdf"; }, enumerable: true | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization should still kicking if prototype have getter? Can we add a test #Resolved |
||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, orig.b); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "has setter", | ||
body: function () | ||
{ | ||
let orig = {}; | ||
orig.a = 1; | ||
Object.defineProperty(orig, 'b', { | ||
set: function() { }, enumerable: true | ||
}); | ||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, orig.b); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "different proto", | ||
body: function () | ||
{ | ||
let protoObj = {}; | ||
let orig = Object.create(protoObj); | ||
orig.a = 1; | ||
orig.b = "asdf"; | ||
|
||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, orig.b); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "non-enumerable", | ||
body: function () | ||
{ | ||
let orig = {}; | ||
orig.a = 1; | ||
Object.defineProperty(orig, 'b', { | ||
value: "asdf", enumerable: false | ||
}); | ||
|
||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, undefined); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
}, | ||
{ | ||
name: "proto accessor", | ||
body: function () | ||
{ | ||
Object.defineProperty(Object.prototype, 'b', { | ||
get: function() { return "asdf"; } | ||
}); | ||
let orig = {}; | ||
orig.a = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious : symbol will not have impact, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nope, doesn't matter. I added symbol to the test case |
||
|
||
let newObj = Object.assign({}, orig); | ||
assert.areEqual(newObj.b, "asdf"); | ||
assert.areEqual(newObj.a, orig.a); | ||
} | ||
} | ||
]; | ||
|
||
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
say we failed to clone for the first source, do we still attempt to do for the next sources?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check for cross-site object and just don't try that?
In reply to: 174883683 [](ancestors = 174883683)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we exclude external objects (IsExternal()). Not sure what is the harm though.
In reply to: 174883937 [](ancestors = 174883937,174883683)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add case for cross-site object, I'm not sure about external, but I'll exclude them for good measure