[JSC] Add CheckArrayOrEmpty to handle the case when hoisting CheckArray for places...
authorysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Jan 2020 20:45:37 +0000 (20:45 +0000)
committerysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Jan 2020 20:45:37 +0000 (20:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=206571
<rdar://problem/58757016>

Reviewed by Saam Barati.

JSTests:

* stress/check-array-or-empty.js: Added.
(C):

Source/JavaScriptCore:

Since we hoist CheckArray too in DFGTypeCheckHoistingPhase, we have the same problem to CheckStructureOrEmpty: we
could insert CheckArray where the input can include Empty. We should have CheckArrayOrEmpty as we have CheckStructureOrEmpty
for CheckStructure: CheckArrayOrEmpty accepts empty or cell with specified array-modes.

* dfg/DFGAbstractInterpreter.h:
(JSC::DFG::AbstractInterpreter::filterArrayModes):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
(JSC::DFG::AbstractInterpreter<AbstractStateType>::filterArrayModes):
* dfg/DFGAbstractValue.cpp:
(JSC::DFG::AbstractValue::filterArrayModes):
* dfg/DFGAbstractValue.h:
* dfg/DFGArgumentsEliminationPhase.cpp:
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNode.h:
(JSC::DFG::Node::convertCheckArrayOrEmptyToCheckArray):
(JSC::DFG::Node::hasArrayMode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::checkArray):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGTypeCheckHoistingPhase.cpp:
(JSC::DFG::TypeCheckHoistingPhase::run):
* dfg/DFGValidate.cpp:
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileCheckArrayOrEmpty):

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

23 files changed:
JSTests/ChangeLog
JSTests/stress/check-array-or-empty.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreter.h
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGAbstractValue.cpp
Source/JavaScriptCore/dfg/DFGAbstractValue.h
Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNode.h
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/dfg/DFGTypeCheckHoistingPhase.cpp
Source/JavaScriptCore/dfg/DFGValidate.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

index dccf36c..6e11d32 100644 (file)
@@ -1,3 +1,14 @@
+2020-01-22  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Add CheckArrayOrEmpty to handle the case when hoisting CheckArray for places where input can be empty
+        https://bugs.webkit.org/show_bug.cgi?id=206571
+        <rdar://problem/58757016>
+
+        Reviewed by Saam Barati.
+
+        * stress/check-array-or-empty.js: Added.
+        (C):
+
 2020-01-21  Tadeu Zagallo  <tzagallo@apple.com>
 
         Object allocation sinking is missing PutHint for allocations unreachable in the graph
diff --git a/JSTests/stress/check-array-or-empty.js b/JSTests/stress/check-array-or-empty.js
new file mode 100644 (file)
index 0000000..6e0bf08
--- /dev/null
@@ -0,0 +1,24 @@
+//@ runDefault("--useGenerationalGC=0", "--useConcurrentGC=0", "--collectContinuously=1", "--useConcurrentJIT=0")
+class C extends Object {
+    constructor(beforeSuper) {
+        let f = () => {
+            for (let j = 0; j < 100; j++) {
+                try {
+                    this[0] = {};
+                } catch (e) {
+                }
+            }
+        };
+        if (beforeSuper) {
+            f();
+            super();
+        } else {
+            super();
+            f();
+        }
+    }
+}
+for (let i = 0; i < 100; i++) {
+    new C(false);
+    new C(true);
+}
index 5f2d8e2..bc87403 100644 (file)
@@ -1,5 +1,56 @@
 2020-01-22  Yusuke Suzuki  <ysuzuki@apple.com>
 
+        [JSC] Add CheckArrayOrEmpty to handle the case when hoisting CheckArray for places where input can be empty
+        https://bugs.webkit.org/show_bug.cgi?id=206571
+        <rdar://problem/58757016>
+
+        Reviewed by Saam Barati.
+
+        Since we hoist CheckArray too in DFGTypeCheckHoistingPhase, we have the same problem to CheckStructureOrEmpty: we
+        could insert CheckArray where the input can include Empty. We should have CheckArrayOrEmpty as we have CheckStructureOrEmpty
+        for CheckStructure: CheckArrayOrEmpty accepts empty or cell with specified array-modes.
+
+        * dfg/DFGAbstractInterpreter.h:
+        (JSC::DFG::AbstractInterpreter::filterArrayModes):
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::filterArrayModes):
+        * dfg/DFGAbstractValue.cpp:
+        (JSC::DFG::AbstractValue::filterArrayModes):
+        * dfg/DFGAbstractValue.h:
+        * dfg/DFGArgumentsEliminationPhase.cpp:
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGConstantFoldingPhase.cpp:
+        (JSC::DFG::ConstantFoldingPhase::foldConstants):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNode.h:
+        (JSC::DFG::Node::convertCheckArrayOrEmptyToCheckArray):
+        (JSC::DFG::Node::hasArrayMode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::checkArray):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGTypeCheckHoistingPhase.cpp:
+        (JSC::DFG::TypeCheckHoistingPhase::run):
+        * dfg/DFGValidate.cpp:
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileCheckArrayOrEmpty):
+
+2020-01-22  Yusuke Suzuki  <ysuzuki@apple.com>
+
         [JSC] Attempt to fix BytecodeIndex handling in 32bit
         https://bugs.webkit.org/show_bug.cgi?id=206577
 
index e296102..4f66acc 100644 (file)
@@ -182,9 +182,9 @@ public:
     }
     
     template<typename T>
-    FiltrationResult filterArrayModes(T node, ArrayModes arrayModes)
+    FiltrationResult filterArrayModes(T node, ArrayModes arrayModes, SpeculatedType admittedTypes = SpecNone)
     {
-        return filterArrayModes(forNode(node), arrayModes);
+        return filterArrayModes(forNode(node), arrayModes, admittedTypes);
     }
     
     template<typename T>
@@ -206,7 +206,7 @@ public:
     }
 
     FiltrationResult filter(AbstractValue&, const RegisteredStructureSet&, SpeculatedType admittedTypes = SpecNone);
-    FiltrationResult filterArrayModes(AbstractValue&, ArrayModes);
+    FiltrationResult filterArrayModes(AbstractValue&, ArrayModes, SpeculatedType admittedTypes = SpecNone);
     FiltrationResult filter(AbstractValue&, SpeculatedType);
     FiltrationResult filterByValue(AbstractValue&, FrozenValue);
     FiltrationResult filterClassInfo(AbstractValue&, const ClassInfo*);
index 7be7e2c..1378bab 100644 (file)
@@ -3379,14 +3379,27 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         break;
     }
 
+    case CheckArrayOrEmpty:
     case CheckArray: {
-        if (node->arrayMode().alreadyChecked(m_graph, node, forNode(node->child1()))) {
+        AbstractValue& value = forNode(node->child1());
+
+        SpeculatedType admittedTypes = SpecNone;
+        if (node->op() == CheckArrayOrEmpty) {
+            bool mayBeEmpty = value.m_type & SpecEmpty;
+            if (!mayBeEmpty)
+                m_state.setShouldTryConstantFolding(true);
+            else
+                admittedTypes = SpecEmpty;
+        }
+
+        if (node->arrayMode().alreadyChecked(m_graph, node, value)) {
             m_state.setShouldTryConstantFolding(true);
             break;
         }
+
         switch (node->arrayMode().type()) {
         case Array::String:
-            filter(node->child1(), SpecString);
+            filter(node->child1(), SpecString | admittedTypes);
             break;
         case Array::Int32:
         case Array::Double:
@@ -3396,46 +3409,46 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         case Array::SlowPutArrayStorage:
             break;
         case Array::DirectArguments:
-            filter(node->child1(), SpecDirectArguments);
+            filter(node->child1(), SpecDirectArguments | admittedTypes);
             break;
         case Array::ScopedArguments:
-            filter(node->child1(), SpecScopedArguments);
+            filter(node->child1(), SpecScopedArguments | admittedTypes);
             break;
         case Array::Int8Array:
-            filter(node->child1(), SpecInt8Array);
+            filter(node->child1(), SpecInt8Array | admittedTypes);
             break;
         case Array::Int16Array:
-            filter(node->child1(), SpecInt16Array);
+            filter(node->child1(), SpecInt16Array | admittedTypes);
             break;
         case Array::Int32Array:
-            filter(node->child1(), SpecInt32Array);
+            filter(node->child1(), SpecInt32Array | admittedTypes);
             break;
         case Array::Uint8Array:
-            filter(node->child1(), SpecUint8Array);
+            filter(node->child1(), SpecUint8Array | admittedTypes);
             break;
         case Array::Uint8ClampedArray:
-            filter(node->child1(), SpecUint8ClampedArray);
+            filter(node->child1(), SpecUint8ClampedArray | admittedTypes);
             break;
         case Array::Uint16Array:
-            filter(node->child1(), SpecUint16Array);
+            filter(node->child1(), SpecUint16Array | admittedTypes);
             break;
         case Array::Uint32Array:
-            filter(node->child1(), SpecUint32Array);
+            filter(node->child1(), SpecUint32Array | admittedTypes);
             break;
         case Array::Float32Array:
-            filter(node->child1(), SpecFloat32Array);
+            filter(node->child1(), SpecFloat32Array | admittedTypes);
             break;
         case Array::Float64Array:
-            filter(node->child1(), SpecFloat64Array);
+            filter(node->child1(), SpecFloat64Array | admittedTypes);
             break;
         case Array::AnyTypedArray:
-            filter(node->child1(), SpecTypedArrayView);
+            filter(node->child1(), SpecTypedArrayView | admittedTypes);
             break;
         default:
             RELEASE_ASSERT_NOT_REACHED();
             break;
         }
-        filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering());
+        filterArrayModes(node->child1(), node->arrayMode().arrayModesThatPassFiltering(), admittedTypes);
         break;
     }
 
@@ -4464,9 +4477,9 @@ FiltrationResult AbstractInterpreter<AbstractStateType>::filter(
 
 template<typename AbstractStateType>
 FiltrationResult AbstractInterpreter<AbstractStateType>::filterArrayModes(
-    AbstractValue& value, ArrayModes arrayModes)
+    AbstractValue& value, ArrayModes arrayModes, SpeculatedType admittedTypes)
 {
-    if (value.filterArrayModes(arrayModes) == FiltrationOK)
+    if (value.filterArrayModes(arrayModes, admittedTypes) == FiltrationOK)
         return FiltrationOK;
     m_state.setIsValid(false);
     return Contradiction;
index ba019c9..8c71a47 100644 (file)
@@ -266,14 +266,15 @@ FiltrationResult AbstractValue::changeStructure(Graph& graph, const RegisteredSt
     return normalizeClarity(graph);
 }
 
-FiltrationResult AbstractValue::filterArrayModes(ArrayModes arrayModes)
+FiltrationResult AbstractValue::filterArrayModes(ArrayModes arrayModes, SpeculatedType admittedTypes)
 {
     ASSERT(arrayModes);
+    ASSERT(!(admittedTypes & SpecCell));
     
     if (isClear())
         return FiltrationOK;
     
-    m_type &= SpecCell;
+    m_type &= SpecCell | admittedTypes;
     m_arrayModes &= arrayModes;
     return normalizeClarity();
 }
index cf9fd08..e869a1b 100644 (file)
@@ -336,7 +336,7 @@ struct AbstractValue {
     // with SpecCell.
     FiltrationResult filter(Graph&, const RegisteredStructureSet&, SpeculatedType admittedTypes = SpecNone);
     
-    FiltrationResult filterArrayModes(ArrayModes);
+    FiltrationResult filterArrayModes(ArrayModes, SpeculatedType admittedTypes = SpecNone);
 
     ALWAYS_INLINE FiltrationResult filter(SpeculatedType type)
     {
index 9bb4f4f..53b0148 100644 (file)
@@ -409,6 +409,7 @@ private:
                 case FilterInByIdStatus:
                     break;
 
+                case CheckArrayOrEmpty:
                 case CheckArray:
                     escapeBasedOnArrayMode(node->arrayMode(), node->child1(), node);
                     break;
@@ -1259,6 +1260,7 @@ private:
                     break;
                 }
                     
+                case CheckArrayOrEmpty:
                 case CheckArray:
                 case GetButterfly:
                 case FilterGetByStatus:
index c386187..c758de6 100644 (file)
@@ -1096,6 +1096,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         read(JSCell_structureID);
         return;
 
+    case CheckArrayOrEmpty:
     case CheckArray:
         read(JSCell_indexingType);
         read(JSCell_typeInfoType);
index d8af035..b43f7cf 100644 (file)
@@ -275,7 +275,20 @@ private:
                 }
                 break;
             }
-                
+
+            case CheckArrayOrEmpty: {
+                const AbstractValue& value = m_state.forNode(node->child1());
+                if (!(value.m_type & SpecEmpty)) {
+                    node->convertCheckArrayOrEmptyToCheckArray();
+                    changed = true;
+                }
+                // Even if the input includes SpecEmpty, we can fall through to CheckArray and remove the node.
+                // CheckArrayOrEmpty can be removed when arrayMode meets the requirement. In that case, CellUse's
+                // check just remains, and it works as CheckArrayOrEmpty without ArrayMode checking.
+                ASSERT(typeFilterFor(node->child1().useKind()) & SpecEmpty);
+                FALLTHROUGH;
+            }
+
             case CheckArray:
             case Arrayify: {
                 if (!node->arrayMode().alreadyChecked(m_graph, node, m_state.forNode(node->child1())))
index 6ee725a..55d3c8f 100644 (file)
@@ -117,6 +117,7 @@ bool doesGC(Graph& graph, Node* node)
     case GetButterfly:
     case CheckSubClass:
     case CheckArray:
+    case CheckArrayOrEmpty:
     case CheckNeutered:
     case GetScope:
     case SkipScope:
index c79cd7c..7ffa38e 100644 (file)
@@ -1945,6 +1945,7 @@ private:
         case PutHint:
         case CheckStructureImmediate:
         case CheckStructureOrEmpty:
+        case CheckArrayOrEmpty:
         case MaterializeNewObject:
         case MaterializeCreateActivation:
         case MaterializeNewInternalFieldObject:
index 344a2ab..13455b5 100644 (file)
@@ -479,6 +479,12 @@ public:
         m_op = CheckStructureImmediate;
         children.setChild1(Edge(structure, CellUse));
     }
+
+    void convertCheckArrayOrEmptyToCheckArray()
+    {
+        ASSERT(op() == CheckArrayOrEmpty);
+        setOpAndDefaultFlags(CheckArray);
+    }
     
     void replaceWith(Graph&, Node* other);
     void replaceWithWithoutChecks(Node* other);
@@ -2145,6 +2151,7 @@ public:
         case StringCharCodeAt:
         case StringCodePointAt:
         case CheckArray:
+        case CheckArrayOrEmpty:
         case Arrayify:
         case ArrayifyToStructure:
         case ArrayPush:
index 547fe4d..e211ea7 100644 (file)
@@ -238,6 +238,7 @@ namespace JSC { namespace DFG {
     macro(GetButterfly, NodeResultStorage) \
     macro(NukeStructureAndSetButterfly, NodeMustGenerate) \
     macro(CheckArray, NodeMustGenerate) \
+    macro(CheckArrayOrEmpty, NodeMustGenerate) \
     /* This checks if the edge is a typed array and if it is neutered. */ \
     macro(CheckNeutered, NodeMustGenerate) \
     macro(Arrayify, NodeMustGenerate) \
index 9b8d60d..288858b 100644 (file)
@@ -1292,6 +1292,7 @@ private:
         case PutHint:
         case CheckStructureImmediate:
         case CheckStructureOrEmpty:
+        case CheckArrayOrEmpty:
         case MaterializeNewObject:
         case MaterializeCreateActivation:
         case MaterializeNewInternalFieldObject:
index ecdd993..c4c2a2b 100644 (file)
@@ -161,6 +161,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno
         switch (node->op()) {
         case CheckNotEmpty:
         case CheckStructureOrEmpty:
+        case CheckArrayOrEmpty:
             break;
         default:
             return false;
@@ -278,6 +279,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno
     case CallDOM:
     case CheckSubClass:
     case CheckArray:
+    case CheckArrayOrEmpty:
     case CheckNeutered:
     case Arrayify:
     case ArrayifyToStructure:
index 13f28ce..6302986 100644 (file)
@@ -829,10 +829,38 @@ void SpeculativeJIT::checkArray(Node* node)
     GPRReg baseReg = base.gpr();
     
     if (node->arrayMode().alreadyChecked(m_jit.graph(), node, m_state.forNode(node->child1()))) {
+        // We can purge Empty check completely in this case of CheckArrayOrEmpty since CellUse only accepts SpecCell | SpecEmpty.
+        ASSERT(typeFilterFor(node->child1().useKind()) & SpecEmpty);
         noResult(m_currentNode);
         return;
     }
-    
+
+    Optional<GPRTemporary> temp;
+    Optional<GPRReg> tempGPR;
+    switch (node->arrayMode().type()) {
+    case Array::Int32:
+    case Array::Double:
+    case Array::Contiguous:
+    case Array::Undecided:
+    case Array::ArrayStorage:
+    case Array::SlowPutArrayStorage: {
+        temp.emplace(this);
+        tempGPR = temp->gpr();
+        break;
+    }
+    default:
+        break;
+    }
+
+    CCallHelpers::Jump isEmpty;
+
+#if USE(JSVALUE64)
+    if (node->op() == CheckArrayOrEmpty) {
+        if (m_interpreter.forNode(node->child1()).m_type & SpecEmpty)
+            isEmpty = m_jit.branchIfEmpty(baseReg);
+    }
+#endif
+
     switch (node->arrayMode().type()) {
     case Array::AnyTypedArray:
     case Array::String:
@@ -844,31 +872,28 @@ void SpeculativeJIT::checkArray(Node* node)
     case Array::Undecided:
     case Array::ArrayStorage:
     case Array::SlowPutArrayStorage: {
-        GPRTemporary temp(this);
-        GPRReg tempGPR = temp.gpr();
-        m_jit.load8(MacroAssembler::Address(baseReg, JSCell::indexingTypeAndMiscOffset()), tempGPR);
+        m_jit.load8(MacroAssembler::Address(baseReg, JSCell::indexingTypeAndMiscOffset()), tempGPR.value());
         speculationCheck(
             BadIndexingType, JSValueSource::unboxedCell(baseReg), 0,
-            jumpSlowForUnwantedArrayMode(tempGPR, node->arrayMode()));
-        
-        noResult(m_currentNode);
-        return;
+            jumpSlowForUnwantedArrayMode(tempGPR.value(), node->arrayMode()));
+        break;
     }
     case Array::DirectArguments:
         speculateCellTypeWithoutTypeFiltering(node->child1(), baseReg, DirectArgumentsType);
-        noResult(m_currentNode);
-        return;
+        break;
     case Array::ScopedArguments:
         speculateCellTypeWithoutTypeFiltering(node->child1(), baseReg, ScopedArgumentsType);
-        noResult(m_currentNode);
-        return;
+        break;
     default:
         speculateCellTypeWithoutTypeFiltering(
             node->child1(), baseReg,
             typeForTypedArrayType(node->arrayMode().typedArrayType()));
-        noResult(m_currentNode);
-        return;
+        break;
     }
+
+    if (isEmpty.isSet())
+        isEmpty.link(&m_jit);
+    noResult(m_currentNode);
 }
 
 void SpeculativeJIT::arrayify(Node* node, GPRReg baseReg, GPRReg propertyReg)
index fa066bf..ecfc903 100644 (file)
@@ -4181,6 +4181,10 @@ void SpeculativeJIT::compile(Node* node)
     case CheckStructureOrEmpty:
         DFG_CRASH(m_jit.graph(), node, "CheckStructureOrEmpty only used in 64-bit DFG");
         break;
+
+    case CheckArrayOrEmpty:
+        DFG_CRASH(m_jit.graph(), node, "CheckArrayOrEmpty only used in 64-bit DFG");
+        break;
         
     case FilterCallLinkStatus:
     case FilterGetByStatus:
index 804369d..3728b85 100644 (file)
@@ -2375,7 +2375,8 @@ void SpeculativeJIT::compile(Node* node)
         compileCheckNeutered(node);
         break;
     }
-        
+
+    case CheckArrayOrEmpty:
     case CheckArray: {
         checkArray(node);
         break;
index f5b9ed2..7c7abe8 100644 (file)
@@ -143,26 +143,35 @@ public:
                     Node* getLocal = insertionSet.insertNode(
                         indexInBlock + 1, variable->prediction(), GetLocal, origin,
                         OpInfo(variable), Edge(node));
-                    if (iter->value.m_structure) {
-                        auto checkOp = CheckStructure;
-                        if (SpecCellCheck & SpecEmpty) {
-                            VirtualRegister local = node->variableAccessData()->operand().virtualRegister();
-                            auto* inlineCallFrame = node->origin.semantic.inlineCallFrame();
-                            if ((local - (inlineCallFrame ? inlineCallFrame->stackOffset : 0)) == virtualRegisterForArgumentIncludingThis(0)) {
-                                // |this| can be the TDZ value. The call entrypoint won't have |this| as TDZ,
-                                // but a catch or a loop OSR entry may have |this| be TDZ.
-                                checkOp = CheckStructureOrEmpty;
-                            }
+
+                    auto needsEmptyCheck = [](Node* node) -> bool {
+                        if (!(SpecCellCheck & SpecEmpty))
+                            return false;
+                        VirtualRegister local = node->variableAccessData()->operand().virtualRegister();
+                        auto* inlineCallFrame = node->origin.semantic.inlineCallFrame();
+                        if ((local - (inlineCallFrame ? inlineCallFrame->stackOffset : 0)) == virtualRegisterForArgumentIncludingThis(0)) {
+                            // |this| can be the TDZ value. The call entrypoint won't have |this| as TDZ,
+                            // but a catch or a loop OSR entry may have |this| be TDZ.
+                            return true;
                         }
+                        return false;
+                    };
 
+                    if (iter->value.m_structure) {
+                        auto checkOp = CheckStructure;
+                        if (needsEmptyCheck(node))
+                            checkOp = CheckStructureOrEmpty;
                         insertionSet.insertNode(
                             indexInBlock + 1, SpecNone, checkOp, origin,
                             OpInfo(m_graph.addStructureSet(iter->value.m_structure)),
                             Edge(getLocal, CellUse));
                     } else if (iter->value.m_arrayModeIsValid) {
                         ASSERT(iter->value.m_arrayModeHoistingOkay);
+                        auto checkOp = CheckArray;
+                        if (needsEmptyCheck(node))
+                            checkOp = CheckArrayOrEmpty;
                         insertionSet.insertNode(
-                            indexInBlock + 1, SpecNone, CheckArray, origin,
+                            indexInBlock + 1, SpecNone, checkOp, origin,
                             OpInfo(iter->value.m_arrayMode.asWord()),
                             Edge(getLocal, CellUse));
                     } else
@@ -188,12 +197,12 @@ public:
                     NodeOrigin origin = node->origin;
                     Edge child1 = node->child1();
                     
+                    // Note: On 64-bit platforms, cell checks allow the empty value to flow through.
+                    // This means that this structure/array check may see the empty value as input. We need
+                    // to emit a node that explicitly handles the empty value. Most of the time, CheckStructureOrEmpty/CheckArrayOrEmpty
+                    // will be folded to CheckStructure/CheckArray because AI proves that the incoming value is
+                    // definitely not empty.
                     if (iter->value.m_structure) {
-                        // Note: On 64-bit platforms, cell checks allow the empty value to flow through.
-                        // This means that this structure check may see the empty value as input. We need
-                        // to emit a node that explicitly handles the empty value. Most of the time, CheckStructureOrEmpty
-                        // will be folded to CheckStructure because AI proves that the incoming value is
-                        // definitely not empty.
                         insertionSet.insertNode(
                             indexForChecks, SpecNone, (SpecCellCheck & SpecEmpty) ? CheckStructureOrEmpty : CheckStructure,
                             originForChecks.withSemantic(origin.semantic),
@@ -202,7 +211,7 @@ public:
                     } else if (iter->value.m_arrayModeIsValid) {
                         ASSERT(iter->value.m_arrayModeHoistingOkay);
                         insertionSet.insertNode(
-                            indexForChecks, SpecNone, CheckArray,
+                            indexForChecks, SpecNone, (SpecCellCheck & SpecEmpty) ? CheckArrayOrEmpty : CheckArray,
                             originForChecks.withSemantic(origin.semantic),
                             OpInfo(iter->value.m_arrayMode.asWord()),
                             Edge(child1.node(), CellUse));
index e941887..3a77711 100644 (file)
@@ -286,6 +286,11 @@ public:
                     VALIDATE((node), !!node->child1());
                     VALIDATE((node), !!node->cellOperand()->value() && node->cellOperand()->value().isCell());
                     break;
+                case CheckArrayOrEmpty:
+                    VALIDATE((node), is64Bit());
+                    VALIDATE((node), !!node->child1());
+                    VALIDATE((node), node->child1().useKind() == CellUse);
+                    break;
                 case CheckStructureOrEmpty:
                     VALIDATE((node), is64Bit());
                     VALIDATE((node), !!node->child1());
index eba197f..5c69d1d 100644 (file)
@@ -203,6 +203,7 @@ inline CapabilityLevel canCompile(Node* node)
     case Check:
     case CheckVarargs:
     case CheckArray:
+    case CheckArrayOrEmpty:
     case CheckNeutered:
     case CountExecution:
     case SuperSamplerBegin:
index 0c58572..27befc8 100644 (file)
@@ -967,6 +967,9 @@ private:
         case CheckArray:
             compileCheckArray();
             break;
+        case CheckArrayOrEmpty:
+            compileCheckArrayOrEmpty();
+            break;
         case CheckNeutered:
             compileCheckNeutered();
             break;
@@ -4067,6 +4070,37 @@ private:
             m_out.logicalNot(isArrayTypeForCheckArray(cell, m_node->arrayMode())));
     }
 
+    void compileCheckArrayOrEmpty()
+    {
+        Edge edge = m_node->child1();
+        LValue cell = lowCell(edge);
+
+        if (m_node->arrayMode().alreadyChecked(m_graph, m_node, abstractValue(edge))) {
+            // We can purge Empty check of CheckArrayOrEmpty completely in this case since CellUse only accepts SpecCell | SpecEmpty.
+            ASSERT(typeFilterFor(m_node->child1().useKind()) & SpecEmpty);
+            return;
+        }
+
+        bool maySeeEmptyValue = m_interpreter.forNode(m_node->child1()).m_type & SpecEmpty;
+        LBasicBlock continuation = nullptr;
+        LBasicBlock lastNext = nullptr;
+        if (maySeeEmptyValue) {
+            LBasicBlock notEmpty = m_out.newBlock();
+            continuation = m_out.newBlock();
+            m_out.branch(m_out.isZero64(cell), unsure(continuation), unsure(notEmpty));
+            lastNext = m_out.appendTo(notEmpty, continuation);
+        }
+
+        speculate(
+            BadIndexingType, jsValueValue(cell), 0,
+            m_out.logicalNot(isArrayTypeForCheckArray(cell, m_node->arrayMode())));
+
+        if (maySeeEmptyValue) {
+            m_out.jump(continuation);
+            m_out.appendTo(continuation, lastNext);
+        }
+    }
+
     void compileCheckNeutered()
     {
         Edge edge = m_node->child1();