Ignore:
Timestamp:
Sep 29, 2017, 6:16:52 PM (8 years ago)
Author:
Yusuke Suzuki
Message:

[DFG] Support ArrayPush with multiple args
https://bugs.webkit.org/show_bug.cgi?id=175823

Reviewed by Saam Barati.

JSTests:

  • microbenchmarks/array-push-0.js: Added.

(arrayPush0):

  • microbenchmarks/array-push-1.js: Added.

(arrayPush1):

  • microbenchmarks/array-push-2.js: Added.

(arrayPush2):

  • microbenchmarks/array-push-3.js: Added.

(arrayPush3):

  • stress/array-push-multiple-contiguous.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-double-nan.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-double.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-int32.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-many-contiguous.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-many-double.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-many-int32.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-many-storage.js: Added.

(shouldBe):
(test):

  • stress/array-push-multiple-storage.js: Added.

(shouldBe):
(test):

  • stress/array-push-with-force-exit.js: Added.

(target.createBuiltin):

Source/JavaScriptCore:

Reviewed by Saam Barati.

This patch implements ArrayPush(with multiple arguments) in DFG and FTL. Previously, they are not handled
by ArrayPush. Then they go to generic direct call to Array#push and it does in slow path. This patch
extends ArrayPush to push multiple arguments in a bulk push manner.

The problem of ArrayPush is that we need to perform ArrayPush atomically: If OSR exit occurs in the middle
of ArrayPush, we incorrectly push pushed elements twice. Once we start pushing values, we should not exit.
But we do not want to iterate elements twice, once for type checks and once for actually pushing it. It
could move elements between registers and memory back and forth.

This patch achieves the above goal by separating type checks from ArrayPush. When starting ArrayPush, type
checks for elements are already done by separately emitted Check nodes.

We also add JSArray::pushInline for DFG operations just calling JSArray::push. And we also use it in
arrayProtoFuncPush's fast path.

This patch significantly improves performance of push(multiple args).

baseline patched

Microbenchmarks:

array-push-0 461.8455+-28.9995 151.3438+-6.5653 definitely 3.0516x faster
array-push-1 133.8845+-7.0349 ? 136.1775+-5.8327 ? might be 1.0171x slower
array-push-2 675.6555+-13.4645 145.8747+-6.4621 definitely 4.6318x faster
array-push-3 849.5284+-15.2540 253.4421+-9.1249 definitely 3.3520x faster

baseline patched

SixSpeed:

spread-literal.es5 90.3482+-6.6514 24.8123+-2.3304 definitely 3.6413x faster

  • dfg/DFGByteCodeParser.cpp:

(JSC::DFG::ByteCodeParser::handleIntrinsicCall):

  • dfg/DFGFixupPhase.cpp:

(JSC::DFG::FixupPhase::fixupNode):

  • dfg/DFGNodeType.h:
  • dfg/DFGOperations.cpp:
  • dfg/DFGOperations.h:
  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::compileArrayPush):

  • dfg/DFGSpeculativeJIT.h:

(JSC::DFG::SpeculativeJIT::callOperation):

  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGStoreBarrierInsertionPhase.cpp:
  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::compileArrayPush):

  • jit/JITOperations.h:
  • runtime/ArrayPrototype.cpp:

(JSC::arrayProtoFuncPush):

  • runtime/JSArray.cpp:

(JSC::JSArray::push):

  • runtime/JSArray.h:
  • runtime/JSArrayInlines.h:

(JSC::JSArray::pushInline):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/runtime/JSArrayInlines.h

    r222658 r222675  
    2020#pragma once
    2121
     22#include "Error.h"
    2223#include "JSArray.h"
    2324#include "JSCellInlines.h"
     
    8384}
    8485
     86ALWAYS_INLINE void JSArray::pushInline(ExecState* exec, JSValue value)
     87{
     88    VM& vm = exec->vm();
     89    auto scope = DECLARE_THROW_SCOPE(vm);
     90
     91    Butterfly* butterfly = m_butterfly.getMayBeNull();
     92
     93    switch (indexingType()) {
     94    case ArrayClass: {
     95        createInitialUndecided(vm, 0);
     96        FALLTHROUGH;
     97    }
     98
     99    case ArrayWithUndecided: {
     100        convertUndecidedForValue(vm, value);
     101        scope.release();
     102        push(exec, value);
     103        return;
     104    }
     105
     106    case ArrayWithInt32: {
     107        if (!value.isInt32()) {
     108            convertInt32ForValue(vm, value);
     109            scope.release();
     110            push(exec, value);
     111            return;
     112        }
     113
     114        unsigned length = butterfly->publicLength();
     115        ASSERT(length <= butterfly->vectorLength());
     116        if (length < butterfly->vectorLength()) {
     117            butterfly->contiguousInt32()[length].setWithoutWriteBarrier(value);
     118            butterfly->setPublicLength(length + 1);
     119            return;
     120        }
     121
     122        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
     123            methodTable(vm)->putByIndex(this, exec, length, value, true);
     124            if (!scope.exception())
     125                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
     126            return;
     127        }
     128
     129        scope.release();
     130        putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, length, value);
     131        return;
     132    }
     133
     134    case ArrayWithContiguous: {
     135        unsigned length = butterfly->publicLength();
     136        ASSERT(length <= butterfly->vectorLength());
     137        if (length < butterfly->vectorLength()) {
     138            butterfly->contiguous()[length].set(vm, this, value);
     139            butterfly->setPublicLength(length + 1);
     140            return;
     141        }
     142
     143        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
     144            methodTable(vm)->putByIndex(this, exec, length, value, true);
     145            if (!scope.exception())
     146                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
     147            return;
     148        }
     149
     150        scope.release();
     151        putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value);
     152        return;
     153    }
     154
     155    case ArrayWithDouble: {
     156        if (!value.isNumber()) {
     157            convertDoubleToContiguous(vm);
     158            scope.release();
     159            push(exec, value);
     160            return;
     161        }
     162        double valueAsDouble = value.asNumber();
     163        if (valueAsDouble != valueAsDouble) {
     164            convertDoubleToContiguous(vm);
     165            scope.release();
     166            push(exec, value);
     167            return;
     168        }
     169
     170        unsigned length = butterfly->publicLength();
     171        ASSERT(length <= butterfly->vectorLength());
     172        if (length < butterfly->vectorLength()) {
     173            butterfly->contiguousDouble()[length] = valueAsDouble;
     174            butterfly->setPublicLength(length + 1);
     175            return;
     176        }
     177
     178        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
     179            methodTable(vm)->putByIndex(this, exec, length, value, true);
     180            if (!scope.exception())
     181                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
     182            return;
     183        }
     184
     185        scope.release();
     186        putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, length, value);
     187        return;
     188    }
     189
     190    case ArrayWithSlowPutArrayStorage: {
     191        unsigned oldLength = length();
     192        bool putResult = false;
     193        if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) {
     194            if (!scope.exception() && oldLength < 0xFFFFFFFFu) {
     195                scope.release();
     196                setLength(exec, oldLength + 1, true);
     197            }
     198            return;
     199        }
     200        FALLTHROUGH;
     201    }
     202
     203    case ArrayWithArrayStorage: {
     204        ArrayStorage* storage = butterfly->arrayStorage();
     205
     206        // Fast case - push within vector, always update m_length & m_numValuesInVector.
     207        unsigned length = storage->length();
     208        if (length < storage->vectorLength()) {
     209            storage->m_vector[length].set(vm, this, value);
     210            storage->setLength(length + 1);
     211            ++storage->m_numValuesInVector;
     212            return;
     213        }
     214
     215        // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
     216        if (UNLIKELY(storage->length() > MAX_ARRAY_INDEX)) {
     217            methodTable(vm)->putByIndex(this, exec, storage->length(), value, true);
     218            // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
     219            if (!scope.exception())
     220                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
     221            return;
     222        }
     223
     224        // Handled the same as putIndex.
     225        scope.release();
     226        putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage);
     227        return;
     228    }
     229
     230    default:
     231        RELEASE_ASSERT_NOT_REACHED();
     232    }
     233}
     234
    85235} // namespace JSC
Note: See TracChangeset for help on using the changeset viewer.