⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 287800 in webkit


Ignore:
Timestamp:
Jan 7, 2022, 5:52:18 PM (5 years ago)
Author:
commit-queue@webkit.org
Message:

Expand the set of objects we take JSArray::fastSlice() path for
https://bugs.webkit.org/show_bug.cgi?id=234539

Patch by Alexey Shvayka <ashvayka@apple.com> on 2022-01-07
Reviewed by Yusuke Suzuki.

JSTests:

  • microbenchmarks/array-slice-call-cloned-arguments.js: Added.
  • stress/array-slice-beyond-length.js: Added.
  • stress/array-slice-length-lookup.js: Added.

Source/JavaScriptCore:

Currently, Array.prototype's slice() / splice() methods take a fast path only for
JSArray source objects. With this change, gcSafeMemcpy-based path is taken for any
object with ordinary getOwnPropertySlotByIndex() method, which speeds up the common
case of [].slice.call(arguments) by 140% (in strict mode only, see ClonedArguments).

Also, once is https://webkit.org/b/234538 resolved, calling Array.prototype.slice()
on a static NodeList, which is a common idiom to acquire map() / filter() methods,
will become faster as well.

This patch was thoroughly evaluated to be spec-perfect and memory-safe:

  • indexing mode check and holesMustForwardToPrototype() guarantee that there are no observable userland code to be invoked;
  • fastSlice() signature is upgraded to uint64_t so nullptr is returned in case of large "length", resulting in a RangeError being thrown on the slow path;
  • to handle the case of source array being shrinked after "length" lookup (see r175420), OOB read check is moved to JSArray::fastSlice() and refined to rely on vectorLength() so the double "length" lookup is avoided (added a test for this).

All this (and more) is well covered by the test262 suite.

This change improves Speedometer2/EmberJS-Debug-TodoMVC score by 0.5%: although the test
is slow on its own, [].slice.call(arguments) is performed ~56k times per run.

  • runtime/ArrayPrototype.cpp:

(JSC::JSC_DEFINE_HOST_FUNCTION):

  • runtime/JSArray.cpp:

(JSC::JSArray::fastSlice):

  • runtime/JSArray.h:
Location:
trunk
Files:
3 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r287738 r287800  
     12022-01-07  Alexey Shvayka  <ashvayka@apple.com>
     2
     3        Expand the set of objects we take JSArray::fastSlice() path for
     4        https://bugs.webkit.org/show_bug.cgi?id=234539
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        * microbenchmarks/array-slice-call-cloned-arguments.js: Added.
     9        * stress/array-slice-beyond-length.js: Added.
     10        * stress/array-slice-length-lookup.js: Added.
     11
    1122022-01-06  Saam Barati  <sbarati@apple.com>
    213
  • trunk/Source/JavaScriptCore/ChangeLog

    r287784 r287800  
     12022-01-07  Alexey Shvayka  <ashvayka@apple.com>
     2
     3        Expand the set of objects we take JSArray::fastSlice() path for
     4        https://bugs.webkit.org/show_bug.cgi?id=234539
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        Currently, Array.prototype's slice() / splice() methods take a fast path only for
     9        JSArray source objects. With this change, gcSafeMemcpy-based path is taken for any
     10        object with ordinary getOwnPropertySlotByIndex() method, which speeds up the common
     11        case of `[].slice.call(arguments)` by 140% (in strict mode only, see ClonedArguments).
     12
     13        Also, once is https://webkit.org/b/234538 resolved, calling Array.prototype.slice()
     14        on a static NodeList, which is a common idiom to acquire map() / filter() methods,
     15        will become faster as well.
     16
     17        This patch was thoroughly evaluated to be spec-perfect and memory-safe:
     18
     19          - indexing mode check and holesMustForwardToPrototype() guarantee that there
     20            are no observable userland code to be invoked;
     21          - fastSlice() signature is upgraded to uint64_t so `nullptr` is returned in case
     22            of large "length", resulting in a RangeError being thrown on the slow path;
     23          - to handle the case of source array being shrinked after "length" lookup (see r175420),
     24            OOB read check is moved to JSArray::fastSlice() and refined to rely on vectorLength()
     25            so the double "length" lookup is avoided (added a test for this).
     26
     27        All this (and more) is well covered by the test262 suite.
     28
     29        This change improves Speedometer2/EmberJS-Debug-TodoMVC score by 0.5%: although the test
     30        is slow on its own, `[].slice.call(arguments)` is performed ~56k times per run.
     31
     32        * runtime/ArrayPrototype.cpp:
     33        (JSC::JSC_DEFINE_HOST_FUNCTION):
     34        * runtime/JSArray.cpp:
     35        (JSC::JSArray::fastSlice):
     36        * runtime/JSArray.h:
     37
    1382022-01-07  Tim Horton  <timothy_horton@apple.com>
    239
  • trunk/Source/JavaScriptCore/runtime/ArrayPrototype.cpp

    r287561 r287800  
    11401140        return { };
    11411141
    1142     bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(globalObject, thisObj);
    1143     RETURN_IF_EXCEPTION(scope, { });
    1144     if (LIKELY(okToDoFastPath)) {
    1145         if (JSArray* result = asArray(thisObj)->fastSlice(globalObject, static_cast<uint32_t>(begin), static_cast<uint32_t>(end - begin)))
     1142    if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath)) {
     1143        if (JSArray* result = JSArray::fastSlice(globalObject, thisObj, begin, end - begin))
    11461144            return JSValue::encode(result);
    11471145    }
     
    12361234
    12371235    JSObject* result = nullptr;
    1238     bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(globalObject, thisObj);
    1239     RETURN_IF_EXCEPTION(scope, encodedJSValue());
    1240     if (LIKELY(okToDoFastPath))
    1241         result = asArray(thisObj)->fastSlice(globalObject, static_cast<uint32_t>(actualStart), static_cast<uint32_t>(actualDeleteCount));
     1236    if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath))
     1237        result = JSArray::fastSlice(globalObject, thisObj, actualStart, actualDeleteCount);
    12421238
    12431239    if (!result) {
  • trunk/Source/JavaScriptCore/runtime/JSArray.cpp

    r286572 r287800  
    726726}
    727727
    728 JSArray* JSArray::fastSlice(JSGlobalObject* globalObject, unsigned startIndex, unsigned count)
    729 {
    730     VM& vm = globalObject->vm();
    731 
    732     ensureWritable(vm);
    733 
    734     auto arrayType = indexingMode();
     728JSArray* JSArray::fastSlice(JSGlobalObject* globalObject, JSObject* source, uint64_t startIndex, uint64_t count)
     729{
     730    VM& vm = globalObject->vm();
     731
     732    // FIXME: Avoid converting the source from CoW since we aren't modifying it.
     733    // https://bugs.webkit.org/show_bug.cgi?id=234990
     734    source->ensureWritable(vm);
     735
     736    Structure* sourceStructure = source->structure(vm);
     737    if (sourceStructure->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero())
     738        return nullptr;
     739
     740    auto arrayType = source->indexingMode() | IsArray;
    735741    switch (arrayType) {
    736742    case ArrayWithDouble:
    737743    case ArrayWithInt32:
    738744    case ArrayWithContiguous: {
    739         if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm, this))
     745        if (count >= MIN_SPARSE_ARRAY_INDEX || sourceStructure->holesMustForwardToPrototype(vm, source))
     746            return nullptr;
     747
     748        if (startIndex + count > source->butterfly()->vectorLength())
    740749            return nullptr;
    741750
     
    746755        ASSERT(!globalObject->isHavingABadTime());
    747756        ObjectInitializationScope scope(vm);
    748         JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, count);
     757        JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, static_cast<uint32_t>(count));
    749758        if (UNLIKELY(!resultArray))
    750759            return nullptr;
     
    752761        auto& resultButterfly = *resultArray->butterfly();
    753762        if (arrayType == ArrayWithDouble)
    754             gcSafeMemcpy(resultButterfly.contiguousDouble().data(), butterfly()->contiguousDouble().data() + startIndex, sizeof(JSValue) * count);
     763            gcSafeMemcpy(resultButterfly.contiguousDouble().data(), source->butterfly()->contiguousDouble().data() + startIndex, sizeof(JSValue) * static_cast<uint32_t>(count));
    755764        else
    756             gcSafeMemcpy(resultButterfly.contiguous().data(), butterfly()->contiguous().data() + startIndex, sizeof(JSValue) * count);
     765            gcSafeMemcpy(resultButterfly.contiguous().data(), source->butterfly()->contiguous().data() + startIndex, sizeof(JSValue) * static_cast<uint32_t>(count));
    757766
    758767        ASSERT(resultButterfly.publicLength() == count);
  • trunk/Source/JavaScriptCore/runtime/JSArray.h

    r286572 r287800  
    105105    JS_EXPORT_PRIVATE JSValue pop(JSGlobalObject*);
    106106
    107     JSArray* fastSlice(JSGlobalObject*, unsigned startIndex, unsigned count);
     107    static JSArray* fastSlice(JSGlobalObject*, JSObject* source, uint64_t startIndex, uint64_t count);
    108108
    109109    bool canFastCopy(VM&, JSArray* otherArray);
Note: See TracChangeset for help on using the changeset viewer.