[JSC] ArithSub should not propagate "UsesAsOther"
https://bugs.webkit.org/show_bug.cgi?id=155932
Patch by Benjamin Poulain <bpoulain@apple.com> on 2016-03-28
Reviewed by Mark Lam.
The node ArithSub was backpropagating UsesAsOther.
This causes any GetByVal on a Double Array to have an extra
hole check if it flows into an ArithSub.
The definition of ArithSub (12.8.4.1) has both operands go
through ToNumber(). ToNumber() on "undefined" always produces
NaN. It is safe to ignore the NaN marker from hole when
the DAG flows into ArithSub.
This patch also adds this change and test coverage to ArithAdd.
ArithAdd was not a problem in practice because it is only
generated before Fixup if both operands are known to be numerical.
The change to ArithAdd is there to protect us of the ArithSub-like
problems if we ever improve our support of arithmetic operators.
* dfg/DFGBackwardsPropagationPhase.cpp:
(JSC::DFG::BackwardsPropagationPhase::propagate):
* tests/stress/arith-add-on-double-array-with-holes.js: Added.
(let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
(let.testCase.of.testCases.observableHoleOnLhs):
(let.testCase.of.testCases.nonObservableHoleOnRhs):
(let.testCase.of.testCases.observableHoleOnRhs):
* tests/stress/arith-sub-on-double-array-with-holes.js: Added.
(let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
(let.testCase.of.testCases.observableHoleOnLhs):
(let.testCase.of.testCases.nonObservableHoleOnRhs):
(let.testCase.of.testCases.observableHoleOnRhs):
* tests/stress/value-add-on-double-array-with-holes.js: Added.
(let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
(let.testCase.of.testCases.observableHoleOnLhs):
(let.testCase.of.testCases.nonObservableHoleOnRhs):
(let.testCase.of.testCases.observableHoleOnRhs):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@198770 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index db7d98d..e762655 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,43 @@
+2016-03-28 Benjamin Poulain <bpoulain@apple.com>
+
+ [JSC] ArithSub should not propagate "UsesAsOther"
+ https://bugs.webkit.org/show_bug.cgi?id=155932
+
+ Reviewed by Mark Lam.
+
+ The node ArithSub was backpropagating UsesAsOther.
+ This causes any GetByVal on a Double Array to have an extra
+ hole check if it flows into an ArithSub.
+
+ The definition of ArithSub (12.8.4.1) has both operands go
+ through ToNumber(). ToNumber() on "undefined" always produces
+ NaN. It is safe to ignore the NaN marker from hole when
+ the DAG flows into ArithSub.
+
+ This patch also adds this change and test coverage to ArithAdd.
+ ArithAdd was not a problem in practice because it is only
+ generated before Fixup if both operands are known to be numerical.
+ The change to ArithAdd is there to protect us of the ArithSub-like
+ problems if we ever improve our support of arithmetic operators.
+
+ * dfg/DFGBackwardsPropagationPhase.cpp:
+ (JSC::DFG::BackwardsPropagationPhase::propagate):
+ * tests/stress/arith-add-on-double-array-with-holes.js: Added.
+ (let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
+ (let.testCase.of.testCases.observableHoleOnLhs):
+ (let.testCase.of.testCases.nonObservableHoleOnRhs):
+ (let.testCase.of.testCases.observableHoleOnRhs):
+ * tests/stress/arith-sub-on-double-array-with-holes.js: Added.
+ (let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
+ (let.testCase.of.testCases.observableHoleOnLhs):
+ (let.testCase.of.testCases.nonObservableHoleOnRhs):
+ (let.testCase.of.testCases.observableHoleOnRhs):
+ * tests/stress/value-add-on-double-array-with-holes.js: Added.
+ (let.testCase.of.testCases.eval.nonObservableHoleOnLhs):
+ (let.testCase.of.testCases.observableHoleOnLhs):
+ (let.testCase.of.testCases.nonObservableHoleOnRhs):
+ (let.testCase.of.testCases.observableHoleOnRhs):
+
2016-03-28 Brian Burg <bburg@apple.com>
Web Inspector: protocol generator should generate C++ string-to-enum helper functions
diff --git a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
index 9e3acf1..b394729 100644
--- a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
@@ -248,6 +248,7 @@
}
case ArithAdd: {
+ flags &= ~NodeBytecodeUsesAsOther;
if (isNotNegZero(node->child1().node()) || isNotNegZero(node->child2().node()))
flags &= ~NodeBytecodeNeedsNegZero;
if (!isWithinPowerOfTwo<32>(node->child1()) && !isWithinPowerOfTwo<32>(node->child2()))
@@ -268,6 +269,7 @@
}
case ArithSub: {
+ flags &= ~NodeBytecodeUsesAsOther;
if (isNotNegZero(node->child1().node()) || isNotPosZero(node->child2().node()))
flags &= ~NodeBytecodeNeedsNegZero;
if (!isWithinPowerOfTwo<32>(node->child1()) && !isWithinPowerOfTwo<32>(node->child2()))
diff --git a/Source/JavaScriptCore/tests/stress/arith-add-on-double-array-with-holes.js b/Source/JavaScriptCore/tests/stress/arith-add-on-double-array-with-holes.js
new file mode 100644
index 0000000..f589ffe
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/arith-add-on-double-array-with-holes.js
@@ -0,0 +1,100 @@
+let testCases = [
+ // Numbers
+ ['1', NaN, NaN, 2, 2],
+ ['1.5', NaN, NaN, 1 + 1.5, 1 + 1.5],
+ [NaN, NaN, NaN, NaN, NaN],
+
+ // Strings.
+ ['""', NaN, NaN, 1, 1],
+ ['new String()', NaN, NaN, 1, 1],
+ ['"WebKit!"', NaN, NaN, NaN, NaN],
+
+ // Objects.
+ ['{ }', NaN, NaN, NaN, NaN],
+ ['{ foo: 1 }', NaN, NaN, NaN, NaN],
+ ['{ toString: function() { return ""; } }', NaN, NaN, 1, 1],
+ ['{ toString: function() { return "WebKit"; } }', NaN, NaN, NaN, NaN],
+
+ // Others.
+ ['null', NaN, NaN, 1, 1],
+ ['undefined', NaN, NaN, NaN, NaN]
+];
+
+for (let testCase of testCases) {
+ let otherOperand = testCase[0];
+ let expectedLeftValueWithHole = testCase[1];
+ let expectedRightValueWithHole = testCase[2];
+ let expectedLeftValue = testCase[3];
+ let expectedRightValue = testCase[4];
+ eval(
+ `// Those holes are not observable by arithmetic operation.
+ // The return value is always going to be NaN.
+ function nonObservableHoleOnLhs(array, otherValue) {
+ return Math.min(array[0]) + Math.min(otherValue);
+ }
+ noInline(nonObservableHoleOnLhs);
+
+ function observableHoleOnLhs(array, otherValue) {
+ let value = array[0];
+ return [Math.min(value) + Math.min(otherValue), value];
+ }
+ noInline(observableHoleOnLhs);
+
+ function nonObservableHoleOnRhs(array, otherValue) {
+ return Math.min(otherValue) + Math.min(array[0]);
+ }
+ noInline(nonObservableHoleOnRhs);
+
+ function observableHoleOnRhs(array, otherValue) {
+ let value = array[0];
+ return [Math.min(otherValue) + Math.min(value), value];
+ }
+ noInline(observableHoleOnRhs);
+
+ let testArray = new Array;
+ for (let i = 1; i < 3; ++i) {
+ testArray[i] = i + 0.5
+ }
+
+ let isEqual = function(a, b) {
+ if (a === a) {
+ return a === b;
+ }
+ return b !== b;
+ }
+
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult1, ${expectedLeftValueWithHole}))
+ throw "Error on nonObservableHoleOnLhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedLeftValueWithHole} + " got " + lhsResult1;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult2[0], ${expectedLeftValueWithHole}) || lhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnLhs at i = " + i;
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult1, ${expectedRightValueWithHole}))
+ throw "Error on nonObservableHoleOnRhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedRightValueWithHole} + " got " + rhsResult1;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult2[0], ${expectedRightValueWithHole}) || rhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnRhs at i = " + i;
+ }
+
+ // Fill the hole, make sure everything still work correctly.
+ testArray[0] = 1.;
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult1, ${expectedLeftValue}))
+ throw "Error on non hole nonObservableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult1;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult2[0], ${expectedLeftValue}) || lhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult2[0];
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult1, ${expectedRightValue}))
+ throw "Error on non hole nonObservableHoleOnRhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedRightValue} + " got " + rhsResult1;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult2[0], ${expectedRightValue}) || rhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnRhs at i = " + i;
+ }`
+ );
+}
\ No newline at end of file
diff --git a/Source/JavaScriptCore/tests/stress/arith-sub-on-double-array-with-holes.js b/Source/JavaScriptCore/tests/stress/arith-sub-on-double-array-with-holes.js
new file mode 100644
index 0000000..855c78c
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/arith-sub-on-double-array-with-holes.js
@@ -0,0 +1,98 @@
+let testCases = [
+ // Numbers
+ ['1', 0, 0],
+ ['1.5', 1 - 1.5, 1.5 - 1],
+ [NaN, NaN, NaN],
+
+ // Strings.
+ ['""', 1, -1],
+ ['new String()', 1, -1],
+ ['"WebKit!"', NaN, NaN],
+
+ // Objects.
+ ['{ }', NaN, NaN],
+ ['{ foo: 1 }', NaN, NaN],
+ ['{ toString: function() { return ""; } }', 1, -1],
+ ['{ toString: function() { return "WebKit"; } }', NaN, NaN],
+
+ // Others.
+ ['null', 1, -1],
+ ['undefined', NaN, NaN]
+];
+
+for (let testCase of testCases) {
+ let otherOperand = testCase[0];
+ let expectedLeftValue = testCase[1];
+ let expectedRightValue = testCase[2];
+ eval(
+ `// Those holes are not observable by arithmetic operation.
+ // The return value is always going to be NaN.
+ function nonObservableHoleOnLhs(array, otherValue) {
+ return array[0] - otherValue;
+ }
+ noInline(nonObservableHoleOnLhs);
+
+ function observableHoleOnLhs(array, otherValue) {
+ let value = array[0];
+ return [value - otherValue, value];
+ }
+ noInline(observableHoleOnLhs);
+
+ function nonObservableHoleOnRhs(array, otherValue) {
+ return otherValue - array[0];
+ }
+ noInline(nonObservableHoleOnRhs);
+
+ function observableHoleOnRhs(array, otherValue) {
+ let value = array[0];
+ return [otherValue - value, value];
+ }
+ noInline(observableHoleOnRhs);
+
+ let testArray = new Array;
+ for (let i = 1; i < 3; ++i) {
+ testArray[i] = i + 0.5
+ }
+
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (lhsResult1 == lhsResult1)
+ throw "Error on nonObservableHoleOnLhs at i = " + i;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (lhsResult2[0] == lhsResult2[0] || lhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnLhs at i = " + i;
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (rhsResult1 == rhsResult1)
+ throw "Error on nonObservableHoleOnRhs at i = " + i;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (rhsResult2[0] == rhsResult2[0] || rhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnRhs at i = " + i;
+ }
+
+ let isEqual = function(a, b) {
+ if (a === a) {
+ return a === b;
+ }
+ return b !== b;
+ }
+
+ // Fill the hole, make sure everything still work correctly.
+ testArray[0] = 1.;
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult1, ${expectedLeftValue}))
+ throw "Error on non hole nonObservableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult1;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult2[0], ${expectedLeftValue}) || lhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult2[0];
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult1, ${expectedRightValue}))
+ throw "Error on non hole nonObservableHoleOnRhs at i = " + i + " expected " + ${expectedRightValue} + " got " + rhsResult1;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult2[0], ${expectedRightValue}) || rhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnRhs at i = " + i;
+ }`
+ );
+}
\ No newline at end of file
diff --git a/Source/JavaScriptCore/tests/stress/value-add-on-double-array-with-holes.js b/Source/JavaScriptCore/tests/stress/value-add-on-double-array-with-holes.js
new file mode 100644
index 0000000..367e8f7
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/value-add-on-double-array-with-holes.js
@@ -0,0 +1,100 @@
+let testCases = [
+ // Numbers
+ ['1', NaN, NaN, 2, 2],
+ ['1.5', NaN, NaN, 1 + 1.5, 1 + 1.5],
+ [NaN, NaN, NaN, NaN, NaN],
+
+ // Strings.
+ ['""', '"undefined"', '"undefined"', '"1"', '"1"'],
+ ['new String()', '"undefined"', '"undefined"', '"1"', '"1"'],
+ ['"WebKit!"', '"undefinedWebKit!"', '"WebKit!undefined"', '"1WebKit!"', '"WebKit!1"'],
+
+ // Objects.
+ ['{ }', '"undefined[object Object]"', '"[object Object]undefined"', '"1[object Object]"', '"[object Object]1"'],
+ ['{ foo: 1 }', '"undefined[object Object]"', '"[object Object]undefined"', '"1[object Object]"', '"[object Object]1"'],
+ ['{ toString: function() { return ""; } }', '"undefined"', '"undefined"', '"1"', '"1"'],
+ ['{ toString: function() { return "WebKit"; } }', '"undefinedWebKit"', '"WebKitundefined"', '"1WebKit"', '"WebKit1"'],
+
+ // Others.
+ ['null', NaN, NaN, 1, 1],
+ ['undefined', NaN, NaN, NaN, NaN]
+];
+
+for (let testCase of testCases) {
+ let otherOperand = testCase[0];
+ let expectedLeftValueWithHole = testCase[1];
+ let expectedRightValueWithHole = testCase[2];
+ let expectedLeftValue = testCase[3];
+ let expectedRightValue = testCase[4];
+ eval(
+ `// Those holes are not observable by arithmetic operation.
+ // The return value is always going to be NaN.
+ function nonObservableHoleOnLhs(array, otherValue) {
+ return array[0] + otherValue;
+ }
+ noInline(nonObservableHoleOnLhs);
+
+ function observableHoleOnLhs(array, otherValue) {
+ let value = array[0];
+ return [value + otherValue, value];
+ }
+ noInline(observableHoleOnLhs);
+
+ function nonObservableHoleOnRhs(array, otherValue) {
+ return otherValue + array[0];
+ }
+ noInline(nonObservableHoleOnRhs);
+
+ function observableHoleOnRhs(array, otherValue) {
+ let value = array[0];
+ return [otherValue + value, value];
+ }
+ noInline(observableHoleOnRhs);
+
+ let testArray = new Array;
+ for (let i = 1; i < 3; ++i) {
+ testArray[i] = i + 0.5
+ }
+
+ let isEqual = function(a, b) {
+ if (a === a) {
+ return a === b;
+ }
+ return b !== b;
+ }
+
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult1, ${expectedLeftValueWithHole}))
+ throw "Error on nonObservableHoleOnLhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedLeftValueWithHole} + " got " + lhsResult1;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult2[0], ${expectedLeftValueWithHole}) || lhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnLhs at i = " + i;
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult1, ${expectedRightValueWithHole}))
+ throw "Error on nonObservableHoleOnRhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedRightValueWithHole} + " got " + rhsResult1;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult2[0], ${expectedRightValueWithHole}) || rhsResult2[1] !== undefined)
+ throw "Error on observableHoleOnRhs at i = " + i;
+ }
+
+ // Fill the hole, make sure everything still work correctly.
+ testArray[0] = 1.;
+ for (let i = 0; i < 1e4; ++i) {
+ let lhsResult1 = nonObservableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult1, ${expectedLeftValue}))
+ throw "Error on non hole nonObservableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult1;
+ let lhsResult2 = observableHoleOnLhs(testArray, ${otherOperand});
+ if (!isEqual(lhsResult2[0], ${expectedLeftValue}) || lhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnLhs at i = " + i + " expected " + ${expectedLeftValue} + " got " + lhsResult2[0];
+
+ let rhsResult1 = nonObservableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult1, ${expectedRightValue}))
+ throw "Error on non hole nonObservableHoleOnRhs at i = " + i + " with operand " + ${otherOperand} + " expected " + ${expectedRightValue} + " got " + rhsResult1;
+ let rhsResult2 = observableHoleOnRhs(testArray, ${otherOperand});
+ if (!isEqual(rhsResult2[0], ${expectedRightValue}) || rhsResult2[1] !== 1)
+ throw "Error on non hole observableHoleOnRhs at i = " + i;
+ }`
+ );
+}
\ No newline at end of file