Ignore:
Timestamp:
Sep 27, 2017, 11:37:41 AM (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):

Source/JavaScriptCore:

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

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