[JSC] Add support for GetByVal on arrays of Undecided shape
authorbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 14 Aug 2015 03:54:32 +0000 (03:54 +0000)
committerbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 14 Aug 2015 03:54:32 +0000 (03:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=147814

Patch by Benjamin Poulain <bpoulain@apple.com> on 2015-08-13
Reviewed by Filip Pizlo.

Previously, GetByVal on Array::Undecided would just take
the generic path. The problem is the generic path is so
slow that it could take a significant amount of time
even for unfrequent accesses.

With this patch, if the following conditions are met,
the GetByVal just returns a "undefined" constant:
-The object is an OriginalArray.
-The prototype chain is sane.
-The index is an integer.
-The integer is positive (runtime check).

Ideally, the 4th conditions should be removed
deducing a compile-time constant gives us so much better
opportunities at getting rid of this code.

There are two cases where this patch removes the runtime
check:
-If the index is constant (uncommon but easy)
-If the index is within a range known to be positive.
 (common case and made possible with DFGIntegerRangeOptimizationPhase).

When we get into those cases, DFG just nukes everything
and all we have left is a structure check :)

This patch is a 14% improvement on audio-beat-detection,
a few percent faster here and there and no regression.

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
If the index is a positive constant, we can get rid of the GetByVal
entirely. :)

* dfg/DFGArrayMode.cpp:
(JSC::DFG::ArrayMode::fromObserved):
The returned type is now Array::Undecided + profiling information.
The useful type is set in ArrayMode::refine().

(JSC::DFG::ArrayMode::refine):
If we meet the particular set conditions, we speculate an Undecided
array type with sane chain. Anything else comes back to Generic.

(JSC::DFG::ArrayMode::originalArrayStructure):
To enable the structure check for Undecided array.

(JSC::DFG::ArrayMode::alreadyChecked):
* dfg/DFGArrayMode.h:
(JSC::DFG::ArrayMode::withProfile):
(JSC::DFG::ArrayMode::canCSEStorage):
(JSC::DFG::ArrayMode::benefitsFromOriginalArray):
(JSC::DFG::ArrayMode::lengthNeedsStorage): Deleted.
(JSC::DFG::ArrayMode::isSpecific): Deleted.A

* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsic): Deleted.
This is somewhat unrelated.

Having Array::Undecided on ArrayPush was impossible before
since ArrayMode::fromObserved() used to return Array::Generic.

Now that Array::Undecided is possible, we must make sure not
to provide it to ArrayPush since there is no code to handle it
properly.

* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
The operation only depends on the index, it is pure.

* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode): Deleted.
* dfg/DFGIntegerRangeOptimizationPhase.cpp:
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::jumpSlowForUnwantedArrayMode):
(JSC::DFG::SpeculativeJIT::checkArray):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::DFG::LowerDFGToLLVM::compileGetByVal):
* tests/stress/get-by-val-on-undecided-array-type.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-1.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-2.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-3.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-4.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-5.js: Added.
* tests/stress/get-by-val-on-undecided-sane-chain-6.js: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@188432 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGArrayMode.cpp
Source/JavaScriptCore/dfg/DFGArrayMode.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-array-type.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-1.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-2.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-3.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-4.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-5.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-6.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-trivial.js [new file with mode: 0644]

index 72a7eeb..1280648 100644 (file)
@@ -1,3 +1,100 @@
+2015-08-13  Benjamin Poulain  <bpoulain@apple.com>
+
+        [JSC] Add support for GetByVal on arrays of Undecided shape
+        https://bugs.webkit.org/show_bug.cgi?id=147814
+
+        Reviewed by Filip Pizlo.
+
+        Previously, GetByVal on Array::Undecided would just take
+        the generic path. The problem is the generic path is so
+        slow that it could take a significant amount of time
+        even for unfrequent accesses.
+
+        With this patch, if the following conditions are met,
+        the GetByVal just returns a "undefined" constant:
+        -The object is an OriginalArray.
+        -The prototype chain is sane.
+        -The index is an integer.
+        -The integer is positive (runtime check).
+
+        Ideally, the 4th conditions should be removed
+        deducing a compile-time constant gives us so much better
+        opportunities at getting rid of this code.
+
+        There are two cases where this patch removes the runtime
+        check:
+        -If the index is constant (uncommon but easy)
+        -If the index is within a range known to be positive.
+         (common case and made possible with DFGIntegerRangeOptimizationPhase).
+
+        When we get into those cases, DFG just nukes everything
+        and all we have left is a structure check :)
+
+        This patch is a 14% improvement on audio-beat-detection,
+        a few percent faster here and there and no regression.
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        If the index is a positive constant, we can get rid of the GetByVal
+        entirely. :)
+
+        * dfg/DFGArrayMode.cpp:
+        (JSC::DFG::ArrayMode::fromObserved):
+        The returned type is now Array::Undecided + profiling information.
+        The useful type is set in ArrayMode::refine().
+
+        (JSC::DFG::ArrayMode::refine):
+        If we meet the particular set conditions, we speculate an Undecided
+        array type with sane chain. Anything else comes back to Generic.
+
+        (JSC::DFG::ArrayMode::originalArrayStructure):
+        To enable the structure check for Undecided array.
+
+        (JSC::DFG::ArrayMode::alreadyChecked):
+        * dfg/DFGArrayMode.h:
+        (JSC::DFG::ArrayMode::withProfile):
+        (JSC::DFG::ArrayMode::canCSEStorage):
+        (JSC::DFG::ArrayMode::benefitsFromOriginalArray):
+        (JSC::DFG::ArrayMode::lengthNeedsStorage): Deleted.
+        (JSC::DFG::ArrayMode::isSpecific): Deleted.A
+
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsic): Deleted.
+        This is somewhat unrelated.
+
+        Having Array::Undecided on ArrayPush was impossible before
+        since ArrayMode::fromObserved() used to return Array::Generic.
+
+        Now that Array::Undecided is possible, we must make sure not
+        to provide it to ArrayPush since there is no code to handle it
+        properly.
+
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        The operation only depends on the index, it is pure.
+
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode): Deleted.
+        * dfg/DFGIntegerRangeOptimizationPhase.cpp:
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::jumpSlowForUnwantedArrayMode):
+        (JSC::DFG::SpeculativeJIT::checkArray):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToLLVM.cpp:
+        (JSC::FTL::DFG::LowerDFGToLLVM::compileGetByVal):
+        * tests/stress/get-by-val-on-undecided-array-type.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-1.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-2.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-3.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-4.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-5.js: Added.
+        * tests/stress/get-by-val-on-undecided-sane-chain-6.js: Added.
+
 2015-08-13  Simon Fraser  <simon.fraser@apple.com>
 
         Remove a few includes from JSGlobalObject.h
index 229087d..9325348 100644 (file)
@@ -1287,12 +1287,21 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         switch (node->arrayMode().type()) {
         case Array::SelectUsingPredictions:
         case Array::Unprofiled:
-        case Array::Undecided:
+        case Array::SelectUsingArguments:
             RELEASE_ASSERT_NOT_REACHED();
             break;
         case Array::ForceExit:
             m_state.setIsValid(false);
             break;
+        case Array::Undecided: {
+            JSValue index = forNode(node->child2()).value();
+            if (index && index.isInt32() && index.asInt32() >= 0) {
+                setConstant(node, jsUndefined());
+                break;
+            }
+            forNode(node).setType(SpecOther);
+            break;
+        }
         case Array::Generic:
             clobberWorld(node->origin.semantic, clobberLimit);
             forNode(node).makeHeapTop();
@@ -1910,6 +1919,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         case Array::Int32:
         case Array::Double:
         case Array::Contiguous:
+        case Array::Undecided:
         case Array::ArrayStorage:
         case Array::SlowPutArrayStorage:
             break;
index b0a4b04..75fd6d1 100644 (file)
@@ -28,6 +28,7 @@
 
 #if ENABLE(DFG_JIT)
 
+#include "ArrayPrototype.h"
 #include "DFGAbstractValue.h"
 #include "DFGGraph.h"
 #include "JSCInlines.h"
@@ -48,17 +49,17 @@ ArrayMode ArrayMode::fromObserved(const ConcurrentJITLocker& locker, ArrayProfil
         return ArrayMode(Array::Unprofiled);
     case asArrayModes(NonArray):
         if (action == Array::Write && !profile->mayInterceptIndexedAccesses(locker))
-            return ArrayMode(Array::Undecided, nonArray, Array::OutOfBounds, Array::Convert);
+            return ArrayMode(Array::SelectUsingArguments, nonArray, Array::OutOfBounds, Array::Convert);
         return ArrayMode(Array::SelectUsingPredictions, nonArray).withSpeculationFromProfile(locker, profile, makeSafe);
 
     case asArrayModes(ArrayWithUndecided):
         if (action == Array::Write)
-            return ArrayMode(Array::Undecided, Array::Array, Array::OutOfBounds, Array::Convert);
-        return ArrayMode(Array::Generic);
+            return ArrayMode(Array::SelectUsingArguments, Array::Array, Array::OutOfBounds, Array::Convert);
+        return ArrayMode(Array::Undecided, Array::Array, Array::OutOfBounds, Array::AsIs).withProfile(locker, profile, makeSafe);
         
     case asArrayModes(NonArray) | asArrayModes(ArrayWithUndecided):
         if (action == Array::Write && !profile->mayInterceptIndexedAccesses(locker))
-            return ArrayMode(Array::Undecided, Array::PossiblyArray, Array::OutOfBounds, Array::Convert);
+            return ArrayMode(Array::SelectUsingArguments, Array::PossiblyArray, Array::OutOfBounds, Array::Convert);
         return ArrayMode(Array::SelectUsingPredictions).withSpeculationFromProfile(locker, profile, makeSafe);
 
     case asArrayModes(NonArrayWithInt32):
@@ -134,7 +135,7 @@ ArrayMode ArrayMode::fromObserved(const ConcurrentJITLocker& locker, ArrayProfil
         else if (shouldUseInt32(observed))
             type = Array::Int32;
         else
-            type = Array::Undecided;
+            type = Array::SelectUsingArguments;
         
         if (hasSeenArray(observed) && hasSeenNonArray(observed))
             arrayClass = Array::PossiblyArray;
@@ -179,7 +180,7 @@ ArrayMode ArrayMode::refine(
     // should just trust the array profile.
     
     switch (type()) {
-    case Array::Undecided:
+    case Array::SelectUsingArguments:
         if (!value)
             return withType(Array::ForceExit);
         if (isInt32Speculation(value))
@@ -187,7 +188,20 @@ ArrayMode ArrayMode::refine(
         if (isFullNumberSpeculation(value))
             return withTypeAndConversion(Array::Double, Array::Convert);
         return withTypeAndConversion(Array::Contiguous, Array::Convert);
-        
+    case Array::Undecided: {
+        // If we have an OriginalArray and the JSArray prototype chain is sane,
+        // any indexed access always return undefined. We have a fast path for that.
+        JSGlobalObject* globalObject = graph.globalObjectFor(node->origin.semantic);
+        if (node->op() == GetByVal
+            && arrayClass() == Array::OriginalArray
+            && globalObject->arrayPrototypeChainIsSane()
+            && !graph.hasExitSite(node->origin.semantic, OutOfBounds)) {
+            graph.watchpoints().addLazily(globalObject->arrayPrototype()->structure()->transitionWatchpointSet());
+            graph.watchpoints().addLazily(globalObject->objectPrototype()->structure()->transitionWatchpointSet());
+            return withSpeculation(Array::SaneChain);
+        }
+        return ArrayMode(Array::Generic);
+    }
     case Array::Int32:
         if (!value || isInt32Speculation(value))
             return *this;
@@ -302,6 +316,8 @@ Structure* ArrayMode::originalArrayStructure(Graph& graph, const CodeOrigin& cod
             return globalObject->originalArrayStructureForIndexingType(ArrayWithDouble);
         case Array::Contiguous:
             return globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous);
+        case Array::Undecided:
+            return globalObject->originalArrayStructureForIndexingType(ArrayWithUndecided);
         case Array::ArrayStorage:
             return globalObject->originalArrayStructureForIndexingType(ArrayWithArrayStorage);
         default:
@@ -398,6 +414,9 @@ bool ArrayMode::alreadyChecked(Graph& graph, Node* node, const AbstractValue& va
         
     case Array::ArrayStorage:
         return alreadyChecked(graph, node, value, ArrayStorageShape);
+
+    case Array::Undecided:
+        return alreadyChecked(graph, node, value, UndecidedShape);
         
     case Array::SlowPutArrayStorage:
         switch (arrayClass()) {
@@ -469,7 +488,7 @@ bool ArrayMode::alreadyChecked(Graph& graph, Node* node, const AbstractValue& va
         
     case Array::SelectUsingPredictions:
     case Array::Unprofiled:
-    case Array::Undecided:
+    case Array::SelectUsingArguments:
         break;
     }
     
@@ -482,6 +501,8 @@ const char* arrayTypeToString(Array::Type type)
     switch (type) {
     case Array::SelectUsingPredictions:
         return "SelectUsingPredictions";
+    case Array::SelectUsingArguments:
+        return "SelectUsingArguments";
     case Array::Unprofiled:
         return "Unprofiled";
     case Array::Generic:
index a77c888..9c988c0 100644 (file)
@@ -53,6 +53,7 @@ enum Action {
 
 enum Type {
     SelectUsingPredictions, // Implies that we need predictions to decide. We will never get to the backend in this mode.
+    SelectUsingArguments, // Implies that we use the Node's arguments to decide. We will never get to the backend in this mode.
     Unprofiled, // Implies that array profiling didn't see anything. But that could be because the operands didn't comply with basic type assumptions (base is cell, property is int). This either becomes Generic or ForceExit depending on value profiling.
     ForceExit, // Implies that we have no idea how to execute this operation, so we should just give up.
     Generic,
@@ -195,7 +196,7 @@ public:
     ArrayMode withProfile(const ConcurrentJITLocker& locker, ArrayProfile* profile, bool makeSafe) const
     {
         Array::Class myArrayClass;
-        
+
         if (isJSArray()) {
             if (profile->usesOriginalArrayStructures(locker) && benefitsFromOriginalArray())
                 myArrayClass = Array::OriginalArray;
@@ -293,7 +294,9 @@ public:
     {
         switch (type()) {
         case Array::SelectUsingPredictions:
+        case Array::SelectUsingArguments:
         case Array::Unprofiled:
+        case Array::Undecided:
         case Array::ForceExit:
         case Array::Generic:
         case Array::DirectArguments:
@@ -307,7 +310,6 @@ public:
     bool lengthNeedsStorage() const
     {
         switch (type()) {
-        case Array::Undecided:
         case Array::Int32:
         case Array::Double:
         case Array::Contiguous:
@@ -335,10 +337,10 @@ public:
     {
         switch (type()) {
         case Array::SelectUsingPredictions:
+        case Array::SelectUsingArguments:
         case Array::Unprofiled:
         case Array::ForceExit:
         case Array::Generic:
-        case Array::Undecided:
             return false;
         default:
             return true;
@@ -372,6 +374,7 @@ public:
         case Array::Int32:
         case Array::Double:
         case Array::Contiguous:
+        case Array::Undecided:
         case Array::ArrayStorage:
             return true;
         default:
index e272343..a56337c 100644 (file)
@@ -1951,7 +1951,6 @@ bool ByteCodeParser::handleIntrinsic(int resultOperand, Intrinsic intrinsic, int
         if (!arrayMode.isJSArray())
             return false;
         switch (arrayMode.type()) {
-        case Array::Undecided:
         case Array::Int32:
         case Array::Double:
         case Array::Contiguous:
index 46f5fe4..85c57e5 100644 (file)
@@ -467,7 +467,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         switch (mode.type()) {
         case Array::SelectUsingPredictions:
         case Array::Unprofiled:
-        case Array::Undecided:
+        case Array::SelectUsingArguments:
             // Assume the worst since we don't have profiling yet.
             read(World);
             write(Heap);
@@ -534,6 +534,10 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
             read(World);
             write(Heap);
             return;
+
+        case Array::Undecided:
+            def(PureValue(node));
+            return;
             
         case Array::ArrayStorage:
         case Array::SlowPutArrayStorage:
@@ -580,6 +584,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         Node* value = graph.varArgChild(node, 2).node();
         switch (mode.modeForPut().type()) {
         case Array::SelectUsingPredictions:
+        case Array::SelectUsingArguments:
         case Array::Unprofiled:
         case Array::Undecided:
             // Assume the worst since we don't have profiling yet.
index d6a9790..da76e7a 100644 (file)
@@ -625,7 +625,6 @@ private:
             switch (arrayMode.type()) {
             case Array::SelectUsingPredictions:
             case Array::Unprofiled:
-            case Array::Undecided:
                 RELEASE_ASSERT_NOT_REACHED();
                 break;
             case Array::Generic:
@@ -686,6 +685,7 @@ private:
             
             switch (node->arrayMode().modeForPut().type()) {
             case Array::SelectUsingPredictions:
+            case Array::SelectUsingArguments:
             case Array::Unprofiled:
             case Array::Undecided:
                 RELEASE_ASSERT_NOT_REACHED();
index 6ab8af8..d67e3f1 100644 (file)
@@ -958,7 +958,28 @@ public:
                     }
                     break;
                 }
-                    
+
+                case GetByVal: {
+                    if (node->arrayMode().type() != Array::Undecided)
+                        break;
+
+                    auto iter = m_relationships.find(node->child2().node());
+                    if (iter == m_relationships.end())
+                        break;
+
+                    int minValue = std::numeric_limits<int>::min();
+                    for (Relationship relationship : iter->value)
+                        minValue = std::max(minValue, relationship.minValueOfLeft());
+
+                    if (minValue < 0)
+                        break;
+
+                    executeNode(block->at(nodeIndex));
+                    m_graph.convertToConstant(node, jsUndefined());
+                    changed = true;
+                    break;
+                }
+
                 default:
                     break;
                 }
index 0537363..484bf8d 100644 (file)
@@ -716,6 +716,9 @@ JITCompiler::JumpList SpeculativeJIT::jumpSlowForUnwantedArrayMode(GPRReg tempGP
     case Array::Contiguous:
         return jumpSlowForUnwantedArrayMode(tempGPR, arrayMode, ContiguousShape);
 
+    case Array::Undecided:
+        return jumpSlowForUnwantedArrayMode(tempGPR, arrayMode, UndecidedShape);
+
     case Array::ArrayStorage:
     case Array::SlowPutArrayStorage: {
         ASSERT(!arrayMode.isJSArrayWithOriginalStructure());
@@ -781,6 +784,7 @@ void SpeculativeJIT::checkArray(Node* node)
     case Array::Int32:
     case Array::Double:
     case Array::Contiguous:
+    case Array::Undecided:
     case Array::ArrayStorage:
     case Array::SlowPutArrayStorage: {
         GPRTemporary temp(this);
index 6249902..800b872 100644 (file)
@@ -2331,6 +2331,26 @@ void SpeculativeJIT::compile(Node* node)
             terminateSpeculativeExecution(InadequateCoverage, JSValueRegs(), 0);
 #endif
             break;
+        case Array::Undecided: {
+            SpeculateStrictInt32Operand index(this, node->child2());
+            GPRTemporary resultTag(this, Reuse, index);
+            GPRTemporary resultPayload(this);
+
+            GPRReg indexGPR = index.gpr();
+            GPRReg resultTagGPR = resultTag.gpr();
+            GPRReg resultPayloadGPR = resultPayload.gpr();
+
+            use(node->child1());
+            index.use();
+
+            speculationCheck(OutOfBounds, JSValueRegs(), node,
+                m_jit.branch32(MacroAssembler::LessThan, indexGPR, MacroAssembler::TrustedImm32(0)));
+
+            m_jit.move(MacroAssembler::TrustedImm32(JSValue::UndefinedTag), resultTagGPR);
+            m_jit.move(MacroAssembler::TrustedImm32(0), resultPayloadGPR);
+            jsValueResult(resultTagGPR, resultPayloadGPR, node, UseChildrenCalledExplicitly);
+            break;
+        }
         case Array::Generic: {
             SpeculateCellOperand base(this, node->child1()); // Save a register, speculate cell. We'll probably be right.
             JSValueOperand property(this, node->child2());
index 13e628f..53daf34 100644 (file)
@@ -2461,6 +2461,22 @@ void SpeculativeJIT::compile(Node* node)
         case Array::ForceExit:
             DFG_CRASH(m_jit.graph(), node, "Bad array mode type");
             break;
+        case Array::Undecided: {
+            SpeculateStrictInt32Operand index(this, node->child2());
+            GPRTemporary result(this, Reuse, index);
+            GPRReg indexGPR = index.gpr();
+            GPRReg resultGPR = result.gpr();
+
+            use(node->child1());
+            index.use();
+
+            speculationCheck(OutOfBounds, JSValueRegs(), node,
+                m_jit.branch32(MacroAssembler::LessThan, indexGPR, MacroAssembler::TrustedImm32(0)));
+
+            m_jit.move(MacroAssembler::TrustedImm64(ValueUndefined), resultGPR);
+            jsValueResult(resultGPR, node, UseChildrenCalledExplicitly);
+            break;
+        }
         case Array::Generic: {
             JSValueOperand base(this, node->child1());
             JSValueOperand property(this, node->child2());
index b493f30..e2b1f41 100644 (file)
@@ -263,6 +263,7 @@ inline CapabilityLevel canCompile(Node* node)
         case Array::Int32:
         case Array::Double:
         case Array::Contiguous:
+        case Array::Undecided:
         case Array::DirectArguments:
         case Array::ScopedArguments:
             break;
index a1b19bf..c61a751 100644 (file)
@@ -2447,6 +2447,14 @@ private:
             setJSValue(m_out.phi(m_out.int64, fastResult, slowResult));
             return;
         }
+
+        case Array::Undecided: {
+            LValue index = lowInt32(m_node->child2());
+
+            speculate(OutOfBounds, noValue(), m_node, m_out.lessThan(index, m_out.int32Zero));
+            setJSValue(m_out.constInt64(ValueUndefined));
+            return;
+        }
             
         case Array::DirectArguments: {
             LValue base = lowCell(m_node->child1());
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-array-type.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-array-type.js
new file mode 100644 (file)
index 0000000..b5b01f2
--- /dev/null
@@ -0,0 +1,358 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+function testAccessInBounds() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    // Adding non-indexed properties to change the kind of array we are dealing with.
+    target["webkit"] = "awesome!";
+    target[-5] = "Uh?";
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+
+    if (target["webkit"] !== "awesome!")
+        throw "Failed to retrieve \"webkit\"";
+    if (opaqueGetByVal1(target, -5) !== "Uh?")
+        throw "Failed to retrive -5";
+}
+testAccessInBounds();
+
+// Empty array access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testEmptyArrayAccess() {
+    const target = new Array();
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    // Adding non-indexed properties to change the kind of array we are dealing with.
+    target["webkit"] = "awesome!";
+    target[-5] = "Uh?";
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 2 failed for i = " + i + " value = " + value;
+    }
+    if (target["webkit"] !== "awesome!")
+        throw "Failed to retrieve \"webkit\"";
+    if (opaqueGetByVal2(target, -5) !== "Uh?")
+        throw "Failed to retrive -5";
+}
+testEmptyArrayAccess();
+
+// Out of bounds array access.
+function opaqueGetByVal3(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal3);
+
+function testOutOfBoundsArrayAccess() {
+    const target = new Array(42);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal3(target, i + 43);
+        if (value !== undefined)
+            throw "opaqueGetByVal3() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    // Adding non-indexed properties to change the kind of array we are dealing with.
+    target["webkit"] = "awesome!";
+    target[-5] = "Uh?";
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal3(target, i + 43);
+        if (value !== undefined)
+            throw "opaqueGetByVal3() case 2 failed for i = " + i + " value = " + value;
+    }
+    if (target["webkit"] !== "awesome!")
+        throw "Failed to retrieve \"webkit\"";
+    if (opaqueGetByVal3(target, -5) !== "Uh?")
+        throw "Failed to retrive -5";
+}
+testOutOfBoundsArrayAccess();
+
+// In-and-out of bounds.
+function opaqueGetByVal4(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal4);
+
+function testInAndOutOfBoundsArrayAccess() {
+    const target = new Array(71);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal4(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal4() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    // Adding non-indexed properties to change the kind of array we are dealing with.
+    target["webkit"] = "awesome!";
+    target[-5] = "Uh?";
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal4(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal4() case 2 failed for i = " + i + " value = " + value;
+    }
+    if (target["webkit"] !== "awesome!")
+        throw "Failed to retrieve \"webkit\"";
+    if (opaqueGetByVal4(target, -5) !== "Uh?")
+        throw "Failed to retrive -5";
+}
+testInAndOutOfBoundsArrayAccess();
+
+// Negative index.
+function opaqueGetByVal5(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal5);
+
+function testNegativeIndex() {
+    const target = new Array();
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal5(target, -1 - i);
+        if (value !== undefined)
+            throw "opaqueGetByVal5() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    // Adding non-indexed properties to change the kind of array we are dealing with.
+    target["webkit"] = "awesome!";
+    target[-5] = "Uh?";
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal5(target, -1 - i);
+        if (i === 4) {
+            if (value !== "Uh?")
+                throw "opaqueGetByVal5() case 2 failed for i = " + i + " value = " + value;
+        } else if (value !== undefined)
+            throw "opaqueGetByVal5() case 2 failed for i = " + i + " value = " + value;
+    }
+    if (target["webkit"] !== "awesome!")
+        throw "Failed to retrieve \"webkit\"";
+    if (opaqueGetByVal5(target, -5) !== "Uh?")
+        throw "Failed to retrive -5";
+}
+testNegativeIndex();
+
+// Test integer boundaries.
+function opaqueGetByVal6(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal6);
+
+function testIntegerBoundaries() {
+    const target = new Array(42);
+
+    for (let i = 0; i < 1e4; ++i) {
+        // 2^31 - 1
+        let value = opaqueGetByVal6(target, 2147483647);
+        if (value !== undefined)
+            throw "opaqueGetByVal6() case 1 failed for 2147483647 value = " + value;
+
+        // 2^31
+        value = opaqueGetByVal6(target, 2147483648);
+        if (value !== undefined)
+            throw "opaqueGetByVal6() case 1 failed for 2147483648 value = " + value;
+
+        // 2^32 - 1
+        value = opaqueGetByVal6(target, 4294967295);
+        if (value !== undefined)
+            throw "opaqueGetByVal6() case 1 failed for 4294967295 value = " + value;
+
+        // 2^32
+        value = opaqueGetByVal6(target, 4294967296);
+        if (value !== undefined)
+            throw "opaqueGetByVal6() case 1 failed for 4294967296 value = " + value;
+
+        // 2^52
+        value = opaqueGetByVal6(target, 4503599627370496);
+        if (value !== undefined)
+            throw "opaqueGetByVal6() case 1 failed for 4503599627370496 value = " + value;
+    }
+}
+testIntegerBoundaries();
+
+// Use a constant index.
+function opaqueGetByVal7_zero(array) {
+    return array[0];
+}
+noInline(opaqueGetByVal7_zero);
+
+function opaqueGetByVal7_doubleZero(array) {
+    return array[1.5 - 1.5];
+}
+noInline(opaqueGetByVal7_doubleZero);
+
+function opaqueGetByVal7_101(array) {
+    return array[101];
+}
+noInline(opaqueGetByVal7_101);
+
+function opaqueGetByVal7_double101(array) {
+    return array[101.0000];
+}
+noInline(opaqueGetByVal7_double101);
+
+function opaqueGetByVal7_1038(array) {
+    return array[1038];
+}
+noInline(opaqueGetByVal7_1038);
+
+function testContantIndex() {
+    const emptyArray = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        let value = opaqueGetByVal7_zero(emptyArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 1 failed for 0 value = " + value;
+
+        value = opaqueGetByVal7_doubleZero(emptyArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7_doubleZero() case 1 failed for 0 value = " + value;
+
+        value = opaqueGetByVal7_101(emptyArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 1 failed for 101 value = " + value;
+
+        value = opaqueGetByVal7_double101(emptyArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 1 failed for 101 value = " + value;
+    }
+
+    const uninitializedArray = new Array(1038);
+
+    for (let i = 0; i < 1e4; ++i) {
+        let value = opaqueGetByVal7_zero(uninitializedArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 2 failed for 0 value = " + value;
+
+        value = opaqueGetByVal7_doubleZero(uninitializedArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7_doubleZero() case 2 failed for 0 value = " + value;
+
+        value = opaqueGetByVal7_101(uninitializedArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 2 failed for 101 value = " + value;
+
+        value = opaqueGetByVal7_double101(uninitializedArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7_double101() case 2 failed for 101 value = " + value;
+
+        value = opaqueGetByVal7_1038(uninitializedArray);
+        if (value !== undefined)
+            throw "opaqueGetByVal7() case 2 failed for 1038 value = " + value;
+    }
+
+}
+testContantIndex();
+
+// Test natural integer proggression.
+function testValueIsUndefinedInNaturalProgression(value) {
+    if (value !== undefined)
+        throw "Invalid value in natural progression test"
+}
+noInline(testValueIsUndefinedInNaturalProgression);
+
+function testNaturalProgression() {
+    const target = new Array(42);
+
+    for (let i = 0; i < 10; ++i) {
+        const value = target[i];
+        testValueIsUndefinedInNaturalProgression(value);
+    }
+
+    const emptyTarget = new Array();
+    for (let i = 10; i--;) {
+        const value = emptyTarget[i];
+        testValueIsUndefinedInNaturalProgression(value);
+    }
+}
+noInline(testNaturalProgression);
+for (let i = 0; i < 1e4; ++i)
+    testNaturalProgression();
+
+// PutByVal changes the array type.
+function getUndecidedArray()
+{
+    return new Array(50);
+}
+noInline(getUndecidedArray);
+
+for (let i = 0; i < 1e4; ++i) {
+    // Warm up getUndecidedArray() without any useful profiling information.
+    getUndecidedArray();
+}
+
+function getByValAfterPutByVal()
+{
+    const array = getUndecidedArray();
+
+    for (let i = 0; i < array.length + 1; ++i) {
+        if (array[i] !== undefined)
+            throw "Invalid access on the empty array in getByValAfterPutByVal()";
+    }
+
+    array[5] = 42;
+
+    for (let i = array.length + 1; i--;) {
+        if (i === 5) {
+            if (array[i] !== 42)
+                throw "array[5] !== 42"
+        } else if (array[i] !== undefined)
+            throw "Invalid access on the mostly empty array in getByValAfterPutByVal()";
+    }
+}
+noInline(getByValAfterPutByVal);
+
+for (let i = 0; i < 1e4; ++i)
+    getByValAfterPutByVal();
+
+// Push changes the array type.
+function getByValAfterPush()
+{
+    const array = getUndecidedArray();
+
+    for (let i = 0; i < array.length + 1; ++i) {
+        if (array[i] !== undefined)
+            throw "Invalid access on the empty array in getByValAfterPush()";
+    }
+
+    array.push(43);
+
+    for (let i = array.length + 1; i--;) {
+        if (i === 50) {
+            if (array[i] !== 43)
+                throw "array[50] !== 43"
+        } else if (array[i] !== undefined)
+            throw "Invalid access on the mostly empty array in getByValAfterPush()";
+    }
+}
+noInline(getByValAfterPutByVal);
+
+for (let i = 0; i < 1e4; ++i)
+    getByValAfterPush();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-1.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-1.js
new file mode 100644 (file)
index 0000000..be3afb1
--- /dev/null
@@ -0,0 +1,63 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+function testAccessInBounds() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[42] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const index = i % 100;
+        const value = opaqueGetByVal1(target, index);
+        if (index == 42) {
+            if (value !== "Uh!")
+                throw "opaqueGetByVal1() case 2 failed on 42, value = " + value;
+        } else if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+
+    delete Array.prototype[42];
+}
+testAccessInBounds();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[5] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const index = i % 100;
+        const value = opaqueGetByVal2(target, index);
+        if (index == 5) {
+            if (value !== "Uh!")
+                throw "opaqueGetByVal2() case 2 failed on 42, value = " + value;
+        } else if (value !== undefined)
+            throw "opaqueGetByVal2() case 2 failed for i = " + i + " value = " + value;
+    }
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-2.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-2.js
new file mode 100644 (file)
index 0000000..22450a0
--- /dev/null
@@ -0,0 +1,52 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+function testAccessInBounds() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i % 100);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Object.prototype[42] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const index = i % 100;
+        const value = opaqueGetByVal1(target, index);
+        if (index == 42) {
+            if (value !== "Uh!")
+                throw "opaqueGetByVal1() case 2 failed on 42, value = " + value;
+        } else if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+}
+testAccessInBounds();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        const index = i % 100;
+        const value = opaqueGetByVal2(target, index);
+        if (index == 42) {
+            if (value !== "Uh!")
+                throw "opaqueGetByVal2() case 2 failed on 42, value = " + value;
+        } else if (value !== undefined)
+            throw "opaqueGetByVal2() case 2 failed for i = " + i + " value = " + value;
+    }
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-3.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-3.js
new file mode 100644 (file)
index 0000000..ef1c404
--- /dev/null
@@ -0,0 +1,51 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+function testUninitializedArray() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[-1] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal1(target, -1)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+
+}
+testUninitializedArray();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal2(target, -1)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-4.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-4.js
new file mode 100644 (file)
index 0000000..f11d974
--- /dev/null
@@ -0,0 +1,53 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+const IntMax = Math.pow(2, 31) - 1;
+
+function testUninitializedArray() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[IntMax] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal1(target, IntMax)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+
+}
+testUninitializedArray();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal2(target, IntMax)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-5.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-5.js
new file mode 100644 (file)
index 0000000..4334db3
--- /dev/null
@@ -0,0 +1,53 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+const IntMaxPlusOne = Math.pow(2, 31);
+
+function testUninitializedArray() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[IntMaxPlusOne] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal1(target, IntMaxPlusOne)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+
+}
+testUninitializedArray();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal2(target, IntMaxPlusOne)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-6.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-sane-chain-6.js
new file mode 100644 (file)
index 0000000..e24a7c2
--- /dev/null
@@ -0,0 +1,54 @@
+"use strict"
+
+// Test in-bounds access.
+function opaqueGetByVal1(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal1);
+
+// The max unsigned 32bits integer is the first integer not considered an indexing property.
+const NotIndexInteger = 0xFFFFFFFF;
+
+function testUninitializedArray() {
+    const target = new Array(100);
+
+    // We start with an original array. Those GetByVal can be eliminated.
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 1 failed for i = " + i + " value = " + value;
+    }
+
+    Array.prototype[NotIndexInteger] = "Uh!";
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal1(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal1() case 2 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal1(target, NotIndexInteger)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+
+}
+testUninitializedArray();
+
+// Test in-bounds access.
+function opaqueGetByVal2(array, index) {
+    return array[index];
+}
+noInline(opaqueGetByVal2);
+
+function testAccessOnEmpty() {
+    const target = new Array();
+
+    for (let i = 0; i < 1e4; ++i) {
+        const value = opaqueGetByVal2(target, i);
+        if (value !== undefined)
+            throw "opaqueGetByVal2() case 1 failed for i = " + i + " value = " + value;
+    }
+    const prototypeValue = opaqueGetByVal2(target, NotIndexInteger)
+    if (prototypeValue !== "Uh!")
+        throw "prototypeValue value = " + value;
+}
+testAccessOnEmpty();
diff --git a/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-trivial.js b/Source/JavaScriptCore/tests/stress/get-by-val-on-undecided-trivial.js
new file mode 100644 (file)
index 0000000..22e371c
--- /dev/null
@@ -0,0 +1,37 @@
+"use strict"
+
+// Trivial case where everything could be eliminated.
+function iterateEmptyArray()
+{
+    const array = new Array();
+    for (let i = 0; i < 100; ++i) {
+        if (array[i] !== undefined)
+            throw "Unexpected value in empty array at index i = " + i;
+    }
+}
+noInline(iterateEmptyArray);
+
+for (let i = 1e4; i--;) {
+    iterateEmptyArray();
+}
+
+// Trivial case but the array needs to be checked.
+function getArrayOpaque()
+{
+    return new Array(10);
+}
+noInline(getArrayOpaque);
+
+function iterateOpaqueEmptyArray()
+{
+    const array = getArrayOpaque();
+    for (let i = 0; i < 100; ++i) {
+        if (array[i] !== undefined)
+            throw "Unexpected value in empty array at index i = " + i;
+    }
+}
+noInline(iterateEmptyArray);
+
+for (let i = 1e4; i--;) {
+    iterateOpaqueEmptyArray();
+}
\ No newline at end of file