Changeset 271343 in webkit


Ignore:
Timestamp:
Jan 8, 2021 8:31:12 PM (19 months ago)
Author:
Alexey Shvayka
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/JavaScriptCore:

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:
Location:
trunk
Files:
2 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r271305 r271343  
     12021-01-08  Alexey Shvayka  <shvaikalesh@gmail.com>
     2
     3        Implement @copyDataProperties in C++ to optimize object rest / spread
     4        https://bugs.webkit.org/show_bug.cgi?id=193618
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        * microbenchmarks/object-rest-destructuring.js: Added.
     9        * microbenchmarks/object-spread.js: Added.
     10        * stress/object-rest-deconstruct.js:
     11        * stress/object-spread.js:
     12
    1132021-01-08  Alexey Shvayka  <shvaikalesh@gmail.com>
    214
  • trunk/JSTests/stress/object-rest-deconstruct.js

    r267440 r271343  
    44}
    55
    6 let assertPropDescriptor = (restObj, prop) => {
     6function shouldBe(actual, expected) {
     7    if (actual !== expected)
     8        throw new Error(`Bad value: ${actual}!`);
     9}
     10
     11function assertDataProperty(restObj, prop, value) {
     12    shouldBe(restObj[prop], value);
    713    let desc = Object.getOwnPropertyDescriptor(restObj, prop);
     14    shouldBe(desc.value, value);
    815    assert(desc.enumerable);
    916    assert(desc.writable);
     
    2027    assert(b === 3);
    2128
    22     assert(rest.x === 1);
    23     assert(rest.y === 2);
    24 
    25     assertPropDescriptor(rest, 'x');
    26     assertPropDescriptor(rest, 'y');
     29    assertDataProperty(rest, 'x', 1);
     30    assertDataProperty(rest, 'y', 2);
    2731})();
    2832
     
    102106    assert(b === 4);
    103107
    104     assert(rest.x === 3);
    105     assertPropDescriptor(rest, 'x');
     108    assertDataProperty(rest, 'x', 3);
    106109})();
    107110
     
    129132    assert(rest.b === 4);
    130133
    131     assertPropDescriptor(rest, 'a');
    132     assertPropDescriptor(rest, 'b');
     134    assertDataProperty(rest, 'a', 3);
     135    assertDataProperty(rest, 'b', 4);
    133136})();
    134137
     
    236239})();
    237240
     241(function nonEnumerableSymbol() {
     242    var __s0 = Symbol("s0");
     243    var __s1 = Symbol("s1");
     244
     245    var source = {};
     246    Object.defineProperties(source, {
     247        [__s0]: {value: 0, enumerable: false},
     248        [__s1]: {value: 1, enumerable: false},
     249    });
     250
     251    var {[__s0]: s0, ...target} = source;
     252
     253    shouldBe(s0, 0);
     254    assert(!Object.getOwnPropertySymbols(target).length);
     255})();
     256
     257(function dunderProto() {
     258    var source = {};
     259    var protoValue = {};
     260    Object.defineProperty(source, "__proto__", {value: protoValue, enumerable: true});
     261    var {...target} = source;
     262
     263    assertDataProperty(target, "__proto__", protoValue);
     264    shouldBe(Object.getPrototypeOf(target), Object.prototype);
     265})();
     266
     267(function stringPrimitive() {
     268    var source = "012";
     269    var {0: a, ["2"]: c, ...target} = source;
     270
     271    shouldBe(a, "0");
     272    shouldBe(c, "2");
     273    assertDataProperty(target, 1, "1");
     274    shouldBe(Object.keys(target).join(), "1");
     275})();
     276
     277(function ProxyObject() {
     278    var __s0 = Symbol("s0");
     279    var __s1 = Symbol("s1");
     280
     281    var ownKeysCalls = 0;
     282    var gopdCalls = [];
     283    var getCalls = [];
     284
     285    var source = new Proxy({
     286        [__s0]: "s0", [__s1]: "s1",
     287        a: 0, b: 1, c: 2, d: 3,
     288    }, {
     289        ownKeys: (t) => {
     290            ++ownKeysCalls;
     291            return Reflect.ownKeys(t);
     292        },
     293        getOwnPropertyDescriptor: (t, key) => {
     294            gopdCalls.push(key);
     295            var desc = Reflect.getOwnPropertyDescriptor(t, key);
     296            if (key === "b" || key === __s0)
     297                desc.enumerable = false;
     298            return desc;
     299        },
     300        get: (t, key, receiver) => {
     301            getCalls.push(key);
     302            return Reflect.get(t, key, receiver);
     303        },
     304    });
     305
     306    var {c, ["d"]: d, ...target} = source;
     307
     308    shouldBe(ownKeysCalls, 1);
     309    shouldBe(gopdCalls.map(String).join(), "a,b,Symbol(s0),Symbol(s1)");
     310    shouldBe(getCalls.map(String).join(), "c,d,a,Symbol(s1)");
     311
     312    assertDataProperty(target, "a", 0);
     313    shouldBe(c, 2);
     314    shouldBe(d, 3);
     315    shouldBe(Object.keys(target).join(), "a");
     316
     317    var symbols = Object.getOwnPropertySymbols(target);
     318    shouldBe(symbols[0], __s1);
     319    shouldBe(symbols.length, 1);
     320})();
     321
     322(function indexedProperties() {
     323    var source = [0, 1, 2, 3];
     324    Object.defineProperty(source, "1", {enumerable: false});
     325    var {2: c, ["3"]: d, ...target} = source;
     326
     327    assertDataProperty(target, "0", 0);
     328    shouldBe(Object.keys(target).join(), "0");
     329})();
     330
     331(function CustomAccessor() {
     332    var source = $vm.createCustomTestGetterSetter();
     333    var {customValueGlobalObject, ...target} = source;
     334
     335    assertDataProperty(target, "customValue", source);
     336    assert(!target.hasOwnProperty("customValueGlobalObject"));
     337    shouldBe(customValueGlobalObject[Symbol.toStringTag], "global");
     338    assertDataProperty(target, "customAccessor", source);
     339    assertDataProperty(target, "customAccessorGlobalObject", customValueGlobalObject);
     340})();
     341
     342(function CustomAccessorInNonReifiedPropertyTable() {
     343    // make sure we reifyAllStaticProperties() before deciding on the fast/slow path
     344
     345    var source = $vm.createStaticCustomAccessor();
     346    source.testField = 42;
     347    var {...target} = source;
     348
     349    assertDataProperty(target, "testField", 42);
     350    assertDataProperty(target, "testStaticAccessor", 42);
     351})();
     352
     353(function uncacheableDictionary() {
     354    var source = {a: 0, b: 1, c: 2, d: 3};
     355    Object.defineProperty(source, "c", {enumerable: false});
     356    $vm.toUncacheableDictionary(source);
     357    var {b, ["d"]: d, ...target} = source;
     358
     359    assertDataProperty(target, "a", 0);
     360    shouldBe(b, 1);
     361    shouldBe(d, 3);
     362    shouldBe(Object.keys(target).join(), "a");
     363})();
  • trunk/JSTests/stress/object-spread.js

    r219443 r271343  
    88}
    99
    10 function validatePropertyDescriptor(o, p) {
    11     let desc = Object.getOwnPropertyDescriptor(o, p);
    12 
     10function shouldBe(actual, expected) {
     11    if (actual !== expected)
     12        throw new Error(`Bad value: ${actual}!`);
     13}
     14
     15function assertDataProperty(target, prop, value) {
     16    shouldBe(target[prop], value);
     17    let desc = Object.getOwnPropertyDescriptor(target, prop);
     18    shouldBe(desc.value, value);
    1319    assert(desc.enumerable);
     20    assert(desc.writable);
    1421    assert(desc.configurable);
    15     assert(desc.writable);
    1622}
    1723
     
    2329    assert.sameValue(obj.a, 1);
    2430    assert(obj.b, 2);
    25     assert(obj.c, 3);
    26     assert(obj.d, 4);
    27     validatePropertyDescriptor(obj, "c");
    28     validatePropertyDescriptor(obj, "d");
     31    assertDataProperty(obj, "c", 3);
     32    assertDataProperty(obj, "d", 4);
    2933    assert(Object.keys(obj), 2);
    3034})();
     
    3438    let obj = {a: 1, b: 2, ...o};
    3539
    36     assert.sameValue(obj.a, 1);
    37     assert.sameValue(obj.b, 2);
    38     assert.sameValue(obj.c, 3);
    39     assert.sameValue(obj.d, 4);
     40    assertDataProperty(obj, "a", 1);
     41    assertDataProperty(obj, "b", 2);
     42    assertDataProperty(obj, "c", 3);
     43    assertDataProperty(obj, "d", 4);
    4044    assert.sameValue(Object.keys(obj).length, 4);
    41 
    42     validatePropertyDescriptor(obj, "a");
    43     validatePropertyDescriptor(obj, "b");
    44     validatePropertyDescriptor(obj, "c");
    45     validatePropertyDescriptor(obj, "d");
    4645})();
    4746
     
    129128    let obj = {...o, c: 4, d: 5};
    130129
    131     assert.sameValue(Object.getOwnPropertyDescriptor(obj, "a").value, 42);
     130    assertDataProperty(obj, "a", 42);
    132131    assert.sameValue(obj.c, 4);
    133132    assert.sameValue(obj.d, 5);
    134133    assert.sameValue(Object.keys(obj).length, 3);
    135 
    136     validatePropertyDescriptor(obj, "a");
    137134})();
    138135
     
    224221    let obj = {...o, a: 3};
    225222
    226     assert.sameValue(obj.a, 3)
    227     assert.sameValue(obj.b, 2);
    228     validatePropertyDescriptor(obj, "a");
    229     validatePropertyDescriptor(obj, "b");
     223    assertDataProperty(obj, "a", 3);
     224    assertDataProperty(obj, "b", 2);
    230225})();
    231226
     
    312307})();
    313308
     309(function nonEnumerableSymbol() {
     310    var __s0 = Symbol("s0");
     311    var source = {};
     312    Object.defineProperties(source, {
     313        [__s0]: {value: 0, enumerable: false},
     314    });
     315
     316    var target = {[__s0]: 1, ...target};
     317    assertDataProperty(target, __s0, 1);
     318})();
     319
     320(function dunderProto() {
     321    var source = {};
     322    var protoValue = {};
     323    Object.defineProperty(source, "__proto__", {value: protoValue, enumerable: true});
     324    var target = {...source};
     325
     326    assertDataProperty(target, "__proto__", protoValue);
     327    shouldBe(Object.getPrototypeOf(target), Object.prototype);
     328})();
     329
     330(function stringPrimitive() {
     331    var source = "012";
     332    var target = {get 0() {}, ["2"]: 22, ...source};
     333
     334    assertDataProperty(target, 0, "0");
     335    assertDataProperty(target, 1, "1");
     336    assertDataProperty(target, 2, "2");
     337    shouldBe(Object.keys(target).join(), "0,1,2");
     338})();
     339
     340(function ProxyObject() {
     341    var __s0 = Symbol("s0");
     342    var __s1 = Symbol("s1");
     343
     344    var ownKeysCalls = 0;
     345    var gopdCalls = [];
     346    var getCalls = [];
     347
     348    var source = new Proxy({
     349        [__s0]: "s0", [__s1]: "s1",
     350        a: 0, b: 1, c: 2, d: 3,
     351    }, {
     352        ownKeys: (t) => {
     353            ++ownKeysCalls;
     354            return Reflect.ownKeys(t);
     355        },
     356        getOwnPropertyDescriptor: (t, key) => {
     357            gopdCalls.push(key);
     358            var desc = Reflect.getOwnPropertyDescriptor(t, key);
     359            if (key === "b" || key === __s0)
     360                desc.enumerable = false;
     361            return desc;
     362        },
     363        get: (t, key, receiver) => {
     364            getCalls.push(key);
     365            return Reflect.get(t, key, receiver);
     366        },
     367    });
     368
     369    var target = {a() {}, b: 11, [__s1]: "foo", ...source, ["d"]: 33};
     370
     371    shouldBe(ownKeysCalls, 1);
     372    shouldBe(gopdCalls.map(String).join(), "a,b,c,d,Symbol(s0),Symbol(s1)");
     373    shouldBe(getCalls.map(String).join(), "a,c,d,Symbol(s1)");
     374
     375    assertDataProperty(target, "a", 0);
     376    assertDataProperty(target, "b", 11);
     377    assertDataProperty(target, "c", 2);
     378    assertDataProperty(target, "d", 33);
     379
     380    shouldBe(Reflect.ownKeys(target).map(String).join(), "a,b,c,d,Symbol(s1)");
     381})();
     382
     383(function indexedProperties() {
     384    var source = [0, 1, 2];
     385    Object.defineProperty(source, "1", {enumerable: false});
     386    var target = {set ["2"](_v) {}, ...source};
     387
     388    assertDataProperty(target, "0", 0);
     389    assertDataProperty(target, "2", 2);
     390    shouldBe(Object.keys(target).join(), "0,2");
     391})();
     392
     393(function CustomAccessor() {
     394    var source = $vm.createCustomTestGetterSetter();
     395    var target = {...source};
     396
     397    assertDataProperty(target, "customValue", source);
     398    shouldBe(target.customValueGlobalObject, target.customAccessorGlobalObject);
     399    shouldBe(target.customValueGlobalObject[Symbol.toStringTag], "global");
     400    assertDataProperty(target, "customAccessor", source);
     401})();
     402
     403(function CustomAccessorInNonReifiedPropertyTable() {
     404    // make sure we reifyAllStaticProperties() before deciding on the fast/slow path
     405
     406    var source = $vm.createStaticCustomAccessor();
     407    source.testField = 42;
     408    var target = {...source};
     409
     410    assertDataProperty(target, "testField", 42);
     411    assertDataProperty(target, "testStaticAccessor", 42);
     412})();
     413
     414(function uncacheableDictionary() {
     415    var source = {a: 0, b: 1, c: 2};
     416    Object.defineProperty(source, "c", {enumerable: false});
     417    $vm.toUncacheableDictionary(source);
     418    var b = 11;
     419    var target = {get a() {}, b, ...source};
     420
     421    assertDataProperty(target, "a", 0);
     422    assertDataProperty(target, "b", 1);
     423    shouldBe(Object.keys(target).join(), "a,b");
     424})();
  • trunk/Source/JavaScriptCore/ChangeLog

    r271332 r271343  
     12021-01-08  Alexey Shvayka  <shvaikalesh@gmail.com>
     2
     3        Implement @copyDataProperties in C++ to optimize object rest / spread
     4        https://bugs.webkit.org/show_bug.cgi?id=193618
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        Since @copyDataProperties is inherently polymorphic, implementing it in JS is not beneficial.
     9        This patch:
     10
     11        1. Merges almost identical @copyDataProperties variants and moves them to C++, avoiding
     12           allocations of JSArray instances and Identifier wrappers.
     13        2. Skips non-observable [[Get]] calls, leveraging `slot.isTaintedByOpaqueObject()`.
     14        3. Performs [[DefineOwnProperty]] via putDirectMayBeIndex(), since the spec guarantees
     15           property creation to be successful [1]: `target` is an newly created object that is
     16           not yet accessible to userland code. It's impossible for `target` to be non-extensible
     17           nor have a non-configurable property.
     18        4. Introduces a fast path similar to Object.assign, but:
     19           a) with no checks on `target`, because it's guaranteed to be an extensible JSFinalObject;
     20           b) with less checks on `source`, since we are performing putDirect() and don't care about
     21              read-only properties nor __proto__.
     22
     23        Altogether, these changes result in 3.1x speed-up for object rest / spread.
     24        Also, this patch removes unnecessary `target` return and @isObject check.
     25
     26        [1]: https://tc39.es/ecma262/#sec-copydataproperties (step 6.c.ii.2, note the "!" prefix)
     27
     28        * builtins/BuiltinNames.h:
     29        * builtins/GlobalOperations.js:
     30        (globalPrivate.speciesConstructor):
     31        (globalPrivate.copyDataProperties): Deleted.
     32        (globalPrivate.copyDataPropertiesNoExclusions): Deleted.
     33        * bytecode/BytecodeIntrinsicRegistry.h:
     34        * bytecode/LinkTimeConstant.h:
     35        * bytecompiler/NodesCodegen.cpp:
     36        (JSC::ObjectPatternNode::bindValue const):
     37        (JSC::ObjectSpreadExpressionNode::emitBytecode):
     38        (JSC::BytecodeIntrinsicNode::emit_intrinsic_defineEnumerableWritableConfigurableDataProperty): Deleted.
     39        * runtime/JSGlobalObject.cpp:
     40        (JSC::JSGlobalObject::init):
     41        * runtime/JSGlobalObjectFunctions.cpp:
     42        (JSC::canPerformFastPropertyEnumerationForCopyDataProperties):
     43        (JSC::JSC_DEFINE_HOST_FUNCTION):
     44        * runtime/JSGlobalObjectFunctions.h:
     45
    1462021-01-08  Yusuke Suzuki  <ysuzuki@apple.com>
    247
  • trunk/Source/JavaScriptCore/builtins/BuiltinNames.h

    r269531 r271343  
    6868    macro(defaultPromiseThen) \
    6969    macro(getOwnPropertyNames) \
    70     macro(ownKeys) \
    7170    macro(Set) \
    7271    macro(throwTypeErrorFunction) \
     
    170169    macro(importModule) \
    171170    macro(propertyIsEnumerable) \
     171    macro(copyDataProperties) \
    172172    macro(meta) \
    173173    macro(webAssemblyCompileStreamingInternal) \
  • trunk/Source/JavaScriptCore/builtins/GlobalOperations.js

    r262017 r271343  
    7676    @throwTypeError("|this|.constructor[Symbol.species] is not a constructor");
    7777}
    78 
    79 @globalPrivate
    80 function copyDataProperties(target, source, excludedSet)
    81 {
    82     "use strict";
    83 
    84     if (!@isObject(target))
    85         @throwTypeError("target needs to be an object");
    86 
    87     if (@isUndefinedOrNull(source))
    88         return target;
    89 
    90     var from = @toObject(source);
    91     var keys = @ownKeys(from);
    92     var keysLength = keys.length;
    93     for (var i = 0; i < keysLength; i++) {
    94         var nextKey = keys[i];
    95         if (!excludedSet.@has(nextKey)) {
    96             if (@propertyIsEnumerable(from, nextKey)) {
    97                 var propValue = from[nextKey];
    98                 @defineEnumerableWritableConfigurableDataProperty(target, nextKey, propValue);
    99             }
    100         }
    101     }
    102 
    103     return target;
    104 }
    105 
    106 @globalPrivate
    107 function copyDataPropertiesNoExclusions(target, source)
    108 {
    109     "use strict";
    110 
    111     if (!@isObject(target))
    112         @throwTypeError("target needs to be an object");
    113 
    114     if (@isUndefinedOrNull(source))
    115         return target;
    116 
    117     var from = @toObject(source);
    118     var keys = @ownKeys(from);
    119     var keysLength = keys.length;
    120     for (var i = 0; i < keysLength; i++) {
    121         var nextKey = keys[i];
    122         if (@propertyIsEnumerable(from, nextKey)) {
    123             var propValue = from[nextKey];
    124             @defineEnumerableWritableConfigurableDataProperty(target, nextKey, propValue);
    125         }
    126     }
    127 
    128     return target;
    129 }
  • trunk/Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h

    r268489 r271343  
    9393    macro(createPromise) \
    9494    macro(createArgumentsButterfly) \
    95     macro(defineEnumerableWritableConfigurableDataProperty) \
    9695
    9796#define JSC_COMMON_BYTECODE_INTRINSIC_CONSTANTS_EACH_NAME(macro) \
  • trunk/Source/JavaScriptCore/bytecode/LinkTimeConstant.h

    r269531 r271343  
    4646    v(setPrototypeDirect, nullptr) \
    4747    v(propertyIsEnumerable, nullptr) \
    48     v(ownKeys, nullptr) \
     48    v(copyDataProperties, nullptr) \
    4949    v(enqueueJob, nullptr) \
    5050    v(makeTypeError, nullptr) \
  • trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

    r271265 r271343  
    17771777    ASSERT(!m_args->m_listNode);
    17781778    return generator.emitCreateArgumentsButterfly(generator.finalDestination(dst));
    1779 }
    1780 
    1781 RegisterID* BytecodeIntrinsicNode::emit_intrinsic_defineEnumerableWritableConfigurableDataProperty(JSC::BytecodeGenerator& generator, JSC::RegisterID* dst)
    1782 {
    1783     ArgumentListNode* node = m_args->m_listNode;
    1784     RefPtr<RegisterID> newObj = generator.emitNode(node);
    1785     node = node->m_next;
    1786     RefPtr<RegisterID> propertyNameRegister = generator.emitNode(node);
    1787     node = node->m_next;
    1788     RefPtr<RegisterID> value = generator.emitNode(node);
    1789     ASSERT(!node->m_next);
    1790 
    1791     generator.emitCallDefineProperty(newObj.get(), propertyNameRegister.get(), value.get(), nullptr, nullptr, BytecodeGenerator::PropertyConfigurable | BytecodeGenerator::PropertyWritable | BytecodeGenerator::PropertyEnumerable, m_position);
    1792     return dst;
    17931779}
    17941780
     
    53595345            }
    53605346
    5361             RefPtr<RegisterID> result = generator.newTemporary();
    5362             generator.emitCall(result.get(), copyDataProperties.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd(), DebuggableCall::No);
    5363             target.pattern->bindValue(generator, result.get());
     5347            generator.emitCall(generator.newTemporary(), copyDataProperties.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd(), DebuggableCall::No);
     5348            target.pattern->bindValue(generator, newObject.get());
    53645349        }
    53655350    }
     
    55705555    generator.emitNode(src.get(), m_expression);
    55715556   
    5572     // load and call @copyDataPropertiesNoExclusions
    5573     RefPtr<RegisterID> copyDataProperties = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::copyDataPropertiesNoExclusions);
     5557    RefPtr<RegisterID> copyDataProperties = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::copyDataProperties);
    55745558   
    55755559    CallArguments args(generator, nullptr, 2);
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObject.cpp

    r269574 r271343  
    11901190            init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, String(), globalFuncPropertyIsEnumerable));
    11911191        });
    1192     m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::ownKeys)].initLater([] (const Initializer<JSCell>& init) {
    1193             init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, String(), globalFuncOwnKeys));
     1192    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::copyDataProperties)].initLater([] (const Initializer<JSCell>& init) {
     1193            init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 2, String(), globalFuncCopyDataProperties));
    11941194        });
    11951195    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::enqueueJob)].initLater([] (const Initializer<JSCell>& init) {
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp

    r267594 r271343  
    840840}
    841841
    842 JSC_DEFINE_HOST_FUNCTION(globalFuncOwnKeys, (JSGlobalObject* globalObject, CallFrame* callFrame))
    843 {
    844     VM& vm = globalObject->vm();
    845     auto scope = DECLARE_THROW_SCOPE(vm);
    846     JSObject* object = callFrame->argument(0).toObject(globalObject);
    847     RETURN_IF_EXCEPTION(scope, encodedJSValue());
    848     RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(globalObject, object, PropertyNameMode::StringsAndSymbols, DontEnumPropertiesMode::Include, WTF::nullopt)));
     842static bool canPerformFastPropertyEnumerationForCopyDataProperties(Structure* structure)
     843{
     844    if (structure->typeInfo().overridesGetOwnPropertySlot())
     845        return false;
     846    if (structure->typeInfo().overridesAnyFormOfGetOwnPropertyNames())
     847        return false;
     848    // FIXME: Indexed properties can be handled.
     849    // https://bugs.webkit.org/show_bug.cgi?id=185358
     850    if (hasIndexedProperties(structure->indexingType()))
     851        return false;
     852    if (structure->hasGetterSetterProperties())
     853        return false;
     854    if (structure->hasCustomGetterSetterProperties())
     855        return false;
     856    if (structure->isUncacheableDictionary())
     857        return false;
     858    return true;
     859};
     860
     861// https://tc39.es/ecma262/#sec-copydataproperties
     862JSC_DEFINE_HOST_FUNCTION(globalFuncCopyDataProperties, (JSGlobalObject* globalObject, CallFrame* callFrame))
     863{
     864    VM& vm = globalObject->vm();
     865    auto scope = DECLARE_THROW_SCOPE(vm);
     866
     867    JSFinalObject* target = jsCast<JSFinalObject*>(callFrame->uncheckedArgument(0));
     868    ASSERT(target->isStructureExtensible(vm));
     869
     870    JSValue sourceValue = callFrame->uncheckedArgument(1);
     871    if (sourceValue.isUndefinedOrNull())
     872        return JSValue::encode(jsUndefined());
     873
     874    JSObject* source = sourceValue.toObject(globalObject);
     875    scope.assertNoException();
     876
     877    JSSet* excludedSet = nullptr;
     878    if (callFrame->argumentCount() > 2)
     879        excludedSet = jsCast<JSSet*>(callFrame->uncheckedArgument(2));
     880
     881    auto isPropertyNameExcluded = [&] (JSGlobalObject* globalObject, PropertyName propertyName) -> bool {
     882        ASSERT(!propertyName.isPrivateName());
     883        if (!excludedSet)
     884            return false;
     885
     886        JSValue propertyNameValue = identifierToJSValue(vm, Identifier::fromUid(vm, propertyName.uid()));
     887        RETURN_IF_EXCEPTION(scope, false);
     888        return excludedSet->has(globalObject, propertyNameValue);
     889    };
     890
     891    if (!source->staticPropertiesReified(vm)) {
     892        source->reifyAllStaticProperties(globalObject);
     893        RETURN_IF_EXCEPTION(scope, { });
     894    }
     895
     896    if (canPerformFastPropertyEnumerationForCopyDataProperties(source->structure(vm))) {
     897        Vector<RefPtr<UniquedStringImpl>, 8> properties;
     898        MarkedArgumentBuffer values;
     899
     900        // FIXME: It doesn't seem like we should have to do this in two phases, but
     901        // we're running into crashes where it appears that source is transitioning
     902        // under us, and even ends up in a state where it has a null butterfly. My
     903        // leading hypothesis here is that we fire some value replacement watchpoint
     904        // that ends up transitioning the structure underneath us.
     905        // https://bugs.webkit.org/show_bug.cgi?id=187837
     906
     907        source->structure(vm)->forEachProperty(vm, [&] (const PropertyMapEntry& entry) -> bool {
     908            PropertyName propertyName(entry.key);
     909            if (propertyName.isPrivateName())
     910                return true;
     911
     912            bool excluded = isPropertyNameExcluded(globalObject, propertyName);
     913            RETURN_IF_EXCEPTION(scope, false);
     914            if (excluded)
     915                return true;
     916            if (entry.attributes & PropertyAttribute::DontEnum)
     917                return true;
     918
     919            properties.append(entry.key);
     920            values.appendWithCrashOnOverflow(source->getDirect(entry.offset));
     921            return true;
     922        });
     923
     924        RETURN_IF_EXCEPTION(scope, { });
     925
     926        for (size_t i = 0; i < properties.size(); ++i) {
     927            // FIXME: We could put properties in a batching manner to accelerate CopyDataProperties more.
     928            // https://bugs.webkit.org/show_bug.cgi?id=185358
     929            target->putDirect(vm, properties[i].get(), values.at(i));
     930        }
     931    } else {
     932        PropertyNameArray propertyNames(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
     933        source->methodTable(vm)->getOwnPropertyNames(source, globalObject, propertyNames, DontEnumPropertiesMode::Include);
     934        RETURN_IF_EXCEPTION(scope, { });
     935
     936        for (const auto& propertyName : propertyNames) {
     937            bool excluded = isPropertyNameExcluded(globalObject, propertyName);
     938            RETURN_IF_EXCEPTION(scope, false);
     939            if (excluded)
     940                continue;
     941
     942            PropertySlot slot(source, PropertySlot::InternalMethodType::GetOwnProperty);
     943            bool hasProperty = source->methodTable(vm)->getOwnPropertySlot(source, globalObject, propertyName, slot);
     944            RETURN_IF_EXCEPTION(scope, { });
     945            if (!hasProperty)
     946                continue;
     947            if (slot.attributes() & PropertyAttribute::DontEnum)
     948                continue;
     949
     950            JSValue value;
     951            if (LIKELY(!slot.isTaintedByOpaqueObject()))
     952                value = slot.getValue(globalObject, propertyName);
     953            else
     954                value = source->get(globalObject, propertyName);
     955            RETURN_IF_EXCEPTION(scope, { });
     956
     957            target->putDirectMayBeIndex(globalObject, propertyName, value);
     958        }
     959    }
     960
     961    return JSValue::encode(jsUndefined());
    849962}
    850963
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.h

    r268640 r271343  
    5858JSC_DECLARE_HOST_FUNCTION(globalFuncImportModule);
    5959JSC_DECLARE_HOST_FUNCTION(globalFuncPropertyIsEnumerable);
    60 JSC_DECLARE_HOST_FUNCTION(globalFuncOwnKeys);
     60JSC_DECLARE_HOST_FUNCTION(globalFuncCopyDataProperties);
    6161JSC_DECLARE_HOST_FUNCTION(globalFuncDateTimeFormat);
    6262
Note: See TracChangeset for help on using the changeset viewer.