Ignore:
Timestamp:
Jan 8, 2021, 8:31:12 PM (4 years 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:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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
Note: See TracChangeset for help on using the changeset viewer.