Diff
Modified: trunk/JSTests/ChangeLog (271342 => 271343)
--- trunk/JSTests/ChangeLog 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/JSTests/ChangeLog 2021-01-09 04:31:12 UTC (rev 271343)
@@ -1,5 +1,17 @@
2021-01-08 Alexey Shvayka <[email protected]>
+ Implement @copyDataProperties in C++ to optimize object rest / spread
+ https://bugs.webkit.org/show_bug.cgi?id=193618
+
+ Reviewed by Yusuke Suzuki.
+
+ * microbenchmarks/object-rest-destructuring.js: Added.
+ * microbenchmarks/object-spread.js: Added.
+ * stress/object-rest-deconstruct.js:
+ * stress/object-spread.js:
+
+2021-01-08 Alexey Shvayka <[email protected]>
+
for/in over a Proxy should not call [[GetOwnProperty]] trap twice per property
https://bugs.webkit.org/show_bug.cgi?id=189034
Added: trunk/JSTests/microbenchmarks/object-rest-destructuring.js (0 => 271343)
--- trunk/JSTests/microbenchmarks/object-rest-destructuring.js (rev 0)
+++ trunk/JSTests/microbenchmarks/object-rest-destructuring.js 2021-01-09 04:31:12 UTC (rev 271343)
@@ -0,0 +1,12 @@
+(function() {
+ var obj = { a: 0, b: 0, c: 0, d: 1, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0 };
+ var acc = 0;
+
+ for (var i = 0; i < 1e5; i++) {
+ var { a, b, c, ...rest } = obj;
+ acc += rest.d;
+ }
+
+ if (acc !== 1e5)
+ throw new Error("Bad assertion!");
+})();
Added: trunk/JSTests/microbenchmarks/object-spread.js (0 => 271343)
--- trunk/JSTests/microbenchmarks/object-spread.js (rev 0)
+++ trunk/JSTests/microbenchmarks/object-spread.js 2021-01-09 04:31:12 UTC (rev 271343)
@@ -0,0 +1,10 @@
+(function() {
+ var obj = { a: 1, b: 2, c: 3, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0 };
+ var acc = 0;
+
+ for (var i = 0; i < 1e5; i++)
+ acc += ({ a: 0, b: 0, c: 0, ...obj }).a;
+
+ if (acc !== 1e5)
+ throw new Error("Bad assertion!");
+})();
Modified: trunk/JSTests/stress/object-rest-deconstruct.js (271342 => 271343)
--- trunk/JSTests/stress/object-rest-deconstruct.js 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/JSTests/stress/object-rest-deconstruct.js 2021-01-09 04:31:12 UTC (rev 271343)
@@ -3,8 +3,15 @@
throw Error("Bad assertion!");
}
-let assertPropDescriptor = (restObj, prop) => {
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error(`Bad value: ${actual}!`);
+}
+
+function assertDataProperty(restObj, prop, value) {
+ shouldBe(restObj[prop], value);
let desc = Object.getOwnPropertyDescriptor(restObj, prop);
+ shouldBe(desc.value, value);
assert(desc.enumerable);
assert(desc.writable);
assert(desc.configurable);
@@ -19,11 +26,8 @@
assert(a === 5);
assert(b === 3);
- assert(rest.x === 1);
- assert(rest.y === 2);
-
- assertPropDescriptor(rest, 'x');
- assertPropDescriptor(rest, 'y');
+ assertDataProperty(rest, 'x', 1);
+ assertDataProperty(rest, 'y', 2);
})();
// Empty Object
@@ -101,8 +105,7 @@
assert(a === 3);
assert(b === 4);
- assert(rest.x === 3);
- assertPropDescriptor(rest, 'x');
+ assertDataProperty(rest, 'x', 3);
})();
// Skip non-enumerable case
@@ -128,8 +131,8 @@
assert(rest.a === 3);
assert(rest.b === 4);
- assertPropDescriptor(rest, 'a');
- assertPropDescriptor(rest, 'b');
+ assertDataProperty(rest, 'a', 3);
+ assertDataProperty(rest, 'b', 4);
})();
// Destructuring function parameter
@@ -235,3 +238,126 @@
}
})();
+(function nonEnumerableSymbol() {
+ var __s0 = Symbol("s0");
+ var __s1 = Symbol("s1");
+
+ var source = {};
+ Object.defineProperties(source, {
+ [__s0]: {value: 0, enumerable: false},
+ [__s1]: {value: 1, enumerable: false},
+ });
+
+ var {[__s0]: s0, ...target} = source;
+
+ shouldBe(s0, 0);
+ assert(!Object.getOwnPropertySymbols(target).length);
+})();
+
+(function dunderProto() {
+ var source = {};
+ var protoValue = {};
+ Object.defineProperty(source, "__proto__", {value: protoValue, enumerable: true});
+ var {...target} = source;
+
+ assertDataProperty(target, "__proto__", protoValue);
+ shouldBe(Object.getPrototypeOf(target), Object.prototype);
+})();
+
+(function stringPrimitive() {
+ var source = "012";
+ var {0: a, ["2"]: c, ...target} = source;
+
+ shouldBe(a, "0");
+ shouldBe(c, "2");
+ assertDataProperty(target, 1, "1");
+ shouldBe(Object.keys(target).join(), "1");
+})();
+
+(function ProxyObject() {
+ var __s0 = Symbol("s0");
+ var __s1 = Symbol("s1");
+
+ var ownKeysCalls = 0;
+ var gopdCalls = [];
+ var getCalls = [];
+
+ var source = new Proxy({
+ [__s0]: "s0", [__s1]: "s1",
+ a: 0, b: 1, c: 2, d: 3,
+ }, {
+ ownKeys: (t) => {
+ ++ownKeysCalls;
+ return Reflect.ownKeys(t);
+ },
+ getOwnPropertyDescriptor: (t, key) => {
+ gopdCalls.push(key);
+ var desc = Reflect.getOwnPropertyDescriptor(t, key);
+ if (key === "b" || key === __s0)
+ desc.enumerable = false;
+ return desc;
+ },
+ get: (t, key, receiver) => {
+ getCalls.push(key);
+ return Reflect.get(t, key, receiver);
+ },
+ });
+
+ var {c, ["d"]: d, ...target} = source;
+
+ shouldBe(ownKeysCalls, 1);
+ shouldBe(gopdCalls.map(String).join(), "a,b,Symbol(s0),Symbol(s1)");
+ shouldBe(getCalls.map(String).join(), "c,d,a,Symbol(s1)");
+
+ assertDataProperty(target, "a", 0);
+ shouldBe(c, 2);
+ shouldBe(d, 3);
+ shouldBe(Object.keys(target).join(), "a");
+
+ var symbols = Object.getOwnPropertySymbols(target);
+ shouldBe(symbols[0], __s1);
+ shouldBe(symbols.length, 1);
+})();
+
+(function indexedProperties() {
+ var source = [0, 1, 2, 3];
+ Object.defineProperty(source, "1", {enumerable: false});
+ var {2: c, ["3"]: d, ...target} = source;
+
+ assertDataProperty(target, "0", 0);
+ shouldBe(Object.keys(target).join(), "0");
+})();
+
+(function CustomAccessor() {
+ var source = $vm.createCustomTestGetterSetter();
+ var {customValueGlobalObject, ...target} = source;
+
+ assertDataProperty(target, "customValue", source);
+ assert(!target.hasOwnProperty("customValueGlobalObject"));
+ shouldBe(customValueGlobalObject[Symbol.toStringTag], "global");
+ assertDataProperty(target, "customAccessor", source);
+ assertDataProperty(target, "customAccessorGlobalObject", customValueGlobalObject);
+})();
+
+(function CustomAccessorInNonReifiedPropertyTable() {
+ // make sure we reifyAllStaticProperties() before deciding on the fast/slow path
+
+ var source = $vm.createStaticCustomAccessor();
+ source.testField = 42;
+ var {...target} = source;
+
+ assertDataProperty(target, "testField", 42);
+ assertDataProperty(target, "testStaticAccessor", 42);
+})();
+
+(function uncacheableDictionary() {
+ var source = {a: 0, b: 1, c: 2, d: 3};
+ Object.defineProperty(source, "c", {enumerable: false});
+ $vm.toUncacheableDictionary(source);
+ var {b, ["d"]: d, ...target} = source;
+
+ assertDataProperty(target, "a", 0);
+ shouldBe(b, 1);
+ shouldBe(d, 3);
+ shouldBe(Object.keys(target).join(), "a");
+})();
Modified: trunk/JSTests/stress/object-spread.js (271342 => 271343)
--- trunk/JSTests/stress/object-spread.js 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/JSTests/stress/object-spread.js 2021-01-09 04:31:12 UTC (rev 271343)
@@ -7,12 +7,18 @@
assert(a === b);
}
-function validatePropertyDescriptor(o, p) {
- let desc = Object.getOwnPropertyDescriptor(o, p);
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error(`Bad value: ${actual}!`);
+}
+function assertDataProperty(target, prop, value) {
+ shouldBe(target[prop], value);
+ let desc = Object.getOwnPropertyDescriptor(target, prop);
+ shouldBe(desc.value, value);
assert(desc.enumerable);
+ assert(desc.writable);
assert(desc.configurable);
- assert(desc.writable);
}
// Base cases
@@ -22,10 +28,8 @@
assert.sameValue(obj.a, 1);
assert(obj.b, 2);
- assert(obj.c, 3);
- assert(obj.d, 4);
- validatePropertyDescriptor(obj, "c");
- validatePropertyDescriptor(obj, "d");
+ assertDataProperty(obj, "c", 3);
+ assertDataProperty(obj, "d", 4);
assert(Object.keys(obj), 2);
})();
@@ -33,16 +37,11 @@
let o = {c: 3, d: 4};
let obj = {a: 1, b: 2, ...o};
- assert.sameValue(obj.a, 1);
- assert.sameValue(obj.b, 2);
- assert.sameValue(obj.c, 3);
- assert.sameValue(obj.d, 4);
+ assertDataProperty(obj, "a", 1);
+ assertDataProperty(obj, "b", 2);
+ assertDataProperty(obj, "c", 3);
+ assertDataProperty(obj, "d", 4);
assert.sameValue(Object.keys(obj).length, 4);
-
- validatePropertyDescriptor(obj, "a");
- validatePropertyDescriptor(obj, "b");
- validatePropertyDescriptor(obj, "c");
- validatePropertyDescriptor(obj, "d");
})();
(() => {
@@ -128,12 +127,10 @@
let obj = {...o, c: 4, d: 5};
- assert.sameValue(Object.getOwnPropertyDescriptor(obj, "a").value, 42);
+ assertDataProperty(obj, "a", 42);
assert.sameValue(obj.c, 4);
assert.sameValue(obj.d, 5);
assert.sameValue(Object.keys(obj).length, 3);
-
- validatePropertyDescriptor(obj, "a");
})();
(() => {
@@ -223,10 +220,8 @@
let obj = {...o, a: 3};
- assert.sameValue(obj.a, 3)
- assert.sameValue(obj.b, 2);
- validatePropertyDescriptor(obj, "a");
- validatePropertyDescriptor(obj, "b");
+ assertDataProperty(obj, "a", 3);
+ assertDataProperty(obj, "b", 2);
})();
// Setter
@@ -311,3 +306,119 @@
assert(executedSetter);
})();
+(function nonEnumerableSymbol() {
+ var __s0 = Symbol("s0");
+ var source = {};
+ Object.defineProperties(source, {
+ [__s0]: {value: 0, enumerable: false},
+ });
+
+ var target = {[__s0]: 1, ...target};
+ assertDataProperty(target, __s0, 1);
+})();
+
+(function dunderProto() {
+ var source = {};
+ var protoValue = {};
+ Object.defineProperty(source, "__proto__", {value: protoValue, enumerable: true});
+ var target = {...source};
+
+ assertDataProperty(target, "__proto__", protoValue);
+ shouldBe(Object.getPrototypeOf(target), Object.prototype);
+})();
+
+(function stringPrimitive() {
+ var source = "012";
+ var target = {get 0() {}, ["2"]: 22, ...source};
+
+ assertDataProperty(target, 0, "0");
+ assertDataProperty(target, 1, "1");
+ assertDataProperty(target, 2, "2");
+ shouldBe(Object.keys(target).join(), "0,1,2");
+})();
+
+(function ProxyObject() {
+ var __s0 = Symbol("s0");
+ var __s1 = Symbol("s1");
+
+ var ownKeysCalls = 0;
+ var gopdCalls = [];
+ var getCalls = [];
+
+ var source = new Proxy({
+ [__s0]: "s0", [__s1]: "s1",
+ a: 0, b: 1, c: 2, d: 3,
+ }, {
+ ownKeys: (t) => {
+ ++ownKeysCalls;
+ return Reflect.ownKeys(t);
+ },
+ getOwnPropertyDescriptor: (t, key) => {
+ gopdCalls.push(key);
+ var desc = Reflect.getOwnPropertyDescriptor(t, key);
+ if (key === "b" || key === __s0)
+ desc.enumerable = false;
+ return desc;
+ },
+ get: (t, key, receiver) => {
+ getCalls.push(key);
+ return Reflect.get(t, key, receiver);
+ },
+ });
+
+ var target = {a() {}, b: 11, [__s1]: "foo", ...source, ["d"]: 33};
+
+ shouldBe(ownKeysCalls, 1);
+ shouldBe(gopdCalls.map(String).join(), "a,b,c,d,Symbol(s0),Symbol(s1)");
+ shouldBe(getCalls.map(String).join(), "a,c,d,Symbol(s1)");
+
+ assertDataProperty(target, "a", 0);
+ assertDataProperty(target, "b", 11);
+ assertDataProperty(target, "c", 2);
+ assertDataProperty(target, "d", 33);
+
+ shouldBe(Reflect.ownKeys(target).map(String).join(), "a,b,c,d,Symbol(s1)");
+})();
+
+(function indexedProperties() {
+ var source = [0, 1, 2];
+ Object.defineProperty(source, "1", {enumerable: false});
+ var target = {set ["2"](_v) {}, ...source};
+
+ assertDataProperty(target, "0", 0);
+ assertDataProperty(target, "2", 2);
+ shouldBe(Object.keys(target).join(), "0,2");
+})();
+
+(function CustomAccessor() {
+ var source = $vm.createCustomTestGetterSetter();
+ var target = {...source};
+
+ assertDataProperty(target, "customValue", source);
+ shouldBe(target.customValueGlobalObject, target.customAccessorGlobalObject);
+ shouldBe(target.customValueGlobalObject[Symbol.toStringTag], "global");
+ assertDataProperty(target, "customAccessor", source);
+})();
+
+(function CustomAccessorInNonReifiedPropertyTable() {
+ // make sure we reifyAllStaticProperties() before deciding on the fast/slow path
+
+ var source = $vm.createStaticCustomAccessor();
+ source.testField = 42;
+ var target = {...source};
+
+ assertDataProperty(target, "testField", 42);
+ assertDataProperty(target, "testStaticAccessor", 42);
+})();
+
+(function uncacheableDictionary() {
+ var source = {a: 0, b: 1, c: 2};
+ Object.defineProperty(source, "c", {enumerable: false});
+ $vm.toUncacheableDictionary(source);
+ var b = 11;
+ var target = {get a() {}, b, ...source};
+
+ assertDataProperty(target, "a", 0);
+ assertDataProperty(target, "b", 1);
+ shouldBe(Object.keys(target).join(), "a,b");
+})();
Modified: trunk/Source/_javascript_Core/ChangeLog (271342 => 271343)
--- trunk/Source/_javascript_Core/ChangeLog 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/ChangeLog 2021-01-09 04:31:12 UTC (rev 271343)
@@ -1,3 +1,48 @@
+2021-01-08 Alexey Shvayka <[email protected]>
+
+ Implement @copyDataProperties in C++ to optimize object rest / spread
+ https://bugs.webkit.org/show_bug.cgi?id=193618
+
+ Reviewed by Yusuke Suzuki.
+
+ Since @copyDataProperties is inherently polymorphic, implementing it in JS is not beneficial.
+ This patch:
+
+ 1. Merges almost identical @copyDataProperties variants and moves them to C++, avoiding
+ allocations of JSArray instances and Identifier wrappers.
+ 2. Skips non-observable [[Get]] calls, leveraging `slot.isTaintedByOpaqueObject()`.
+ 3. Performs [[DefineOwnProperty]] via putDirectMayBeIndex(), since the spec guarantees
+ property creation to be successful [1]: `target` is an newly created object that is
+ not yet accessible to userland code. It's impossible for `target` to be non-extensible
+ nor have a non-configurable property.
+ 4. Introduces a fast path similar to Object.assign, but:
+ a) with no checks on `target`, because it's guaranteed to be an extensible JSFinalObject;
+ b) with less checks on `source`, since we are performing putDirect() and don't care about
+ read-only properties nor __proto__.
+
+ Altogether, these changes result in 3.1x speed-up for object rest / spread.
+ Also, this patch removes unnecessary `target` return and @isObject check.
+
+ [1]: https://tc39.es/ecma262/#sec-copydataproperties (step 6.c.ii.2, note the "!" prefix)
+
+ * builtins/BuiltinNames.h:
+ * builtins/GlobalOperations.js:
+ (globalPrivate.speciesConstructor):
+ (globalPrivate.copyDataProperties): Deleted.
+ (globalPrivate.copyDataPropertiesNoExclusions): Deleted.
+ * bytecode/BytecodeIntrinsicRegistry.h:
+ * bytecode/LinkTimeConstant.h:
+ * bytecompiler/NodesCodegen.cpp:
+ (JSC::ObjectPatternNode::bindValue const):
+ (JSC::ObjectSpreadExpressionNode::emitBytecode):
+ (JSC::BytecodeIntrinsicNode::emit_intrinsic_defineEnumerableWritableConfigurableDataProperty): Deleted.
+ * runtime/JSGlobalObject.cpp:
+ (JSC::JSGlobalObject::init):
+ * runtime/JSGlobalObjectFunctions.cpp:
+ (JSC::canPerformFastPropertyEnumerationForCopyDataProperties):
+ (JSC::JSC_DEFINE_HOST_FUNCTION):
+ * runtime/JSGlobalObjectFunctions.h:
+
2021-01-08 Yusuke Suzuki <[email protected]>
[JSC] Disable JITCage compile time in old iOS
Modified: trunk/Source/_javascript_Core/builtins/BuiltinNames.h (271342 => 271343)
--- trunk/Source/_javascript_Core/builtins/BuiltinNames.h 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/builtins/BuiltinNames.h 2021-01-09 04:31:12 UTC (rev 271343)
@@ -67,7 +67,6 @@
macro(defineProperty) \
macro(defaultPromiseThen) \
macro(getOwnPropertyNames) \
- macro(ownKeys) \
macro(Set) \
macro(throwTypeErrorFunction) \
macro(typedArrayLength) \
@@ -169,6 +168,7 @@
macro(hasOwnLengthProperty) \
macro(importModule) \
macro(propertyIsEnumerable) \
+ macro(copyDataProperties) \
macro(meta) \
macro(webAssemblyCompileStreamingInternal) \
macro(webAssemblyInstantiateStreamingInternal) \
Modified: trunk/Source/_javascript_Core/builtins/GlobalOperations.js (271342 => 271343)
--- trunk/Source/_javascript_Core/builtins/GlobalOperations.js 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/builtins/GlobalOperations.js 2021-01-09 04:31:12 UTC (rev 271343)
@@ -75,55 +75,3 @@
return constructor;
@throwTypeError("|this|.constructor[Symbol.species] is not a constructor");
}
-
-@globalPrivate
-function copyDataProperties(target, source, excludedSet)
-{
- "use strict";
-
- if (!@isObject(target))
- @throwTypeError("target needs to be an object");
-
- if (@isUndefinedOrNull(source))
- return target;
-
- var from = @toObject(source);
- var keys = @ownKeys(from);
- var keysLength = keys.length;
- for (var i = 0; i < keysLength; i++) {
- var nextKey = keys[i];
- if (!excludedSet.@has(nextKey)) {
- if (@propertyIsEnumerable(from, nextKey)) {
- var propValue = from[nextKey];
- @defineEnumerableWritableConfigurableDataProperty(target, nextKey, propValue);
- }
- }
- }
-
- return target;
-}
-
-@globalPrivate
-function copyDataPropertiesNoExclusions(target, source)
-{
- "use strict";
-
- if (!@isObject(target))
- @throwTypeError("target needs to be an object");
-
- if (@isUndefinedOrNull(source))
- return target;
-
- var from = @toObject(source);
- var keys = @ownKeys(from);
- var keysLength = keys.length;
- for (var i = 0; i < keysLength; i++) {
- var nextKey = keys[i];
- if (@propertyIsEnumerable(from, nextKey)) {
- var propValue = from[nextKey];
- @defineEnumerableWritableConfigurableDataProperty(target, nextKey, propValue);
- }
- }
-
- return target;
-}
Modified: trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h (271342 => 271343)
--- trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h 2021-01-09 04:31:12 UTC (rev 271343)
@@ -92,7 +92,6 @@
macro(newPromise) \
macro(createPromise) \
macro(createArgumentsButterfly) \
- macro(defineEnumerableWritableConfigurableDataProperty) \
#define JSC_COMMON_BYTECODE_INTRINSIC_CONSTANTS_EACH_NAME(macro) \
JSC_COMMON_BYTECODE_INTRINSIC_CONSTANTS_SIMPLE_EACH_NAME(macro) \
Modified: trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h (271342 => 271343)
--- trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h 2021-01-09 04:31:12 UTC (rev 271343)
@@ -45,7 +45,7 @@
v(setBucketKey, nullptr) \
v(setPrototypeDirect, nullptr) \
v(propertyIsEnumerable, nullptr) \
- v(ownKeys, nullptr) \
+ v(copyDataProperties, nullptr) \
v(enqueueJob, nullptr) \
v(makeTypeError, nullptr) \
v(AggregateError, nullptr) \
Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (271342 => 271343)
--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2021-01-09 04:31:12 UTC (rev 271343)
@@ -1778,20 +1778,6 @@
return generator.emitCreateArgumentsButterfly(generator.finalDestination(dst));
}
-RegisterID* BytecodeIntrinsicNode::emit_intrinsic_defineEnumerableWritableConfigurableDataProperty(JSC::BytecodeGenerator& generator, JSC::RegisterID* dst)
-{
- ArgumentListNode* node = m_args->m_listNode;
- RefPtr<RegisterID> newObj = generator.emitNode(node);
- node = node->m_next;
- RefPtr<RegisterID> propertyNameRegister = generator.emitNode(node);
- node = node->m_next;
- RefPtr<RegisterID> value = generator.emitNode(node);
- ASSERT(!node->m_next);
-
- generator.emitCallDefineProperty(newObj.get(), propertyNameRegister.get(), value.get(), nullptr, nullptr, BytecodeGenerator::PropertyConfigurable | BytecodeGenerator::PropertyWritable | BytecodeGenerator::PropertyEnumerable, m_position);
- return dst;
-}
-
#define JSC_DECLARE_BYTECODE_INTRINSIC_CONSTANT_GENERATORS(name) \
RegisterID* BytecodeIntrinsicNode::emit_intrinsic_##name(BytecodeGenerator& generator, RegisterID* dst) \
{ \
@@ -5358,9 +5344,8 @@
generator.move(args.argumentRegister(2), excludedSetReg.get());
}
- RefPtr<RegisterID> result = generator.newTemporary();
- generator.emitCall(result.get(), copyDataProperties.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd(), DebuggableCall::No);
- target.pattern->bindValue(generator, result.get());
+ generator.emitCall(generator.newTemporary(), copyDataProperties.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd(), DebuggableCall::No);
+ target.pattern->bindValue(generator, newObject.get());
}
}
@@ -5569,8 +5554,7 @@
RefPtr<RegisterID> src = ""
generator.emitNode(src.get(), m_expression);
- // load and call @copyDataPropertiesNoExclusions
- RefPtr<RegisterID> copyDataProperties = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::copyDataPropertiesNoExclusions);
+ RefPtr<RegisterID> copyDataProperties = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::copyDataProperties);
CallArguments args(generator, nullptr, 2);
generator.emitLoad(args.thisRegister(), jsUndefined());
Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (271342 => 271343)
--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2021-01-09 04:31:12 UTC (rev 271343)
@@ -1189,8 +1189,8 @@
m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::propertyIsEnumerable)].initLater([] (const Initializer<JSCell>& init) {
init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, String(), globalFuncPropertyIsEnumerable));
});
- m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::ownKeys)].initLater([] (const Initializer<JSCell>& init) {
- init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, String(), globalFuncOwnKeys));
+ m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::copyDataProperties)].initLater([] (const Initializer<JSCell>& init) {
+ init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 2, String(), globalFuncCopyDataProperties));
});
m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::enqueueJob)].initLater([] (const Initializer<JSCell>& init) {
init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, "enqueueJob"_s, enqueueJob));
Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp (271342 => 271343)
--- trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp 2021-01-09 04:31:12 UTC (rev 271343)
@@ -839,13 +839,126 @@
return JSValue::encode(jsBoolean(enumerable));
}
-JSC_DEFINE_HOST_FUNCTION(globalFuncOwnKeys, (JSGlobalObject* globalObject, CallFrame* callFrame))
+static bool canPerformFastPropertyEnumerationForCopyDataProperties(Structure* structure)
{
+ if (structure->typeInfo().overridesGetOwnPropertySlot())
+ return false;
+ if (structure->typeInfo().overridesAnyFormOfGetOwnPropertyNames())
+ return false;
+ // FIXME: Indexed properties can be handled.
+ // https://bugs.webkit.org/show_bug.cgi?id=185358
+ if (hasIndexedProperties(structure->indexingType()))
+ return false;
+ if (structure->hasGetterSetterProperties())
+ return false;
+ if (structure->hasCustomGetterSetterProperties())
+ return false;
+ if (structure->isUncacheableDictionary())
+ return false;
+ return true;
+};
+
+// https://tc39.es/ecma262/#sec-copydataproperties
+JSC_DEFINE_HOST_FUNCTION(globalFuncCopyDataProperties, (JSGlobalObject* globalObject, CallFrame* callFrame))
+{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
- JSObject* object = callFrame->argument(0).toObject(globalObject);
- RETURN_IF_EXCEPTION(scope, encodedJSValue());
- RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(globalObject, object, PropertyNameMode::StringsAndSymbols, DontEnumPropertiesMode::Include, WTF::nullopt)));
+
+ JSFinalObject* target = jsCast<JSFinalObject*>(callFrame->uncheckedArgument(0));
+ ASSERT(target->isStructureExtensible(vm));
+
+ JSValue sourceValue = callFrame->uncheckedArgument(1);
+ if (sourceValue.isUndefinedOrNull())
+ return JSValue::encode(jsUndefined());
+
+ JSObject* source = sourceValue.toObject(globalObject);
+ scope.assertNoException();
+
+ JSSet* excludedSet = nullptr;
+ if (callFrame->argumentCount() > 2)
+ excludedSet = jsCast<JSSet*>(callFrame->uncheckedArgument(2));
+
+ auto isPropertyNameExcluded = [&] (JSGlobalObject* globalObject, PropertyName propertyName) -> bool {
+ ASSERT(!propertyName.isPrivateName());
+ if (!excludedSet)
+ return false;
+
+ JSValue propertyNameValue = identifierToJSValue(vm, Identifier::fromUid(vm, propertyName.uid()));
+ RETURN_IF_EXCEPTION(scope, false);
+ return excludedSet->has(globalObject, propertyNameValue);
+ };
+
+ if (!source->staticPropertiesReified(vm)) {
+ source->reifyAllStaticProperties(globalObject);
+ RETURN_IF_EXCEPTION(scope, { });
+ }
+
+ if (canPerformFastPropertyEnumerationForCopyDataProperties(source->structure(vm))) {
+ Vector<RefPtr<UniquedStringImpl>, 8> properties;
+ MarkedArgumentBuffer values;
+
+ // FIXME: It doesn't seem like we should have to do this in two phases, but
+ // we're running into crashes where it appears that source is transitioning
+ // under us, and even ends up in a state where it has a null butterfly. My
+ // leading hypothesis here is that we fire some value replacement watchpoint
+ // that ends up transitioning the structure underneath us.
+ // https://bugs.webkit.org/show_bug.cgi?id=187837
+
+ source->structure(vm)->forEachProperty(vm, [&] (const PropertyMapEntry& entry) -> bool {
+ PropertyName propertyName(entry.key);
+ if (propertyName.isPrivateName())
+ return true;
+
+ bool excluded = isPropertyNameExcluded(globalObject, propertyName);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (excluded)
+ return true;
+ if (entry.attributes & PropertyAttribute::DontEnum)
+ return true;
+
+ properties.append(entry.key);
+ values.appendWithCrashOnOverflow(source->getDirect(entry.offset));
+ return true;
+ });
+
+ RETURN_IF_EXCEPTION(scope, { });
+
+ for (size_t i = 0; i < properties.size(); ++i) {
+ // FIXME: We could put properties in a batching manner to accelerate CopyDataProperties more.
+ // https://bugs.webkit.org/show_bug.cgi?id=185358
+ target->putDirect(vm, properties[i].get(), values.at(i));
+ }
+ } else {
+ PropertyNameArray propertyNames(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
+ source->methodTable(vm)->getOwnPropertyNames(source, globalObject, propertyNames, DontEnumPropertiesMode::Include);
+ RETURN_IF_EXCEPTION(scope, { });
+
+ for (const auto& propertyName : propertyNames) {
+ bool excluded = isPropertyNameExcluded(globalObject, propertyName);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (excluded)
+ continue;
+
+ PropertySlot slot(source, PropertySlot::InternalMethodType::GetOwnProperty);
+ bool hasProperty = source->methodTable(vm)->getOwnPropertySlot(source, globalObject, propertyName, slot);
+ RETURN_IF_EXCEPTION(scope, { });
+ if (!hasProperty)
+ continue;
+ if (slot.attributes() & PropertyAttribute::DontEnum)
+ continue;
+
+ JSValue value;
+ if (LIKELY(!slot.isTaintedByOpaqueObject()))
+ value = slot.getValue(globalObject, propertyName);
+ else
+ value = source->get(globalObject, propertyName);
+ RETURN_IF_EXCEPTION(scope, { });
+
+ target->putDirectMayBeIndex(globalObject, propertyName, value);
+ }
+ }
+
+ return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(globalFuncDateTimeFormat, (JSGlobalObject* globalObject, CallFrame* callFrame))
Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.h (271342 => 271343)
--- trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.h 2021-01-09 03:40:33 UTC (rev 271342)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.h 2021-01-09 04:31:12 UTC (rev 271343)
@@ -57,7 +57,7 @@
JSC_DECLARE_HOST_FUNCTION(globalFuncBuiltinDescribe);
JSC_DECLARE_HOST_FUNCTION(globalFuncImportModule);
JSC_DECLARE_HOST_FUNCTION(globalFuncPropertyIsEnumerable);
-JSC_DECLARE_HOST_FUNCTION(globalFuncOwnKeys);
+JSC_DECLARE_HOST_FUNCTION(globalFuncCopyDataProperties);
JSC_DECLARE_HOST_FUNCTION(globalFuncDateTimeFormat);
JS_EXPORT_PRIVATE double jsToNumber(StringView);