[JSC] Optimize more cases of something-compared-to-null/undefined
https://bugs.webkit.org/show_bug.cgi?id=148157
Patch by Benjamin Poulain <bpoulain@apple.com> on 2015-08-18
Reviewed by Geoffrey Garen and Filip Pizlo.
Source/JavaScriptCore:
CompareEq is fairly trivial if you assert one of the operands is either
null or undefined. Under those conditions, the only way to have "true"
is to have the other operand be null/undefined or have an object
that masquerades to undefined.
JSC already had a fast path in CompareEqConstant.
With this patch, I generalize this fast path to more cases and try
to eliminate the checks whenever possible.
CompareEq now does the job of CompareEqConstant. If any operand can
be proved to be undefined/other, its edge is set to OtherUse. Whenever
any edge is OtherUse, we generate the fast code we had for CompareEqConstant.
The AbstractInterpreter has additional checks to reduce the node to a constant
whenever possible.
There are two additional changes in this patch:
-The Fixup Phase tries to set edges to OtherUse early. This is done correctly
in ConstantFoldingPhase but setting it up early helps the phases relying
on Clobberize.
-The codegen for CompareEqConstant was improved. The reason is the comparison
for ObjectOrOther could be faster just because the codegen was better.
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize): Deleted.
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC): Deleted.
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNode.h:
(JSC::DFG::Node::isUndefinedOrNullConstant):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate): Deleted.
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute): Deleted.
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compilePeepHoleBranch):
(JSC::DFG::SpeculativeJIT::compare):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::isKnownNotOther):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined):
(JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined):
(JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull): Deleted.
(JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNull): Deleted.
(JSC::DFG::SpeculativeJIT::nonSpeculativeCompareNull): Deleted.
(JSC::DFG::SpeculativeJIT::compile): Deleted.
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined):
(JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined):
(JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull): Deleted.
(JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNull): Deleted.
(JSC::DFG::SpeculativeJIT::nonSpeculativeCompareNull): Deleted.
(JSC::DFG::SpeculativeJIT::compile): Deleted.
* dfg/DFGValidate.cpp:
(JSC::DFG::Validate::validate): Deleted.
* dfg/DFGWatchpointCollectionPhase.cpp:
(JSC::DFG::WatchpointCollectionPhase::handle):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::DFG::LowerDFGToLLVM::compileCompareEq):
(JSC::FTL::DFG::LowerDFGToLLVM::compileNode): Deleted.
(JSC::FTL::DFG::LowerDFGToLLVM::compileCompareEqConstant): Deleted.
* tests/stress/compare-eq-on-null-and-undefined-non-peephole.js: Added.
(string_appeared_here.useForMath):
(testUseForMath):
* tests/stress/compare-eq-on-null-and-undefined-optimized-in-constant-folding.js: Added.
(string_appeared_here.unreachableCodeTest):
(inlinedCompareToNull):
(inlinedComparedToUndefined):
(warmupInlineFunctions):
(testInlineFunctions):
* tests/stress/compare-eq-on-null-and-undefined.js: Added.
(string_appeared_here.compareConstants):
(opaqueNull):
(opaqueUndefined):
(compareConstantsAndDynamicValues):
(compareDynamicValues):
(compareDynamicValueToItself):
(arrayTesting):
(opaqueCompare1):
(testNullComparatorUpdate):
(opaqueCompare2):
(testUndefinedComparatorUpdate):
(opaqueCompare3):
(testNullAndUndefinedComparatorUpdate):
LayoutTests:
* js/dom/document-all-watchpoint-covers-eliminated-compare-eq-expected.txt: Added.
* js/dom/document-all-watchpoint-covers-eliminated-compare-eq.html: Added.
* js/dom/script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js: Added.
(compareFunction):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@188624 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 26e76b0..76947d9 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,15 @@
+2015-08-18 Benjamin Poulain <bpoulain@apple.com>
+
+ [JSC] Optimize more cases of something-compared-to-null/undefined
+ https://bugs.webkit.org/show_bug.cgi?id=148157
+
+ Reviewed by Geoffrey Garen and Filip Pizlo.
+
+ * js/dom/document-all-watchpoint-covers-eliminated-compare-eq-expected.txt: Added.
+ * js/dom/document-all-watchpoint-covers-eliminated-compare-eq.html: Added.
+ * js/dom/script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js: Added.
+ (compareFunction):
+
2015-08-18 Wenson Hsieh <wenson_hsieh@apple.com>
Attempt to fix the failing search-padding-cancel-results-buttons.html test by making
diff --git a/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq-expected.txt b/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq-expected.txt
new file mode 100644
index 0000000..cb95c98
--- /dev/null
+++ b/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq-expected.txt
@@ -0,0 +1,12 @@
+Test to make sure that document.all works correctly with elminated CompareEq in DFG.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS documentAllCompare.isNull is true
+PASS documentAllCompare.isUndefined is true
+PASS documentAllCompare.length is 13
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq.html b/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq.html
new file mode 100644
index 0000000..7c4371a
--- /dev/null
+++ b/LayoutTests/js/dom/document-all-watchpoint-covers-eliminated-compare-eq.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js"></script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js b/LayoutTests/js/dom/script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js
new file mode 100644
index 0000000..f5db6f2
--- /dev/null
+++ b/LayoutTests/js/dom/script-tests/document-all-watchpoint-covers-eliminated-compare-eq.js
@@ -0,0 +1,49 @@
+description("Test to make sure that document.all works correctly with elminated CompareEq in DFG.");
+
+function compareFunction(a)
+{
+ var length = a.length;
+
+ var aIsNull = (a == null) || (null == a);
+ var aIsUndefined = (a == undefined) || (undefined == a);
+
+ if (a == null || undefined == a)
+ return { isNull: aIsNull, isUndefined: aIsUndefined, length: length };
+ else
+ return { isNull: aIsNull, isUndefined: aIsUndefined };
+}
+
+// Warmup with sane objects.
+for (let i = 0; i < 1e4; ++i) {
+ let result = compareFunction({ length: 5});
+ if (result.isNull || result.isUndefined)
+ debug("Failed warmup with compareFunction({ length: 5}).");
+
+ let object = new Object;
+ object.length = 1;
+ result = compareFunction(object);
+ if (result.isNull || result.isUndefined)
+ debug("Failed warmup with compareFunction(object).");
+}
+
+let documentAll = document.all;
+var documentAllCompare = compareFunction(documentAll);
+shouldBeTrue("documentAllCompare.isNull");
+shouldBeTrue("documentAllCompare.isUndefined");
+shouldBe("documentAllCompare.length", "13");
+
+for (let i = 0; i < 1e3; ++i) {
+ let result = compareFunction({ length: 5});
+ if (result.isNull || result.isUndefined)
+ debug("Failed tail with compareFunction({ length: 5}).");
+
+ result = compareFunction(documentAll);
+ if (!result.isNull || !result.isUndefined)
+ debug("Failed tail with compareFunction(documentAll).");
+
+ let object = new Object;
+ object.length = 1;
+ result = compareFunction(object);
+ if (result.isNull || result.isUndefined)
+ debug("Failed tail with compareFunction(object).");
+}
\ No newline at end of file
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index e777d4a..1e6580a 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,105 @@
+2015-08-18 Benjamin Poulain <bpoulain@apple.com>
+
+ [JSC] Optimize more cases of something-compared-to-null/undefined
+ https://bugs.webkit.org/show_bug.cgi?id=148157
+
+ Reviewed by Geoffrey Garen and Filip Pizlo.
+
+ CompareEq is fairly trivial if you assert one of the operands is either
+ null or undefined. Under those conditions, the only way to have "true"
+ is to have the other operand be null/undefined or have an object
+ that masquerades to undefined.
+
+ JSC already had a fast path in CompareEqConstant.
+ With this patch, I generalize this fast path to more cases and try
+ to eliminate the checks whenever possible.
+
+ CompareEq now does the job of CompareEqConstant. If any operand can
+ be proved to be undefined/other, its edge is set to OtherUse. Whenever
+ any edge is OtherUse, we generate the fast code we had for CompareEqConstant.
+
+ The AbstractInterpreter has additional checks to reduce the node to a constant
+ whenever possible.
+
+ There are two additional changes in this patch:
+ -The Fixup Phase tries to set edges to OtherUse early. This is done correctly
+ in ConstantFoldingPhase but setting it up early helps the phases relying
+ on Clobberize.
+ -The codegen for CompareEqConstant was improved. The reason is the comparison
+ for ObjectOrOther could be faster just because the codegen was better.
+
+ * dfg/DFGAbstractInterpreterInlines.h:
+ (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+ * dfg/DFGByteCodeParser.cpp:
+ (JSC::DFG::ByteCodeParser::parseBlock):
+ * dfg/DFGClobberize.h:
+ (JSC::DFG::clobberize): Deleted.
+ * dfg/DFGConstantFoldingPhase.cpp:
+ (JSC::DFG::ConstantFoldingPhase::foldConstants):
+ * dfg/DFGDoesGC.cpp:
+ (JSC::DFG::doesGC): Deleted.
+ * dfg/DFGFixupPhase.cpp:
+ (JSC::DFG::FixupPhase::fixupNode):
+ * dfg/DFGNode.h:
+ (JSC::DFG::Node::isUndefinedOrNullConstant):
+ * dfg/DFGNodeType.h:
+ * dfg/DFGPredictionPropagationPhase.cpp:
+ (JSC::DFG::PredictionPropagationPhase::propagate): Deleted.
+ * dfg/DFGSafeToExecute.h:
+ (JSC::DFG::safeToExecute): Deleted.
+ * dfg/DFGSpeculativeJIT.cpp:
+ (JSC::DFG::SpeculativeJIT::compilePeepHoleBranch):
+ (JSC::DFG::SpeculativeJIT::compare):
+ * dfg/DFGSpeculativeJIT.h:
+ (JSC::DFG::SpeculativeJIT::isKnownNotOther):
+ * dfg/DFGSpeculativeJIT32_64.cpp:
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined):
+ (JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined):
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeCompareNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::compile): Deleted.
+ * dfg/DFGSpeculativeJIT64.cpp:
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined):
+ (JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined):
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::nonSpeculativePeepholeBranchNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::nonSpeculativeCompareNull): Deleted.
+ (JSC::DFG::SpeculativeJIT::compile): Deleted.
+ * dfg/DFGValidate.cpp:
+ (JSC::DFG::Validate::validate): Deleted.
+ * dfg/DFGWatchpointCollectionPhase.cpp:
+ (JSC::DFG::WatchpointCollectionPhase::handle):
+ * ftl/FTLCapabilities.cpp:
+ (JSC::FTL::canCompile):
+ * ftl/FTLLowerDFGToLLVM.cpp:
+ (JSC::FTL::DFG::LowerDFGToLLVM::compileCompareEq):
+ (JSC::FTL::DFG::LowerDFGToLLVM::compileNode): Deleted.
+ (JSC::FTL::DFG::LowerDFGToLLVM::compileCompareEqConstant): Deleted.
+ * tests/stress/compare-eq-on-null-and-undefined-non-peephole.js: Added.
+ (string_appeared_here.useForMath):
+ (testUseForMath):
+ * tests/stress/compare-eq-on-null-and-undefined-optimized-in-constant-folding.js: Added.
+ (string_appeared_here.unreachableCodeTest):
+ (inlinedCompareToNull):
+ (inlinedComparedToUndefined):
+ (warmupInlineFunctions):
+ (testInlineFunctions):
+ * tests/stress/compare-eq-on-null-and-undefined.js: Added.
+ (string_appeared_here.compareConstants):
+ (opaqueNull):
+ (opaqueUndefined):
+ (compareConstantsAndDynamicValues):
+ (compareDynamicValues):
+ (compareDynamicValueToItself):
+ (arrayTesting):
+ (opaqueCompare1):
+ (testNullComparatorUpdate):
+ (opaqueCompare2):
+ (testUndefinedComparatorUpdate):
+ (opaqueCompare3):
+ (testNullAndUndefinedComparatorUpdate):
+
2015-08-18 Yusuke Suzuki <utatane.tea@gmail.com>
Introduce non-user-observable Promise functions to use Promises internally
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index 1bd8f69..5e2a60b 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -1139,8 +1139,7 @@
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq:
- case CompareEq:
- case CompareEqConstant: {
+ case CompareEq: {
JSValue leftConst = forNode(node->child1()).value();
JSValue rightConst = forNode(node->child2()).value();
if (leftConst && rightConst) {
@@ -1180,13 +1179,40 @@
}
}
- if (node->op() == CompareEqConstant || node->op() == CompareEq) {
+ if (node->op() == CompareEq) {
SpeculatedType leftType = forNode(node->child1()).m_type;
SpeculatedType rightType = forNode(node->child2()).m_type;
if (!valuesCouldBeEqual(leftType, rightType)) {
setConstant(node, jsBoolean(false));
break;
}
+
+ if (leftType == SpecOther)
+ std::swap(leftType, rightType);
+ if (rightType == SpecOther) {
+ // Undefined and Null are always equal when compared to eachother.
+ if (!(leftType & ~SpecOther)) {
+ setConstant(node, jsBoolean(true));
+ break;
+ }
+
+ // Any other type compared to Null or Undefined is always false
+ // as long as the MasqueradesAsUndefined watchpoint is valid.
+ //
+ // MasqueradesAsUndefined only matters for SpecObjectOther, other
+ // cases are always "false".
+ if (!(leftType & (SpecObjectOther | SpecOther))) {
+ setConstant(node, jsBoolean(false));
+ break;
+ }
+
+ if (!(leftType & SpecOther) && m_graph.masqueradesAsUndefinedWatchpointIsStillValid(node->origin.semantic)) {
+ JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
+ m_graph.watchpoints().addLazily(globalObject->masqueradesAsUndefinedWatchpoint());
+ setConstant(node, jsBoolean(false));
+ break;
+ }
+ }
}
if (node->child1() == node->child2()) {
@@ -1206,7 +1232,6 @@
case CompareLessEq:
case CompareGreaterEq:
case CompareEq:
- case CompareEqConstant:
setConstant(node, jsBoolean(true));
break;
default:
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index 53e1bef..9dcc747 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -3356,7 +3356,8 @@
case op_eq_null: {
Node* value = get(VirtualRegister(currentInstruction[2].u.operand));
- set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(CompareEqConstant, value, addToGraph(JSConstant, OpInfo(m_constantNull))));
+ Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull));
+ set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(CompareEq, value, nullConstant));
NEXT_OPCODE(op_eq_null);
}
@@ -3376,7 +3377,8 @@
case op_neq_null: {
Node* value = get(VirtualRegister(currentInstruction[2].u.operand));
- set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(LogicalNot, addToGraph(CompareEqConstant, value, addToGraph(JSConstant, OpInfo(m_constantNull)))));
+ Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull));
+ set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(LogicalNot, addToGraph(CompareEq, value, nullConstant)));
NEXT_OPCODE(op_neq_null);
}
@@ -3523,7 +3525,8 @@
case op_jeq_null: {
unsigned relativeOffset = currentInstruction[2].u.operand;
Node* value = get(VirtualRegister(currentInstruction[1].u.operand));
- Node* condition = addToGraph(CompareEqConstant, value, addToGraph(JSConstant, OpInfo(m_constantNull)));
+ Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull));
+ Node* condition = addToGraph(CompareEq, value, nullConstant);
addToGraph(Branch, OpInfo(branchData(m_currentIndex + relativeOffset, m_currentIndex + OPCODE_LENGTH(op_jeq_null))), condition);
LAST_OPCODE(op_jeq_null);
}
@@ -3531,7 +3534,8 @@
case op_jneq_null: {
unsigned relativeOffset = currentInstruction[2].u.operand;
Node* value = get(VirtualRegister(currentInstruction[1].u.operand));
- Node* condition = addToGraph(CompareEqConstant, value, addToGraph(JSConstant, OpInfo(m_constantNull)));
+ Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull));
+ Node* condition = addToGraph(CompareEq, value, nullConstant);
addToGraph(Branch, OpInfo(branchData(m_currentIndex + OPCODE_LENGTH(op_jneq_null), m_currentIndex + relativeOffset)), condition);
LAST_OPCODE(op_jneq_null);
}
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index 7727a9e..790d655 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -143,7 +143,6 @@
case SkipScope:
case StringCharCodeAt:
case StringFromCharCode:
- case CompareEqConstant:
case CompareStrictEq:
case IsUndefined:
case IsBoolean:
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index 3d85caa..d351e57 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -97,6 +97,14 @@
node->child1().setUseKind(BooleanUse);
break;
}
+
+ case CompareEq: {
+ if (!m_interpreter.needsTypeCheck(node->child1(), SpecOther))
+ node->child1().setUseKind(OtherUse);
+ if (!m_interpreter.needsTypeCheck(node->child2(), SpecOther))
+ node->child2().setUseKind(OtherUse);
+ break;
+ }
case CheckStructure:
case ArrayifyToStructure: {
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index 3547332..e1e409f 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -116,7 +116,6 @@
case CompareGreater:
case CompareGreaterEq:
case CompareEq:
- case CompareEqConstant:
case CompareStrictEq:
case Call:
case Construct:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 0ab3938..98b009e 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -368,10 +368,6 @@
fixEdge<StringUse>(node->child1());
break;
}
-
- case CompareEqConstant: {
- break;
- }
case CompareEq:
case CompareLess:
@@ -424,6 +420,32 @@
node->clearFlags(NodeMustGenerate);
break;
}
+
+ // If either child can be proved to be Null or Undefined, comparing them is greatly simplified.
+ bool oneArgumentIsUsedAsSpecOther = false;
+ if (node->child1()->isUndefinedOrNullConstant()) {
+ fixEdge<OtherUse>(node->child1());
+ oneArgumentIsUsedAsSpecOther = true;
+ } else if (node->child1()->shouldSpeculateOther()) {
+ m_insertionSet.insertNode(m_indexInBlock, SpecNone, Check, node->origin,
+ Edge(node->child1().node(), OtherUse));
+ fixEdge<OtherUse>(node->child1());
+ oneArgumentIsUsedAsSpecOther = true;
+ }
+ if (node->child2()->isUndefinedOrNullConstant()) {
+ fixEdge<OtherUse>(node->child2());
+ oneArgumentIsUsedAsSpecOther = true;
+ } else if (node->child2()->shouldSpeculateOther()) {
+ m_insertionSet.insertNode(m_indexInBlock, SpecNone, Check, node->origin,
+ Edge(node->child2().node(), OtherUse));
+ fixEdge<OtherUse>(node->child2());
+ oneArgumentIsUsedAsSpecOther = true;
+ }
+ if (oneArgumentIsUsedAsSpecOther) {
+ node->clearFlags(NodeMustGenerate);
+ break;
+ }
+
if (node->child1()->shouldSpeculateObject() && node->child2()->shouldSpeculateObjectOrOther()) {
fixEdge<ObjectUse>(node->child1());
fixEdge<ObjectOrOtherUse>(node->child2());
@@ -436,6 +458,7 @@
node->clearFlags(NodeMustGenerate);
break;
}
+
break;
}
diff --git a/Source/JavaScriptCore/dfg/DFGNode.h b/Source/JavaScriptCore/dfg/DFGNode.h
index 306327a..80033b0 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.h
+++ b/Source/JavaScriptCore/dfg/DFGNode.h
@@ -690,7 +690,12 @@
{
return constant()->value().asBoolean();
}
-
+
+ bool isUndefinedOrNullConstant()
+ {
+ return isConstant() && constant()->value().isUndefinedOrNull();
+ }
+
bool isCellConstant()
{
return isConstant() && constant()->value() && constant()->value().isCell();
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index 74aa984..0775c68 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -231,7 +231,6 @@
macro(CompareGreater, NodeResultBoolean | NodeMustGenerate) \
macro(CompareGreaterEq, NodeResultBoolean | NodeMustGenerate) \
macro(CompareEq, NodeResultBoolean | NodeMustGenerate) \
- macro(CompareEqConstant, NodeResultBoolean) \
macro(CompareStrictEq, NodeResultBoolean) \
\
/* Calls. */\
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index 355735f..00e4e86 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -371,7 +371,6 @@
case CompareGreater:
case CompareGreaterEq:
case CompareEq:
- case CompareEqConstant:
case CompareStrictEq:
case InstanceOf:
case IsUndefined:
diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
index f7787f4..b1fc5fb 100644
--- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
+++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
@@ -198,7 +198,6 @@
case CompareGreater:
case CompareGreaterEq:
case CompareEq:
- case CompareEqConstant:
case CompareStrictEq:
case Call:
case Construct:
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index aca4820..71fa605b 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -1369,6 +1369,10 @@
compilePeepHoleObjectToObjectOrOtherEquality(node->child1(), node->child2(), branchNode);
else if (node->isBinaryUseKind(ObjectOrOtherUse, ObjectUse))
compilePeepHoleObjectToObjectOrOtherEquality(node->child2(), node->child1(), branchNode);
+ else if (!needsTypeCheck(node->child1(), SpecOther))
+ nonSpeculativePeepholeBranchNullOrUndefined(node->child2(), branchNode);
+ else if (!needsTypeCheck(node->child2(), SpecOther))
+ nonSpeculativePeepholeBranchNullOrUndefined(node->child1(), branchNode);
else {
nonSpeculativePeepholeBranch(node, branchNode, condition, operation);
return true;
@@ -3900,8 +3904,18 @@
compileObjectToObjectOrOtherEquality(node->child2(), node->child1());
return false;
}
+
+ if (!needsTypeCheck(node->child1(), SpecOther)) {
+ nonSpeculativeNonPeepholeCompareNullOrUndefined(node->child2());
+ return false;
+ }
+
+ if (!needsTypeCheck(node->child2(), SpecOther)) {
+ nonSpeculativeNonPeepholeCompareNullOrUndefined(node->child1());
+ return false;
+ }
}
-
+
nonSpeculativeNonPeepholeCompare(node, condition, operation);
return false;
}
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index b2bbe62..b2008a1 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -556,6 +556,7 @@
bool isKnownNotInteger(Node* node) { return !(m_state.forNode(node).m_type & SpecInt32); }
bool isKnownNotNumber(Node* node) { return !(m_state.forNode(node).m_type & SpecFullNumber); }
bool isKnownNotCell(Node* node) { return !(m_state.forNode(node).m_type & SpecCell); }
+ bool isKnownNotOther(Node* node) { return !(m_state.forNode(node).m_type & SpecOther); }
UniquedStringImpl* identifierUID(unsigned index)
{
@@ -701,9 +702,8 @@
void compileBaseValueStoreBarrier(Edge& baseEdge, Edge& valueEdge);
- void nonSpeculativeNonPeepholeCompareNull(Edge operand, bool invert = false);
- void nonSpeculativePeepholeBranchNull(Edge operand, Node* branchNode, bool invert = false);
- bool nonSpeculativeCompareNull(Node*, Edge operand, bool invert = false);
+ void nonSpeculativeNonPeepholeCompareNullOrUndefined(Edge operand);
+ void nonSpeculativePeepholeBranchNullOrUndefined(Edge operand, Node* branchNode);
void nonSpeculativePeepholeBranch(Node*, Node* branchNode, MacroAssembler::RelationalCondition, S_JITOperation_EJJ helperFunction);
void nonSpeculativeNonPeepholeCompare(Node*, MacroAssembler::RelationalCondition, S_JITOperation_EJJ helperFunction);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index 7512096..bba76c5 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -234,9 +234,9 @@
addSlowPathGenerator(WTF::move(slowPath));
}
-void SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull(Edge operand, bool invert)
+void SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined(Edge operand)
{
- JSValueOperand arg(this, operand);
+ JSValueOperand arg(this, operand, ManualOperandSpeculation);
GPRReg argTagGPR = arg.tagGPR();
GPRReg argPayloadGPR = arg.payloadGPR();
@@ -249,7 +249,7 @@
if (!isKnownCell(operand.node()))
notCell = m_jit.branchIfNotCell(arg.jsValueRegs());
- m_jit.move(invert ? TrustedImm32(1) : TrustedImm32(0), resultPayloadGPR);
+ m_jit.move(TrustedImm32(0), resultPayloadGPR);
notMasqueradesAsUndefined = m_jit.jump();
} else {
GPRTemporary localGlobalObject(this);
@@ -263,7 +263,7 @@
JITCompiler::Address(argPayloadGPR, JSCell::typeInfoFlagsOffset()),
JITCompiler::TrustedImm32(MasqueradesAsUndefined));
- m_jit.move(invert ? TrustedImm32(1) : TrustedImm32(0), resultPayloadGPR);
+ m_jit.move(TrustedImm32(0), resultPayloadGPR);
notMasqueradesAsUndefined = m_jit.jump();
isMasqueradesAsUndefined.link(&m_jit);
@@ -272,7 +272,7 @@
m_jit.move(JITCompiler::TrustedImmPtr(m_jit.graph().globalObjectFor(m_currentNode->origin.semantic)), localGlobalObjectGPR);
m_jit.loadPtr(JITCompiler::Address(argPayloadGPR, JSCell::structureIDOffset()), resultPayloadGPR);
m_jit.loadPtr(JITCompiler::Address(resultPayloadGPR, Structure::globalObjectOffset()), remoteGlobalObjectGPR);
- m_jit.compare32(invert ? JITCompiler::NotEqual : JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, resultPayloadGPR);
+ m_jit.compare32(JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, resultPayloadGPR);
}
if (!isKnownCell(operand.node())) {
@@ -282,7 +282,7 @@
// null or undefined?
COMPILE_ASSERT((JSValue::UndefinedTag | 1) == JSValue::NullTag, UndefinedTag_OR_1_EQUALS_NullTag);
m_jit.or32(TrustedImm32(1), argTagGPR, resultPayloadGPR);
- m_jit.compare32(invert ? JITCompiler::NotEqual : JITCompiler::Equal, resultPayloadGPR, TrustedImm32(JSValue::NullTag), resultPayloadGPR);
+ m_jit.compare32(JITCompiler::Equal, resultPayloadGPR, TrustedImm32(JSValue::NullTag), resultPayloadGPR);
done.link(&m_jit);
}
@@ -292,11 +292,12 @@
booleanResult(resultPayloadGPR, m_currentNode);
}
-void SpeculativeJIT::nonSpeculativePeepholeBranchNull(Edge operand, Node* branchNode, bool invert)
+void SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined(Edge operand, Node* branchNode)
{
BasicBlock* taken = branchNode->branchData()->taken.block;
BasicBlock* notTaken = branchNode->branchData()->notTaken.block;
-
+
+ bool invert = false;
if (taken == nextBlock()) {
invert = !invert;
BasicBlock* tmp = taken;
@@ -304,7 +305,7 @@
notTaken = tmp;
}
- JSValueOperand arg(this, operand);
+ JSValueOperand arg(this, operand, ManualOperandSpeculation);
GPRReg argTagGPR = arg.tagGPR();
GPRReg argPayloadGPR = arg.payloadGPR();
@@ -351,29 +352,6 @@
jump(notTaken);
}
-bool SpeculativeJIT::nonSpeculativeCompareNull(Node* node, Edge operand, bool invert)
-{
- unsigned branchIndexInBlock = detectPeepHoleBranch();
- if (branchIndexInBlock != UINT_MAX) {
- Node* branchNode = m_block->at(branchIndexInBlock);
-
- ASSERT(node->adjustedRefCount() == 1);
-
- nonSpeculativePeepholeBranchNull(operand, branchNode, invert);
-
- use(node->child1());
- use(node->child2());
- m_indexInBlock = branchIndexInBlock;
- m_currentNode = branchNode;
-
- return true;
- }
-
- nonSpeculativeNonPeepholeCompareNull(operand, invert);
-
- return false;
-}
-
void SpeculativeJIT::nonSpeculativePeepholeBranch(Node* node, Node* branchNode, MacroAssembler::RelationalCondition cond, S_JITOperation_EJJ helperFunction)
{
BasicBlock* taken = branchNode->branchData()->taken.block;
@@ -2278,12 +2256,6 @@
if (compare(node, JITCompiler::GreaterThanOrEqual, JITCompiler::DoubleGreaterThanOrEqual, operationCompareGreaterEq))
return;
break;
-
- case CompareEqConstant:
- ASSERT(node->child2()->asJSValue().isNull());
- if (nonSpeculativeCompareNull(node, node->child1()))
- return;
- break;
case CompareEq:
if (compare(node, JITCompiler::Equal, JITCompiler::DoubleEqual, operationCompareEq))
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index aff7314..1398c60 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -192,146 +192,118 @@
addSlowPathGenerator(WTF::move(slowPath));
}
-void SpeculativeJIT::nonSpeculativeNonPeepholeCompareNull(Edge operand, bool invert)
+void SpeculativeJIT::nonSpeculativeNonPeepholeCompareNullOrUndefined(Edge operand)
{
- JSValueOperand arg(this, operand);
+ ASSERT_WITH_MESSAGE(!masqueradesAsUndefinedWatchpointIsStillValid() || !isKnownCell(operand.node()), "The Compare should have been eliminated, it is known to be always false.");
+
+ JSValueOperand arg(this, operand, ManualOperandSpeculation);
GPRReg argGPR = arg.gpr();
- GPRTemporary result(this, Reuse, arg);
+ GPRTemporary result(this);
GPRReg resultGPR = result.gpr();
-
- JITCompiler::Jump notCell;
-
- JITCompiler::Jump notMasqueradesAsUndefined;
- if (masqueradesAsUndefinedWatchpointIsStillValid()) {
- if (!isKnownCell(operand.node()))
- notCell = m_jit.branchIfNotCell(JSValueRegs(argGPR));
- m_jit.move(invert ? TrustedImm32(1) : TrustedImm32(0), resultGPR);
- notMasqueradesAsUndefined = m_jit.jump();
+ m_jit.move(TrustedImm32(0), resultGPR);
+
+ JITCompiler::JumpList done;
+ if (masqueradesAsUndefinedWatchpointIsStillValid()) {
+ if (!isKnownNotCell(operand.node()))
+ done.append(m_jit.branchIfCell(JSValueRegs(argGPR)));
} else {
GPRTemporary localGlobalObject(this);
GPRTemporary remoteGlobalObject(this);
GPRTemporary scratch(this);
+ JITCompiler::Jump notCell;
if (!isKnownCell(operand.node()))
notCell = m_jit.branchIfNotCell(JSValueRegs(argGPR));
- JITCompiler::Jump isMasqueradesAsUndefined = m_jit.branchTest8(
- JITCompiler::NonZero,
+ JITCompiler::Jump isNotMasqueradesAsUndefined = m_jit.branchTest8(
+ JITCompiler::Zero,
JITCompiler::Address(argGPR, JSCell::typeInfoFlagsOffset()),
JITCompiler::TrustedImm32(MasqueradesAsUndefined));
+ done.append(isNotMasqueradesAsUndefined);
- m_jit.move(invert ? TrustedImm32(1) : TrustedImm32(0), resultGPR);
- notMasqueradesAsUndefined = m_jit.jump();
-
- isMasqueradesAsUndefined.link(&m_jit);
GPRReg localGlobalObjectGPR = localGlobalObject.gpr();
GPRReg remoteGlobalObjectGPR = remoteGlobalObject.gpr();
m_jit.move(JITCompiler::TrustedImmPtr(m_jit.graph().globalObjectFor(m_currentNode->origin.semantic)), localGlobalObjectGPR);
m_jit.emitLoadStructure(argGPR, resultGPR, scratch.gpr());
m_jit.loadPtr(JITCompiler::Address(resultGPR, Structure::globalObjectOffset()), remoteGlobalObjectGPR);
- m_jit.comparePtr(invert ? JITCompiler::NotEqual : JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, resultGPR);
+ m_jit.comparePtr(JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, resultGPR);
+ done.append(m_jit.jump());
+ if (!isKnownCell(operand.node()))
+ notCell.link(&m_jit);
}
- if (!isKnownCell(operand.node())) {
- JITCompiler::Jump done = m_jit.jump();
-
- notCell.link(&m_jit);
-
+ if (!isKnownNotOther(operand.node())) {
m_jit.move(argGPR, resultGPR);
m_jit.and64(JITCompiler::TrustedImm32(~TagBitUndefined), resultGPR);
- m_jit.compare64(invert ? JITCompiler::NotEqual : JITCompiler::Equal, resultGPR, JITCompiler::TrustedImm32(ValueNull), resultGPR);
-
- done.link(&m_jit);
+ m_jit.compare64(JITCompiler::Equal, resultGPR, JITCompiler::TrustedImm32(ValueNull), resultGPR);
}
-
- notMasqueradesAsUndefined.link(&m_jit);
+
+ done.link(&m_jit);
m_jit.or32(TrustedImm32(ValueFalse), resultGPR);
jsValueResult(resultGPR, m_currentNode, DataFormatJSBoolean);
}
-void SpeculativeJIT::nonSpeculativePeepholeBranchNull(Edge operand, Node* branchNode, bool invert)
+void SpeculativeJIT::nonSpeculativePeepholeBranchNullOrUndefined(Edge operand, Node* branchNode)
{
+ ASSERT_WITH_MESSAGE(!masqueradesAsUndefinedWatchpointIsStillValid() || !isKnownCell(operand.node()), "The Compare should have been eliminated, it is known to be always false.");
+
BasicBlock* taken = branchNode->branchData()->taken.block;
BasicBlock* notTaken = branchNode->branchData()->notTaken.block;
-
- if (taken == nextBlock()) {
- invert = !invert;
- BasicBlock* tmp = taken;
- taken = notTaken;
- notTaken = tmp;
- }
- JSValueOperand arg(this, operand);
+ JSValueOperand arg(this, operand, ManualOperandSpeculation);
GPRReg argGPR = arg.gpr();
GPRTemporary result(this, Reuse, arg);
GPRReg resultGPR = result.gpr();
-
- JITCompiler::Jump notCell;
-
+
+ // First, handle the case where "operand" is a cell.
if (masqueradesAsUndefinedWatchpointIsStillValid()) {
- if (!isKnownCell(operand.node()))
- notCell = m_jit.branchIfNotCell(JSValueRegs(argGPR));
-
- jump(invert ? taken : notTaken, ForceJump);
+ if (!isKnownNotCell(operand.node())) {
+ JITCompiler::Jump isCell = m_jit.branchIfCell(JSValueRegs(argGPR));
+ addBranch(isCell, notTaken);
+ }
} else {
GPRTemporary localGlobalObject(this);
GPRTemporary remoteGlobalObject(this);
GPRTemporary scratch(this);
+ JITCompiler::Jump notCell;
if (!isKnownCell(operand.node()))
notCell = m_jit.branchIfNotCell(JSValueRegs(argGPR));
branchTest8(JITCompiler::Zero,
JITCompiler::Address(argGPR, JSCell::typeInfoFlagsOffset()),
- JITCompiler::TrustedImm32(MasqueradesAsUndefined),
- invert ? taken : notTaken);
+ JITCompiler::TrustedImm32(MasqueradesAsUndefined), notTaken);
GPRReg localGlobalObjectGPR = localGlobalObject.gpr();
GPRReg remoteGlobalObjectGPR = remoteGlobalObject.gpr();
m_jit.move(TrustedImmPtr(m_jit.graph().globalObjectFor(m_currentNode->origin.semantic)), localGlobalObjectGPR);
m_jit.emitLoadStructure(argGPR, resultGPR, scratch.gpr());
m_jit.loadPtr(JITCompiler::Address(resultGPR, Structure::globalObjectOffset()), remoteGlobalObjectGPR);
- branchPtr(JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, invert ? notTaken : taken);
+ branchPtr(JITCompiler::Equal, localGlobalObjectGPR, remoteGlobalObjectGPR, taken);
+
+ if (!isKnownCell(operand.node())) {
+ jump(notTaken, ForceJump);
+ notCell.link(&m_jit);
+ }
}
-
- if (!isKnownCell(operand.node())) {
- jump(notTaken, ForceJump);
-
- notCell.link(&m_jit);
-
+
+ if (isKnownNotOther(operand.node()))
+ jump(notTaken);
+ else {
+ JITCompiler::RelationalCondition condition = JITCompiler::Equal;
+ if (taken == nextBlock()) {
+ condition = JITCompiler::NotEqual;
+ std::swap(taken, notTaken);
+ }
m_jit.move(argGPR, resultGPR);
m_jit.and64(JITCompiler::TrustedImm32(~TagBitUndefined), resultGPR);
- branch64(invert ? JITCompiler::NotEqual : JITCompiler::Equal, resultGPR, JITCompiler::TrustedImm64(ValueNull), taken);
+ branch64(condition, resultGPR, JITCompiler::TrustedImm64(ValueNull), taken);
+ jump(notTaken);
}
-
- jump(notTaken);
-}
-
-bool SpeculativeJIT::nonSpeculativeCompareNull(Node* node, Edge operand, bool invert)
-{
- unsigned branchIndexInBlock = detectPeepHoleBranch();
- if (branchIndexInBlock != UINT_MAX) {
- Node* branchNode = m_block->at(branchIndexInBlock);
-
- DFG_ASSERT(m_jit.graph(), node, node->adjustedRefCount() == 1);
-
- nonSpeculativePeepholeBranchNull(operand, branchNode, invert);
-
- use(node->child1());
- use(node->child2());
- m_indexInBlock = branchIndexInBlock;
- m_currentNode = branchNode;
-
- return true;
- }
-
- nonSpeculativeNonPeepholeCompareNull(operand, invert);
-
- return false;
}
void SpeculativeJIT::nonSpeculativePeepholeBranch(Node* node, Node* branchNode, MacroAssembler::RelationalCondition cond, S_JITOperation_EJJ helperFunction)
@@ -2411,12 +2383,6 @@
if (compare(node, JITCompiler::GreaterThanOrEqual, JITCompiler::DoubleGreaterThanOrEqual, operationCompareGreaterEq))
return;
break;
-
- case CompareEqConstant:
- ASSERT(node->child2()->asJSValue().isNull());
- if (nonSpeculativeCompareNull(node, node->child1()))
- return;
- break;
case CompareEq:
if (compare(node, JITCompiler::Equal, JITCompiler::DoubleEqual, operationCompareEq))
diff --git a/Source/JavaScriptCore/dfg/DFGValidate.cpp b/Source/JavaScriptCore/dfg/DFGValidate.cpp
index 6a134d0..eca843b 100644
--- a/Source/JavaScriptCore/dfg/DFGValidate.cpp
+++ b/Source/JavaScriptCore/dfg/DFGValidate.cpp
@@ -236,7 +236,6 @@
case CompareGreater:
case CompareGreaterEq:
case CompareEq:
- case CompareEqConstant:
case CompareStrictEq:
VALIDATE((node), !!node->child1());
VALIDATE((node), !!node->child2());
diff --git a/Source/JavaScriptCore/dfg/DFGWatchpointCollectionPhase.cpp b/Source/JavaScriptCore/dfg/DFGWatchpointCollectionPhase.cpp
index f924e4a..40caac8 100644
--- a/Source/JavaScriptCore/dfg/DFGWatchpointCollectionPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGWatchpointCollectionPhase.cpp
@@ -73,7 +73,6 @@
void handle()
{
switch (m_node->op()) {
- case CompareEqConstant:
case IsUndefined:
handleMasqueradesAsUndefined();
break;
@@ -81,7 +80,8 @@
case CompareEq:
if (m_node->isBinaryUseKind(ObjectUse)
|| (m_node->child1().useKind() == ObjectUse && m_node->child2().useKind() == ObjectOrOtherUse)
- || (m_node->child1().useKind() == ObjectOrOtherUse && m_node->child2().useKind() == ObjectUse))
+ || (m_node->child1().useKind() == ObjectOrOtherUse && m_node->child2().useKind() == ObjectUse)
+ || (m_node->child1().useKind() == OtherUse || m_node->child2().useKind() == OtherUse))
handleMasqueradesAsUndefined();
break;
diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
index 7737421..a124e6a 100644
--- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
+++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
@@ -96,7 +96,6 @@
case ArithFRound:
case ArithNegate:
case UInt32ToNumber:
- case CompareEqConstant:
case Jump:
case ForceOSRExit:
case Phi:
@@ -321,6 +320,8 @@
break;
if (node->isBinaryUseKind(ObjectOrOtherUse, ObjectUse))
break;
+ if (node->child1().useKind() == OtherUse || node->child2().useKind() == OtherUse)
+ break;
return CannotCompile;
case CompareStrictEq:
if (node->isBinaryUseKind(Int32Use))
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
index 8a7d00d..1faacdb 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
@@ -701,9 +701,6 @@
case CompareEq:
compileCompareEq();
break;
- case CompareEqConstant:
- compileCompareEqConstant();
- break;
case CompareStrictEq:
compileCompareStrictEq();
break;
@@ -4146,18 +4143,22 @@
nonSpeculativeCompare(LLVMIntEQ, operationCompareEq);
return;
}
-
+
+ if (m_node->child1().useKind() == OtherUse) {
+ ASSERT(!m_interpreter.needsTypeCheck(m_node->child1(), SpecOther));
+ setBoolean(equalNullOrUndefined(m_node->child2(), AllCellsAreFalse, EqualNullOrUndefined, ManualOperandSpeculation));
+ return;
+ }
+
+ if (m_node->child2().useKind() == OtherUse) {
+ ASSERT(!m_interpreter.needsTypeCheck(m_node->child2(), SpecOther));
+ setBoolean(equalNullOrUndefined(m_node->child1(), AllCellsAreFalse, EqualNullOrUndefined, ManualOperandSpeculation));
+ return;
+ }
+
DFG_CRASH(m_graph, m_node, "Bad use kinds");
}
- void compileCompareEqConstant()
- {
- ASSERT(m_node->child2()->asJSValue().isNull());
- setBoolean(
- equalNullOrUndefined(
- m_node->child1(), AllCellsAreFalse, EqualNullOrUndefined));
- }
-
void compileCompareStrictEq()
{
if (m_node->isBinaryUseKind(Int32Use)) {
diff --git a/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-non-peephole.js b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-non-peephole.js
new file mode 100644
index 0000000..20ff95f
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-non-peephole.js
@@ -0,0 +1,45 @@
+"use strict"
+
+function useForMath(undefinedArgument, nullArgument, polymorphicArgument) {
+ var a = (null == undefinedArgument) + (undefinedArgument == null) + (undefined == undefinedArgument) + (undefinedArgument == undefined);
+ var b = (null == nullArgument) + (nullArgument == null) + (undefined == nullArgument) + (nullArgument == undefined);
+ var c = (null == polymorphicArgument) + (polymorphicArgument == null) + (undefined == polymorphicArgument) + (polymorphicArgument == undefined);
+ var d = (5 == null) + (null == true) + (undefined == Math.LN2) + ("const" == undefined);
+ var e = (5 == undefinedArgument) + (nullArgument == true) + (nullArgument == Math.LN2) + ("const" == undefinedArgument);
+
+ return a + b - c + d - e;
+}
+noInline(useForMath);
+
+function testUseForMath() {
+ for (let i = 0; i < 1e4; ++i) {
+ var value = useForMath(undefined, null, 5);
+ if (value != 8)
+ throw "Failed useForMath(undefined, null, 5), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, null);
+ if (value != 4)
+ throw "Failed useForMath(undefined, null, null), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, undefined);
+ if (value != 4)
+ throw "Failed useForMath(undefined, null, undefined), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, { foo: "bar" });
+ if (value != 8)
+ throw "Failed useForMath(undefined, null, { foo: \"bar\" }), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, true);
+ if (value != 8)
+ throw "Failed useForMath(undefined, null, true), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, [1, 2, 3]);
+ if (value != 8)
+ throw "Failed useForMath(undefined, null, true), value = " + value + " with i = " + i;
+
+ var value = useForMath(undefined, null, "WebKit!");
+ if (value != 8)
+ throw "Failed useForMath(undefined, null, true), value = " + value + " with i = " + i;
+ }
+}
+testUseForMath();
\ No newline at end of file
diff --git a/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-optimized-in-constant-folding.js b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-optimized-in-constant-folding.js
new file mode 100644
index 0000000..b714e0c
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined-optimized-in-constant-folding.js
@@ -0,0 +1,70 @@
+"use strict"
+
+function unreachableCodeTest() {
+ var a;
+
+ var b = null;
+ if (b) {
+ a = 5;
+ }
+ return a == b;
+}
+noInline(unreachableCodeTest);
+
+for (let i = 0; i < 1e4; ++i) {
+ if (!unreachableCodeTest())
+ throw "Failed unreachableCodeTest() with i = " + i;
+}
+
+
+function inlinedCompareToNull(a) {
+ return a == null;
+}
+
+function inlinedComparedToUndefined(a) {
+ return a == undefined;
+}
+
+// Warmup. Litter the profile with every types.
+function warmupInlineFunctions() {
+ let returnValue = 0;
+ for (let i = 0; i < 1e4; ++i) {
+ returnValue += inlinedCompareToNull("foo");
+ returnValue += inlinedCompareToNull(null);
+ returnValue += inlinedCompareToNull(Math);
+ returnValue += inlinedCompareToNull(5);
+ returnValue += inlinedCompareToNull(5.5);
+
+ returnValue += inlinedComparedToUndefined("foo");
+ returnValue += inlinedComparedToUndefined(null);
+ returnValue += inlinedComparedToUndefined(Math);
+ returnValue += inlinedComparedToUndefined(5);
+ returnValue += inlinedComparedToUndefined(5.5);
+ }
+ return returnValue;
+}
+noInline(warmupInlineFunctions);
+warmupInlineFunctions();
+
+function testInlineFunctions(undefinedArg, nullArg) {
+ if (inlinedCompareToNull("foo"))
+ throw "Failed inlinedCompareToNull(\"foo\")";
+
+ if (!inlinedCompareToNull(null))
+ throw "Failed !inlinedCompareToNull(null)";
+
+ if (!inlinedCompareToNull(undefined))
+ throw "Failed !inlinedCompareToNull(undefined)";
+
+ if (!inlinedCompareToNull(undefinedArg))
+ throw "Failed !inlinedCompareToNull(undefinedArg)";
+
+ if (!inlinedCompareToNull(nullArg))
+ throw "Failed !inlinedCompareToNull(nullArg)";
+
+}
+noInline(testInlineFunctions);
+
+for (let i = 0; i < 1e4; ++i) {
+ testInlineFunctions(undefined, null);
+}
\ No newline at end of file
diff --git a/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined.js b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined.js
new file mode 100644
index 0000000..e4e92c4
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/compare-eq-on-null-and-undefined.js
@@ -0,0 +1,174 @@
+"use strict"
+
+// Trivial cases: everything is monomorphic and super predictable.
+function compareConstants()
+{
+ return (null == null) && (null == undefined) && (undefined == null);
+}
+noInline(compareConstants);
+
+for (let i = 0; i < 1e4; ++i) {
+ if (!compareConstants())
+ throw "Failed to compareConstants().";
+}
+
+
+function opaqueNull() {
+ return null;
+}
+noInline(opaqueNull);
+
+function opaqueUndefined() {
+ return undefined;
+}
+noInline(opaqueUndefined);
+
+function compareConstantsAndDynamicValues()
+{
+ return ((null == opaqueNull())
+ && (opaqueNull() == null)
+ && (undefined == opaqueNull())
+ && (opaqueNull() == undefined)
+ && (null == opaqueUndefined())
+ && (opaqueUndefined() == null)
+ && (undefined == opaqueUndefined())
+ && (opaqueUndefined() == undefined));
+}
+noInline(compareConstantsAndDynamicValues);
+
+for (let i = 1e4; i--;) {
+ if (!compareConstantsAndDynamicValues())
+ throw "Failed compareConstantsAndDynamicValues()";
+}
+
+
+function compareDynamicValues()
+{
+ return ((opaqueNull() == opaqueNull())
+ && (opaqueUndefined() == opaqueUndefined())
+ && (opaqueNull() == opaqueUndefined())
+ && (opaqueUndefined() == opaqueNull()));
+}
+noInline(compareDynamicValues);
+
+for (let i = 0; i < 1e4; ++i) {
+ if (!compareDynamicValues())
+ throw "Failed compareDynamicValues()";
+}
+
+
+function compareDynamicValueToItself()
+{
+ const value1 = opaqueNull();
+ const value2 = opaqueUndefined();
+ return value1 == value1 && value2 == value2;
+}
+noInline(compareDynamicValueToItself);
+
+for (let i = 0; i < 1e4; ++i) {
+ if (!compareDynamicValueToItself())
+ throw "Failed compareDynamicValueToItself()";
+}
+
+
+// The case that interested us in the first place.
+// Accessing an array with undecided shape always return undefined.
+
+function arrayTesting()
+{
+ let returnValue = true;
+
+ const array1 = new Array(2);
+ for (let i = 0; i < 3; ++i) {
+ returnValue = returnValue && (array1[i] == null);
+ returnValue = returnValue && (null == array1[i]);
+ returnValue = returnValue && (array1[i] == undefined);
+ returnValue = returnValue && (undefined == array1[i]);
+ }
+
+ const array2 = new Array(2);
+ for (let i = 0; i < 2; ++i) {
+ returnValue = returnValue && (array2[i] == opaqueNull());
+ returnValue = returnValue && (opaqueNull() == array2[i]);
+ returnValue = returnValue && (array2[i] == opaqueUndefined());
+ returnValue = returnValue && (opaqueUndefined() == array2[i]);
+ }
+
+ const array3 = new Array(2);
+ for (let i = 0; i < 3; ++i) {
+ returnValue = returnValue && (array3[i] == array3[i]);
+ returnValue = returnValue && (array1[i] == array3[i]);
+ returnValue = returnValue && (array3[i] == array1[i]);
+ returnValue = returnValue && (array2[i] == array3[i]);
+ returnValue = returnValue && (array3[i] == array2[i]);
+
+ }
+
+ return returnValue;
+}
+noInline(arrayTesting);
+
+for (let i = 0; i < 1e4; ++i) {
+ if (!arrayTesting())
+ throw "Failed arrayTesting()";
+}
+
+
+// Let's make it polymorphic after optimization. We should fallback to a generic compare operation.
+
+function opaqueCompare1(a, b) {
+ return a == b;
+}
+noInline(opaqueCompare1);
+
+function testNullComparatorUpdate() {
+ for (let i = 0; i < 1e4; ++i) {
+ if (!opaqueCompare1(null, null))
+ throw "Failed opaqueCompare1(null, null)"
+ }
+
+ // Let's change types
+ for (let i = 0; i < 1e4; ++i) {
+ if (opaqueCompare1("foo", null))
+ throw "Failed opaqueCompare1(\"foo\", null)"
+ }
+}
+testNullComparatorUpdate();
+
+function opaqueCompare2(a, b) {
+ return a == b;
+}
+noInline(opaqueCompare2);
+
+function testUndefinedComparatorUpdate() {
+ for (let i = 0; i < 1e4; ++i) {
+ if (!opaqueCompare2(undefined, undefined))
+ throw "Failed opaqueCompare2(undefined, undefined)"
+ }
+
+ // Let's change types
+ for (let i = 0; i < 1e4; ++i) {
+ if (!opaqueCompare2("bar", "bar"))
+ throw "Failed opaqueCompare2(\"bar\", \"bar\")"
+ }
+}
+testUndefinedComparatorUpdate();
+
+function opaqueCompare3(a, b) {
+ return a == b;
+}
+noInline(opaqueCompare3);
+
+function testNullAndUndefinedComparatorUpdate() {
+ for (let i = 0; i < 1e4; ++i) {
+ if (!opaqueCompare3(undefined, null) || !opaqueCompare2(null, undefined))
+ throw "Failed opaqueCompare2(undefined/null, undefined/null)"
+ }
+
+ // Let's change types
+ for (let i = 0; i < 1e4; ++i) {
+ if (opaqueCompare3(undefined, "bar"))
+ throw "Failed opaqueCompare3(undefined, \"bar\")"
+ }
+}
+testNullAndUndefinedComparatorUpdate();