Bug 1448838: Add native version for CopyDataProperties. r=jandem

This commit is contained in:
André Bargull 2018-03-27 13:56:20 -07:00
Родитель e7a7eb2bdf
Коммит 61f80eae24
9 изменённых файлов: 488 добавлений и 27 удалений

Просмотреть файл

@ -219,67 +219,78 @@ function GetInternalError(msg) {
// To be used when a function is required but calling it shouldn't do anything. // To be used when a function is required but calling it shouldn't do anything.
function NullFunction() {} function NullFunction() {}
// Object Rest/Spread Properties proposal // ES2019 draft rev 4c2df13f4194057f09b920ee88712e5a70b1a556
// Abstract operation: CopyDataProperties (target, source, excluded) // 7.3.23 CopyDataProperties (target, source, excludedItems)
function CopyDataProperties(target, source, excluded) { function CopyDataProperties(target, source, excludedItems) {
// Step 1. // Step 1.
assert(IsObject(target), "target is an object"); assert(IsObject(target), "target is an object");
// Step 2. // Step 2.
assert(IsObject(excluded), "excluded is an object"); assert(IsObject(excludedItems), "excludedItems is an object");
// Steps 3, 6. // Steps 3 and 7.
if (source === undefined || source === null) if (source === undefined || source === null)
return; return;
// Step 4.a. // Step 4.
source = ToObject(source); var from = ToObject(source);
// Step 4.b.
var keys = OwnPropertyKeys(source);
// Step 5. // Step 5.
var keys = CopyDataPropertiesOrGetOwnKeys(target, from, excludedItems);
// Return if we copied all properties in native code.
if (keys === null)
return;
// Step 6.
for (var index = 0; index < keys.length; index++) { for (var index = 0; index < keys.length; index++) {
var key = keys[index]; var key = keys[index];
// We abbreviate this by calling propertyIsEnumerable which is faster // We abbreviate this by calling propertyIsEnumerable which is faster
// and returns false for not defined properties. // and returns false for not defined properties.
if (!hasOwn(key, excluded) && callFunction(std_Object_propertyIsEnumerable, source, key)) if (!hasOwn(key, excludedItems) &&
_DefineDataProperty(target, key, source[key]); callFunction(std_Object_propertyIsEnumerable, from, key))
{
_DefineDataProperty(target, key, from[key]);
}
} }
// Step 6 (Return). // Step 7 (Return).
} }
// Object Rest/Spread Properties proposal // ES2019 draft rev 4c2df13f4194057f09b920ee88712e5a70b1a556
// Abstract operation: CopyDataProperties (target, source, excluded) // 7.3.23 CopyDataProperties (target, source, excludedItems)
function CopyDataPropertiesUnfiltered(target, source) { function CopyDataPropertiesUnfiltered(target, source) {
// Step 1. // Step 1.
assert(IsObject(target), "target is an object"); assert(IsObject(target), "target is an object");
// Step 2 (Not applicable). // Step 2 (Not applicable).
// Steps 3, 6. // Steps 3 and 7.
if (source === undefined || source === null) if (source === undefined || source === null)
return; return;
// Step 4.a. // Step 4.
source = ToObject(source); var from = ToObject(source);
// Step 4.b.
var keys = OwnPropertyKeys(source);
// Step 5. // Step 5.
var keys = CopyDataPropertiesOrGetOwnKeys(target, from, null);
// Return if we copied all properties in native code.
if (keys === null)
return;
// Step 6.
for (var index = 0; index < keys.length; index++) { for (var index = 0; index < keys.length; index++) {
var key = keys[index]; var key = keys[index];
// We abbreviate this by calling propertyIsEnumerable which is faster // We abbreviate this by calling propertyIsEnumerable which is faster
// and returns false for not defined properties. // and returns false for not defined properties.
if (callFunction(std_Object_propertyIsEnumerable, source, key)) if (callFunction(std_Object_propertyIsEnumerable, from, key))
_DefineDataProperty(target, key, source[key]); _DefineDataProperty(target, key, from[key]);
} }
// Step 6 (Return). // Step 7 (Return).
} }
/*************************************** Testing functions ***************************************/ /*************************************** Testing functions ***************************************/

Просмотреть файл

@ -10,7 +10,8 @@ function tryCreateUnboxedObject() {
for (var i = 0; i < 1000; ++i) { for (var i = 0; i < 1000; ++i) {
obj = new Unboxed(); obj = new Unboxed();
} }
if (unboxedObjectsEnabled())
assertEq(isUnboxedObject(obj), true);
return obj; return obj;
} }

Просмотреть файл

@ -0,0 +1,44 @@
load(libdir + "asserts.js");
function Unboxed() {
this.a = 0;
this.b = true;
}
function tryCreateUnboxedObject() {
var obj;
for (var i = 0; i < 1000; ++i) {
obj = new Unboxed();
}
if (unboxedObjectsEnabled())
assertEq(isUnboxedObject(obj), true);
return obj;
}
function basic() {
var unboxed = tryCreateUnboxedObject();
var {...target} = unboxed;
assertDeepEq(target, {a: 0, b: true});
var {a, c, ...target} = unboxed;
assertDeepEq(a, 0);
assertDeepEq(c, undefined);
assertDeepEq(target, {b: true});
}
function expando() {
var unboxed = tryCreateUnboxedObject();
unboxed.c = 3.5;
var {...target} = unboxed;
assertDeepEq(target, {a: 0, b: true, c: 3.5});
var {a, d, ...target} = unboxed;
assertDeepEq(a, 0);
assertDeepEq(d, undefined);
assertDeepEq(target, {b: true, c: 3.5});
}
basic();
expando();

Просмотреть файл

@ -0,0 +1,90 @@
function test() {
var from, to;
// From values.
from = {x: 1, y: 2};
({...to} = from);
assertEq(to.y, 2);
var z;
from = {x: 1, y: 2};
({x: z, ...to} = from);
assertEq(z, 1);
assertEq(to.y, 2);
// From getter.
var c = 7;
from = {x: 1, get y() { return ++c; }};
({...to} = from);
assertEq(c, 8);
assertEq(to.y, 8);
from = {x: 1, get y() { return ++c; }};
({y: z, ...to} = from);
assertEq(c, 9);
assertEq(z, 9);
assertEq(to.y, undefined);
// Array with dense elements.
from = [1, 2, 3];
({...to} = from);
assertEq(to[2], 3);
assertEq("length" in to, false);
from = [1, 2, 3];
({2: z, ...to} = from);
assertEq(z, 3);
assertEq(to[2], undefined);
assertEq(to[0], 1);
assertEq("length" in to, false);
// Object with sparse elements and symbols.
from = {x: 1, 1234567: 2, 1234560: 3, [Symbol.iterator]: 5, z: 3};
({...to} = from);
assertEq(to[1234567], 2);
assertEq(Object.keys(to).toString(), "1234560,1234567,x,z");
assertEq(to[Symbol.iterator], 5);
from = {x: 1, 1234567: 2, 1234560: 3, [Symbol.iterator]: 5, z: 3};
({[Symbol.iterator]: z, ...to} = from);
assertEq(to[1234567], 2);
assertEq(Object.keys(to).toString(), "1234560,1234567,x,z");
assertEq(to[Symbol.iterator], undefined);
assertEq(z, 5);
// Typed array.
from = new Int32Array([1, 2, 3]);
({...to} = from);
assertEq(to[1], 2);
from = new Int32Array([1, 2, 3]);
({1: z, ...to} = from);
assertEq(z, 2);
assertEq(to[1], undefined);
assertEq(to[2], 3);
// Primitive string.
from = "foo";
({...to} = from);
assertEq(to[0], "f");
from = "foo";
({0: z, ...to} = from);
assertEq(z, "f");
assertEq(to[0], undefined);
assertEq(to[1], "o");
// String object.
from = new String("bar");
({...to} = from);
assertEq(to[2], "r");
from = new String("bar");
({1: z, ...to} = from);
assertEq(z, "a");
assertEq(to[1], undefined);
assertEq(to[2], "r");
}
test();
test();
test();

Просмотреть файл

@ -0,0 +1,40 @@
load(libdir + "asserts.js");
function Unboxed() {
this.a = 0;
this.b = true;
}
function tryCreateUnboxedObject() {
var obj;
for (var i = 0; i < 1000; ++i) {
obj = new Unboxed();
}
if (unboxedObjectsEnabled())
assertEq(isUnboxedObject(obj), true);
return obj;
}
function basic() {
var unboxed = tryCreateUnboxedObject();
var target = {...unboxed};
assertDeepEq(target, {a: 0, b: true});
target = {a: 1, c: 3, ...unboxed};
assertDeepEq(target, {a: 0, c: 3, b: true});
}
function expando() {
var unboxed = tryCreateUnboxedObject();
unboxed.c = 3.5;
var target = {...unboxed};
assertDeepEq(target, {a: 0, b: true, c: 3.5});
target = {a: 1, d: 3, ...unboxed};
assertDeepEq(target, {a: 0, d: 3, b: true, c: 3.5});
}
basic();
expando();

Просмотреть файл

@ -0,0 +1,49 @@
function test() {
var from, to;
// From values.
from = {x: 1, y: 2};
to = {...from};
assertEq(to.y, 2);
to = {...from, ...from};
assertEq(to.y, 2);
// From getter.
var c = 7;
from = {x: 1, get y() { return ++c; }};
to = {...from};
assertEq(to.y, 8);
to = {...from, ...from};
assertEq(to.y, 10);
// Array with dense elements.
from = [1, 2, 3];
to = {...from};
assertEq(to[2], 3);
assertEq("length" in to, false);
// Object with sparse elements and symbols.
from = {x: 1, 1234567: 2, 1234560: 3, [Symbol.iterator]: 5, z: 3};
to = {...from};
assertEq(to[1234567], 2);
assertEq(Object.keys(to).toString(), "1234560,1234567,x,z");
assertEq(to[Symbol.iterator], 5);
// Typed array.
from = new Int32Array([1, 2, 3]);
to = {...from};
assertEq(to[1], 2);
// Primitive string.
from = "foo";
to = {...from};
assertEq(to[0], "f");
// String object.
from = new String("bar");
to = {...from};
assertEq(to[2], "r");
}
test();
test();
test();

Просмотреть файл

@ -9,17 +9,20 @@
#include "mozilla/ArrayUtils.h" #include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h" #include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h" #include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "gc/Marking.h" #include "gc/Marking.h"
#include "js/Value.h" #include "js/Value.h"
#include "vm/Debugger.h" #include "vm/Debugger.h"
#include "vm/TypedArrayObject.h" #include "vm/TypedArrayObject.h"
#include "vm/UnboxedObject.h"
#include "gc/Nursery-inl.h" #include "gc/Nursery-inl.h"
#include "vm/ArrayObject-inl.h" #include "vm/ArrayObject-inl.h"
#include "vm/EnvironmentObject-inl.h" #include "vm/EnvironmentObject-inl.h"
#include "vm/JSObject-inl.h" #include "vm/JSObject-inl.h"
#include "vm/Shape-inl.h" #include "vm/Shape-inl.h"
#include "vm/UnboxedObject-inl.h"
using namespace js; using namespace js;
@ -1498,8 +1501,8 @@ AddOrChangeProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
return CallAddPropertyHook(cx, obj, id, desc.value()); return CallAddPropertyHook(cx, obj, id, desc.value());
} }
// Version of AddOrChangeProperty optimized for adding a plain data property. // Versions of AddOrChangeProperty optimized for adding a plain data property.
// This function doesn't handle integer ids as we may have to store them in // These function doesn't handle integer ids as we may have to store them in
// dense elements. // dense elements.
static MOZ_ALWAYS_INLINE bool static MOZ_ALWAYS_INLINE bool
AddDataProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v) AddDataProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v)
@ -1518,6 +1521,24 @@ AddDataProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue
return CallAddPropertyHook(cx, obj, id, v); return CallAddPropertyHook(cx, obj, id, v);
} }
static MOZ_ALWAYS_INLINE bool
AddDataPropertyNonDelegate(JSContext* cx, HandlePlainObject obj, HandleId id, HandleValue v)
{
MOZ_ASSERT(!JSID_IS_INT(id));
MOZ_ASSERT(!obj->isDelegate());
// If we know this is a new property we can call addProperty instead of
// the slower putProperty.
Shape* shape = NativeObject::addEnumerableDataProperty(cx, obj, id);
if (!shape)
return false;
UpdateShapeTypeAndValueForWritableDataProp(cx, obj, shape, id, v);
MOZ_ASSERT(!obj->getClass()->getAddProperty());
return true;
}
static bool IsConfigurable(unsigned attrs) { return (attrs & JSPROP_PERMANENT) == 0; } static bool IsConfigurable(unsigned attrs) { return (attrs & JSPROP_PERMANENT) == 0; }
static bool IsEnumerable(unsigned attrs) { return (attrs & JSPROP_ENUMERATE) != 0; } static bool IsEnumerable(unsigned attrs) { return (attrs & JSPROP_ENUMERATE) != 0; }
static bool IsWritable(unsigned attrs) { return (attrs & JSPROP_READONLY) == 0; } static bool IsWritable(unsigned attrs) { return (attrs & JSPROP_READONLY) == 0; }
@ -2895,3 +2916,143 @@ js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
return SuppressDeletedProperty(cx, obj, id); return SuppressDeletedProperty(cx, obj, id);
} }
bool
js::CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target, HandleNativeObject from,
HandlePlainObject excludedItems, bool* optimized)
{
MOZ_ASSERT(!target->isDelegate(),
"CopyDataPropertiesNative should only be called during object literal construction"
"which precludes that |target| is the prototype of any other object");
*optimized = false;
// Don't use the fast path if |from| may have extra indexed or lazy
// properties.
if (from->getDenseInitializedLength() > 0 ||
from->isIndexed() ||
from->is<TypedArrayObject>() ||
from->getClass()->getNewEnumerate() ||
from->getClass()->getEnumerate())
{
return true;
}
// Collect all enumerable data properties.
using ShapeVector = GCVector<Shape*, 8>;
Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
RootedShape fromShape(cx, from->lastProperty());
for (Shape::Range<NoGC> r(fromShape); !r.empty(); r.popFront()) {
Shape* shape = &r.front();
jsid id = shape->propid();
MOZ_ASSERT(!JSID_IS_INT(id));
if (!shape->enumerable())
continue;
if (excludedItems && excludedItems->contains(cx, id))
continue;
// Don't use the fast path if |from| contains non-data properties.
//
// This enables two optimizations:
// 1. We don't need to handle the case when accessors modify |from|.
// 2. String and symbol properties can be added in one go.
if (!shape->isDataProperty())
return true;
if (!shapes.append(shape))
return false;
}
*optimized = true;
// If |target| contains no own properties, we can directly call
// addProperty instead of the slower putProperty.
const bool targetHadNoOwnProperties = target->lastProperty()->isEmptyShape();
RootedId key(cx);
RootedValue value(cx);
for (size_t i = shapes.length(); i > 0; i--) {
Shape* shape = shapes[i - 1];
MOZ_ASSERT(shape->isDataProperty());
MOZ_ASSERT(shape->enumerable());
key = shape->propid();
MOZ_ASSERT(!JSID_IS_INT(key));
MOZ_ASSERT(from->isNative());
MOZ_ASSERT(from->lastProperty() == fromShape);
value = from->getSlot(shape->slot());
if (targetHadNoOwnProperties) {
MOZ_ASSERT(!target->contains(cx, key),
"didn't expect to find an existing property");
if (!AddDataPropertyNonDelegate(cx, target, key, value))
return false;
} else {
if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE))
return false;
}
}
return true;
}
bool
js::CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
Handle<UnboxedPlainObject*> from, HandlePlainObject excludedItems,
bool* optimized)
{
MOZ_ASSERT(!target->isDelegate(),
"CopyDataPropertiesNative should only be called during object literal construction"
"which precludes that |target| is the prototype of any other object");
*optimized = false;
// Don't use the fast path for unboxed objects with expandos.
if (from->maybeExpando())
return true;
*optimized = true;
// If |target| contains no own properties, we can directly call
// addProperty instead of the slower putProperty.
const bool targetHadNoOwnProperties = target->lastProperty()->isEmptyShape();
#ifdef DEBUG
RootedObjectGroup fromGroup(cx, from->group());
#endif
RootedId key(cx);
RootedValue value(cx);
const UnboxedLayout& layout = from->layout();
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
key = NameToId(property.name);
MOZ_ASSERT(!JSID_IS_INT(key));
if (excludedItems && excludedItems->contains(cx, key))
continue;
// Ensure the object stays unboxed.
MOZ_ASSERT(from->group() == fromGroup);
// All unboxed properties are enumerable.
value = from->getValue(property);
if (targetHadNoOwnProperties) {
MOZ_ASSERT(!target->contains(cx, key),
"didn't expect to find an existing property");
if (!AddDataPropertyNonDelegate(cx, target, key, value))
return false;
} else {
if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE))
return false;
}
}
return true;
}

Просмотреть файл

@ -29,6 +29,7 @@ namespace js {
class Shape; class Shape;
class TenuringTracer; class TenuringTracer;
class UnboxedPlainObject;
/* /*
* To really poison a set of values, using 'magic' or 'undefined' isn't good * To really poison a set of values, using 'magic' or 'undefined' isn't good
@ -1607,6 +1608,16 @@ bool IsPackedArray(JSObject* obj);
extern void extern void
AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup); AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup);
// Specializations of 7.3.23 CopyDataProperties(...) for NativeObjects.
extern bool
CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target, HandleNativeObject from,
HandlePlainObject excludedItems, bool* optimized);
extern bool
CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
Handle<UnboxedPlainObject*> from, HandlePlainObject excludedItems,
bool* optimized);
} // namespace js } // namespace js

Просмотреть файл

@ -2159,6 +2159,59 @@ intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp)
return true; return true;
} }
static bool
intrinsic_CopyDataPropertiesOrGetOwnKeys(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isObject());
MOZ_ASSERT(args[2].isObjectOrNull());
RootedObject target(cx, &args[0].toObject());
RootedObject from(cx, &args[1].toObject());
RootedObject excludedItems(cx, args[2].toObjectOrNull());
if (from->isNative() &&
target->is<PlainObject>() &&
(!excludedItems || excludedItems->is<PlainObject>()))
{
bool optimized;
if (!CopyDataPropertiesNative(cx, target.as<PlainObject>(), from.as<NativeObject>(),
(excludedItems ? excludedItems.as<PlainObject>() : nullptr),
&optimized))
{
return false;
}
if (optimized) {
args.rval().setNull();
return true;
}
}
if (from->is<UnboxedPlainObject>() &&
target->is<PlainObject>() &&
(!excludedItems || excludedItems->is<PlainObject>()))
{
bool optimized;
if (!CopyDataPropertiesNative(cx, target.as<PlainObject>(), from.as<UnboxedPlainObject>(),
(excludedItems ? excludedItems.as<PlainObject>() : nullptr),
&optimized))
{
return false;
}
if (optimized) {
args.rval().setNull();
return true;
}
}
return GetOwnPropertyKeys(cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
args.rval());
}
// The self-hosting global isn't initialized with the normal set of builtins. // The self-hosting global isn't initialized with the normal set of builtins.
// Instead, individual C++-implemented functions that're required by // Instead, individual C++-implemented functions that're required by
// self-hosted code are defined as global functions. Accessing these // self-hosted code are defined as global functions. Accessing these
@ -2284,6 +2337,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("AddContentTelemetry", intrinsic_AddContentTelemetry, 2,0), JS_FN("AddContentTelemetry", intrinsic_AddContentTelemetry, 2,0),
JS_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0), JS_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0),
JS_FN("_DefineProperty", intrinsic_DefineProperty, 6,0), JS_FN("_DefineProperty", intrinsic_DefineProperty, 6,0),
JS_FN("CopyDataPropertiesOrGetOwnKeys", intrinsic_CopyDataPropertiesOrGetOwnKeys, 3,0),
JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0, JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0,
IntrinsicIsConstructing), IntrinsicIsConstructing),