Title: [271343] trunk
Revision
271343
Author
[email protected]
Date
2021-01-08 20:31:12 -0800 (Fri, 08 Jan 2021)

Log Message

Implement @copyDataProperties in C++ to optimize object rest / spread
https://bugs.webkit.org/show_bug.cgi?id=193618

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/object-rest-destructuring.js: Added.
* microbenchmarks/object-spread.js: Added.
* stress/object-rest-deconstruct.js:
* stress/object-spread.js:

Source/_javascript_Core:

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:

Modified Paths

Added Paths

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);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to