[JSC] Make the rounding-related nodes support any type
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Sep 2016 21:24:28 +0000 (21:24 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Sep 2016 21:24:28 +0000 (21:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=161895

Patch by Benjamin Poulain <bpoulain@apple.com> on 2016-09-14
Reviewed by Geoffrey Garen.

JSTests:

* stress/arith-ceil-on-various-types.js: Added.
* stress/arith-floor-on-various-types.js: Added.
* stress/arith-round-on-various-types.js: Added.
* stress/arith-trunc-on-various-types.js: Added.

Source/JavaScriptCore:

This patch changes ArithRound, ArithFloor, ArithCeil and ArithTrunc
to support polymorphic input without exiting on entry.

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
The 4 functions ignore any input past the first argument. It is okay
to use the nodes with the first argument and let the Phantoms keep
the remaining arguments live.

* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
Our fixup had the issue we have seen on previous nodes: unaryArithShouldSpeculateInt32()
prevents us from picking a good type if we do not see any double.

* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
Prediction propagation of those nodes are fully determined
from their flags and results's prediction. They are moved
to the invariant processing.

* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileArithRounding):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileArithRound):
(JSC::FTL::DFG::LowerDFGToB3::compileArithFloor):
(JSC::FTL::DFG::LowerDFGToB3::compileArithCeil):
(JSC::FTL::DFG::LowerDFGToB3::compileArithTrunc):

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

16 files changed:
JSTests/ChangeLog
JSTests/stress/arith-ceil-on-various-types.js [new file with mode: 0644]
JSTests/stress/arith-floor-on-various-types.js [new file with mode: 0644]
JSTests/stress/arith-round-on-various-types.js [new file with mode: 0644]
JSTests/stress/arith-trunc-on-various-types.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

index dc32b02..15011de 100644 (file)
@@ -1,3 +1,15 @@
+2016-09-14  Benjamin Poulain  <bpoulain@apple.com>
+
+        [JSC] Make the rounding-related nodes support any type
+        https://bugs.webkit.org/show_bug.cgi?id=161895
+
+        Reviewed by Geoffrey Garen.
+
+        * stress/arith-ceil-on-various-types.js: Added.
+        * stress/arith-floor-on-various-types.js: Added.
+        * stress/arith-round-on-various-types.js: Added.
+        * stress/arith-trunc-on-various-types.js: Added.
+
 2016-09-14  Joseph Pecoraro  <pecoraro@apple.com>
 
         TaggedTemplateString function calls should emit tail position calls
diff --git a/JSTests/stress/arith-ceil-on-various-types.js b/JSTests/stress/arith-ceil-on-various-types.js
new file mode 100644 (file)
index 0000000..95dd27d
--- /dev/null
@@ -0,0 +1,295 @@
+"use strict";
+
+let validInputTestCases = [
+    // input as string, expected result as string.
+    ["undefined", "NaN"],
+    ["null", "0"],
+    ["0", "0"],
+    ["-0.", "-0"],
+    ["0.5", "1"],
+    ["-0.5", "-0"],
+    ["4", "4"],
+    ["42.1", "43"],
+    ["42.5", "43"],
+    ["42.9", "43"],
+    ["-42.1", "-42"],
+    ["-42.5", "-42"],
+    ["-42.9", "-42"],
+    ["Math.PI", "4"],
+    ["Infinity", "Infinity"],
+    ["-Infinity", "-Infinity"],
+    ["NaN", "NaN"],
+    ["\"WebKit\"", "NaN"],
+    ["\"4\"", "4"],
+    ["\"42.5\"", "43"],
+    ["{ valueOf: () => { return 4; } }", "4"],
+    ["{ valueOf: () => { return 0; } }", "0"],
+    ["{ valueOf: () => { return -0; } }", "-0"],
+    ["{ valueOf: () => { return 0.5; } }", "1"],
+    ["{ valueOf: () => { return -0.5; } }", "-0"],
+    ["{ valueOf: () => { return Number.MIN_SAFE_INTEGER; } }", "-9007199254740991"],
+    ["{ valueOf: () => { return Number.MAX_SAFE_INTEGER; } }", "9007199254740991"],
+    ["{ valueOf: () => { return 0x80000000|0; } }", "-2147483648"],
+    ["{ valueOf: () => { return 0x7fffffff|0; } }", "2147483647"],
+    ["{ valueOf: () => { return (0x80000000|0) - 0.5; } }", "-2147483648"],
+    ["{ valueOf: () => { return (0x7fffffff|0) + 0.5; } }", "2147483648"],
+];
+
+let validInputTypedTestCases = validInputTestCases.map((element) => { return [eval("(" + element[0] + ")"), eval(element[1])] });
+
+function isIdentical(result, expected)
+{
+    if (expected === expected) {
+        if (result !== expected)
+            return false;
+        if (!expected && (1 / expected) !== (1 / result))
+            return false;
+
+        return true;
+    }
+    return result !== result;
+}
+
+
+// Test Math.ceil() without arguments.
+function opaqueCeilNoArgument() {
+    return Math.ceil();
+}
+noInline(opaqueCeilNoArgument);
+noOSRExitFuzzing(opaqueCeilNoArgument);
+
+function testNoArgument() {
+    for (let i = 0; i < 1e4; ++i) {
+        let output = opaqueCeilNoArgument();
+        if (!isIdentical(output, NaN)) {
+            throw "Failed opaqueCeilNoArgument";
+        }
+    }
+    if (numberOfDFGCompiles(opaqueCeilNoArgument) > 1)
+        throw "The call without arguments should never exit.";
+}
+testNoArgument();
+
+
+// Test Math.ceil() with a very polymorphic input. All test cases are seen at each iteration.
+function opaqueAllTypesCeil(argument) {
+    return Math.ceil(argument);
+}
+noInline(opaqueAllTypesCeil);
+noOSRExitFuzzing(opaqueAllTypesCeil);
+
+function testAllTypesCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesCeil(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1]))
+                throw "Failed testAllTypesCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesCeil) > 3)
+        throw "We should have detected ceil() was polymorphic and generated a generic version.";
+}
+testAllTypesCall();
+
+
+// Polymorphic input but negative zero is not observable.
+function opaqueAllTypesCeilWithoutNegativeZero(argument) {
+    return Math.ceil(argument) + 0;
+}
+noInline(opaqueAllTypesCeilWithoutNegativeZero);
+noOSRExitFuzzing(opaqueAllTypesCeilWithoutNegativeZero);
+
+function testAllTypesWithoutNegativeZeroCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesCeilWithoutNegativeZero(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1] + 0))
+                throw "Failed testAllTypesWithoutNegativeZeroCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesCeil) > 3)
+        throw "We should have detected ceil() was polymorphic and generated a generic version.";
+}
+testAllTypesWithoutNegativeZeroCall();
+
+
+// Test Math.ceil() on a completely typed input. Every call see only one type.
+function testSingleTypeCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueCeil(argument) {
+                return Math.ceil(argument);
+            }
+            noInline(opaqueCeil);
+            noOSRExitFuzzing(opaqueCeil);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueCeil(${testCaseInput[0]}), ${testCaseInput[1]})) {
+                    throw "Failed testSingleTypeCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueCeil) > 1)
+                throw "We should have compiled a single ceil for the expected type.";
+        `);
+    }
+}
+testSingleTypeCall();
+
+
+// Test Math.ceil() on a completely typed input, but without negative zero.
+function testSingleTypeWithoutNegativeZeroCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueCeil(argument) {
+                return Math.ceil(argument) + 0;
+            }
+            noInline(opaqueCeil);
+            noOSRExitFuzzing(opaqueCeil);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueCeil(${testCaseInput[0]}), ${testCaseInput[1]} + 0)) {
+                    throw "Failed testSingleTypeWithoutNegativeZeroCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueCeil) > 1)
+                throw "We should have compiled a single ceil for the expected type.";
+        `);
+    }
+}
+testSingleTypeWithoutNegativeZeroCall();
+
+
+// Test Math.ceil() on constants
+function testConstant() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueCeilOnConstant() {
+                return Math.ceil(${testCaseInput[0]});
+            }
+            noInline(opaqueCeilOnConstant);
+            noOSRExitFuzzing(opaqueCeilOnConstant);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueCeilOnConstant(), ${testCaseInput[1]})) {
+                    throw "Failed testConstant()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueCeilOnConstant) > 1)
+                throw "We should have compiled a single ceil for the expected type.";
+        `);
+    }
+}
+testConstant();
+
+
+// Verify we call valueOf() exactly once per call.
+function opaqueCeilForSideEffects(argument) {
+    return Math.ceil(argument);
+}
+noInline(opaqueCeilForSideEffects);
+noOSRExitFuzzing(opaqueCeilForSideEffects);
+
+function testSideEffect() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let ceil16 = Math.ceil(16);
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueCeilForSideEffects(testObject) !== ceil16)
+            throw "Incorrect result in testSideEffect()";
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testSideEffect()";
+    if (numberOfDFGCompiles(opaqueCeilForSideEffects) > 1)
+        throw "opaqueCeilForSideEffects() is predictable, it should only be compiled once.";
+}
+testSideEffect();
+
+
+// Verify ceil() is not subject to CSE if the argument has side effects.
+function opaqueCeilForCSE(argument) {
+    return Math.ceil(argument) + Math.ceil(argument) + Math.ceil(argument);
+}
+noInline(opaqueCeilForCSE);
+noOSRExitFuzzing(opaqueCeilForCSE);
+
+function testCSE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let ceil16 = Math.ceil(16);
+    let threeCeil16 = ceil16 + ceil16 + ceil16;
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueCeilForCSE(testObject) !== threeCeil16)
+            throw "Incorrect result in testCSE()";
+    }
+    if (testObject.counter !== 3e4)
+        throw "Failed testCSE()";
+    if (numberOfDFGCompiles(opaqueCeilForCSE) > 1)
+        throw "opaqueCeilForCSE() is predictable, it should only be compiled once.";
+}
+testCSE();
+
+
+// Verify ceil() is not subject to DCE if the argument has side effects.
+function opaqueCeilForDCE(argument) {
+    Math.ceil(argument);
+}
+noInline(opaqueCeilForDCE);
+noOSRExitFuzzing(opaqueCeilForDCE);
+
+function testDCE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    for (let i = 0; i < 1e4; ++i) {
+        opaqueCeilForDCE(testObject);
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testDCE()";
+    if (numberOfDFGCompiles(opaqueCeilForDCE) > 1)
+        throw "opaqueCeilForDCE() is predictable, it should only be compiled once.";
+}
+testDCE();
+
+
+// Test exceptions in the argument.
+function testException() {
+    let counter = 0;
+    function opaqueCeilWithException(argument) {
+        let result = Math.ceil(argument);
+        ++counter;
+        return result;
+    }
+    noInline(opaqueCeilWithException);
+
+    let testObject = { valueOf: () => {  return 64; } };
+    let ceil64 = Math.ceil(64);
+
+    // Warm up without exception.
+    for (let i = 0; i < 1e3; ++i) {
+        if (opaqueCeilWithException(testObject) !== ceil64)
+            throw "Incorrect result in opaqueCeilWithException()";
+    }
+
+    let testThrowObject = { valueOf: () => { throw testObject; return 64; } };
+
+    for (let i = 0; i < 1e2; ++i) {
+        try {
+            if (opaqueCeilWithException(testThrowObject) !== 8)
+                throw "This code should not be reached!!";
+        } catch (e) {
+            if (e !== testObject) {
+                throw "Wrong object thrown from opaqueCeilWithException."
+            }
+        }
+    }
+
+    if (counter !== 1e3) {
+        throw "Invalid count in testException()";
+    }
+}
+testException();
diff --git a/JSTests/stress/arith-floor-on-various-types.js b/JSTests/stress/arith-floor-on-various-types.js
new file mode 100644 (file)
index 0000000..b2f360c
--- /dev/null
@@ -0,0 +1,295 @@
+"use strict";
+
+let validInputTestCases = [
+    // input as string, expected result as string.
+    ["undefined", "NaN"],
+    ["null", "0"],
+    ["0", "0"],
+    ["-0.", "-0"],
+    ["0.5", "0"],
+    ["-0.5", "-1"],
+    ["4", "4"],
+    ["42.1", "42"],
+    ["42.5", "42"],
+    ["42.9", "42"],
+    ["-42.1", "-43"],
+    ["-42.5", "-43"],
+    ["-42.9", "-43"],
+    ["Math.PI", "3"],
+    ["Infinity", "Infinity"],
+    ["-Infinity", "-Infinity"],
+    ["NaN", "NaN"],
+    ["\"WebKit\"", "NaN"],
+    ["\"4\"", "4"],
+    ["\"42.5\"", "42"],
+    ["{ valueOf: () => { return 4; } }", "4"],
+    ["{ valueOf: () => { return 0; } }", "0"],
+    ["{ valueOf: () => { return -0; } }", "-0"],
+    ["{ valueOf: () => { return 0.5; } }", "0"],
+    ["{ valueOf: () => { return -0.5; } }", "-1"],
+    ["{ valueOf: () => { return Number.MIN_SAFE_INTEGER; } }", "-9007199254740991"],
+    ["{ valueOf: () => { return Number.MAX_SAFE_INTEGER; } }", "9007199254740991"],
+    ["{ valueOf: () => { return 0x80000000|0; } }", "-2147483648"],
+    ["{ valueOf: () => { return 0x7fffffff|0; } }", "2147483647"],
+    ["{ valueOf: () => { return (0x80000000|0) - 0.5; } }", "-2147483649"],
+    ["{ valueOf: () => { return (0x7fffffff|0) + 0.5; } }", "2147483647"],
+];
+
+let validInputTypedTestCases = validInputTestCases.map((element) => { return [eval("(" + element[0] + ")"), eval(element[1])] });
+
+function isIdentical(result, expected)
+{
+    if (expected === expected) {
+        if (result !== expected)
+            return false;
+        if (!expected && (1 / expected) !== (1 / result))
+            return false;
+
+        return true;
+    }
+    return result !== result;
+}
+
+
+// Test Math.floor() without arguments.
+function opaqueFloorNoArgument() {
+    return Math.floor();
+}
+noInline(opaqueFloorNoArgument);
+noOSRExitFuzzing(opaqueFloorNoArgument);
+
+function testNoArgument() {
+    for (let i = 0; i < 1e4; ++i) {
+        let output = opaqueFloorNoArgument();
+        if (!isIdentical(output, NaN)) {
+            throw "Failed opaqueFloorNoArgument";
+        }
+    }
+    if (numberOfDFGCompiles(opaqueFloorNoArgument) > 1)
+        throw "The call without arguments should never exit.";
+}
+testNoArgument();
+
+
+// Test Math.floor() with a very polymorphic input. All test cases are seen at each iteration.
+function opaqueAllTypesFloor(argument) {
+    return Math.floor(argument);
+}
+noInline(opaqueAllTypesFloor);
+noOSRExitFuzzing(opaqueAllTypesFloor);
+
+function testAllTypesCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesFloor(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1]))
+                throw "Failed testAllTypesCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesFloor) > 3)
+        throw "We should have detected floor() was polymorphic and generated a generic version.";
+}
+testAllTypesCall();
+
+
+// Polymorphic input but negative zero is not observable.
+function opaqueAllTypesFloorWithoutNegativeZero(argument) {
+    return Math.floor(argument) + 0;
+}
+noInline(opaqueAllTypesFloorWithoutNegativeZero);
+noOSRExitFuzzing(opaqueAllTypesFloorWithoutNegativeZero);
+
+function testAllTypesWithoutNegativeZeroCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesFloorWithoutNegativeZero(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1] + 0))
+                throw "Failed testAllTypesWithoutNegativeZeroCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesFloor) > 3)
+        throw "We should have detected floor() was polymorphic and generated a generic version.";
+}
+testAllTypesWithoutNegativeZeroCall();
+
+
+// Test Math.floor() on a completely typed input. Every call see only one type.
+function testSingleTypeCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueFloor(argument) {
+                return Math.floor(argument);
+            }
+            noInline(opaqueFloor);
+            noOSRExitFuzzing(opaqueFloor);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueFloor(${testCaseInput[0]}), ${testCaseInput[1]})) {
+                    throw "Failed testSingleTypeCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueFloor) > 1)
+                throw "We should have compiled a single floor for the expected type.";
+        `);
+    }
+}
+testSingleTypeCall();
+
+
+// Test Math.floor() on a completely typed input, but without negative zero.
+function testSingleTypeWithoutNegativeZeroCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueFloor(argument) {
+                return Math.floor(argument) + 0;
+            }
+            noInline(opaqueFloor);
+            noOSRExitFuzzing(opaqueFloor);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueFloor(${testCaseInput[0]}), ${testCaseInput[1]} + 0)) {
+                    throw "Failed testSingleTypeWithoutNegativeZeroCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueFloor) > 1)
+                throw "We should have compiled a single floor for the expected type.";
+        `);
+    }
+}
+testSingleTypeWithoutNegativeZeroCall();
+
+
+// Test Math.floor() on constants
+function testConstant() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueFloorOnConstant() {
+                return Math.floor(${testCaseInput[0]});
+            }
+            noInline(opaqueFloorOnConstant);
+            noOSRExitFuzzing(opaqueFloorOnConstant);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueFloorOnConstant(), ${testCaseInput[1]})) {
+                    throw "Failed testConstant()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueFloorOnConstant) > 1)
+                throw "We should have compiled a single floor for the expected type.";
+        `);
+    }
+}
+testConstant();
+
+
+// Verify we call valueOf() exactly once per call.
+function opaqueFloorForSideEffects(argument) {
+    return Math.floor(argument);
+}
+noInline(opaqueFloorForSideEffects);
+noOSRExitFuzzing(opaqueFloorForSideEffects);
+
+function testSideEffect() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let floor16 = Math.floor(16);
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueFloorForSideEffects(testObject) !== floor16)
+            throw "Incorrect result in testSideEffect()";
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testSideEffect()";
+    if (numberOfDFGCompiles(opaqueFloorForSideEffects) > 1)
+        throw "opaqueFloorForSideEffects() is predictable, it should only be compiled once.";
+}
+testSideEffect();
+
+
+// Verify floor() is not subject to CSE if the argument has side effects.
+function opaqueFloorForCSE(argument) {
+    return Math.floor(argument) + Math.floor(argument) + Math.floor(argument);
+}
+noInline(opaqueFloorForCSE);
+noOSRExitFuzzing(opaqueFloorForCSE);
+
+function testCSE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let floor16 = Math.floor(16);
+    let threeFloor16 = floor16 + floor16 + floor16;
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueFloorForCSE(testObject) !== threeFloor16)
+            throw "Incorrect result in testCSE()";
+    }
+    if (testObject.counter !== 3e4)
+        throw "Failed testCSE()";
+    if (numberOfDFGCompiles(opaqueFloorForCSE) > 1)
+        throw "opaqueFloorForCSE() is predictable, it should only be compiled once.";
+}
+testCSE();
+
+
+// Verify floor() is not subject to DCE if the argument has side effects.
+function opaqueFloorForDCE(argument) {
+    Math.floor(argument);
+}
+noInline(opaqueFloorForDCE);
+noOSRExitFuzzing(opaqueFloorForDCE);
+
+function testDCE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    for (let i = 0; i < 1e4; ++i) {
+        opaqueFloorForDCE(testObject);
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testDCE()";
+    if (numberOfDFGCompiles(opaqueFloorForDCE) > 1)
+        throw "opaqueFloorForDCE() is predictable, it should only be compiled once.";
+}
+testDCE();
+
+
+// Test exceptions in the argument.
+function testException() {
+    let counter = 0;
+    function opaqueFloorWithException(argument) {
+        let result = Math.floor(argument);
+        ++counter;
+        return result;
+    }
+    noInline(opaqueFloorWithException);
+
+    let testObject = { valueOf: () => {  return 64; } };
+    let floor64 = Math.floor(64);
+
+    // Warm up without exception.
+    for (let i = 0; i < 1e3; ++i) {
+        if (opaqueFloorWithException(testObject) !== floor64)
+            throw "Incorrect result in opaqueFloorWithException()";
+    }
+
+    let testThrowObject = { valueOf: () => { throw testObject; return 64; } };
+
+    for (let i = 0; i < 1e2; ++i) {
+        try {
+            if (opaqueFloorWithException(testThrowObject) !== 8)
+                throw "This code should not be reached!!";
+        } catch (e) {
+            if (e !== testObject) {
+                throw "Wrong object thrown from opaqueFloorWithException."
+            }
+        }
+    }
+
+    if (counter !== 1e3) {
+        throw "Invalid count in testException()";
+    }
+}
+testException();
diff --git a/JSTests/stress/arith-round-on-various-types.js b/JSTests/stress/arith-round-on-various-types.js
new file mode 100644 (file)
index 0000000..371921b
--- /dev/null
@@ -0,0 +1,295 @@
+"use strict";
+
+let validInputTestCases = [
+    // input as string, expected result as string.
+    ["undefined", "NaN"],
+    ["null", "0"],
+    ["0", "0"],
+    ["-0.", "-0"],
+    ["0.5", "1"],
+    ["-0.5", "-0"],
+    ["4", "4"],
+    ["42.1", "42"],
+    ["42.5", "43"],
+    ["42.9", "43"],
+    ["-42.1", "-42"],
+    ["-42.5", "-42"],
+    ["-42.9", "-43"],
+    ["Math.PI", "3"],
+    ["Infinity", "Infinity"],
+    ["-Infinity", "-Infinity"],
+    ["NaN", "NaN"],
+    ["\"WebKit\"", "NaN"],
+    ["\"4\"", "4"],
+    ["\"42.5\"", "43"],
+    ["{ valueOf: () => { return 4; } }", "4"],
+    ["{ valueOf: () => { return 0; } }", "0"],
+    ["{ valueOf: () => { return -0; } }", "-0"],
+    ["{ valueOf: () => { return 0.5; } }", "1"],
+    ["{ valueOf: () => { return -0.5; } }", "-0"],
+    ["{ valueOf: () => { return Number.MIN_SAFE_INTEGER; } }", "-9007199254740991"],
+    ["{ valueOf: () => { return Number.MAX_SAFE_INTEGER; } }", "9007199254740991"],
+    ["{ valueOf: () => { return 0x80000000|0; } }", "-2147483648"],
+    ["{ valueOf: () => { return 0x7fffffff|0; } }", "2147483647"],
+    ["{ valueOf: () => { return (0x80000000|0) - 0.5; } }", "-2147483648"],
+    ["{ valueOf: () => { return (0x7fffffff|0) + 0.5; } }", "2147483648"],
+];
+
+let validInputTypedTestCases = validInputTestCases.map((element) => { return [eval("(" + element[0] + ")"), eval(element[1])] });
+
+function isIdentical(result, expected)
+{
+    if (expected === expected) {
+        if (result !== expected)
+            return false;
+        if (!expected && (1 / expected) !== (1 / result))
+            return false;
+
+        return true;
+    }
+    return result !== result;
+}
+
+
+// Test Math.round() without arguments.
+function opaqueRoundNoArgument() {
+    return Math.round();
+}
+noInline(opaqueRoundNoArgument);
+noOSRExitFuzzing(opaqueRoundNoArgument);
+
+function testNoArgument() {
+    for (let i = 0; i < 1e4; ++i) {
+        let output = opaqueRoundNoArgument();
+        if (!isIdentical(output, NaN)) {
+            throw "Failed opaqueRoundNoArgument";
+        }
+    }
+    if (numberOfDFGCompiles(opaqueRoundNoArgument) > 1)
+        throw "The call without arguments should never exit.";
+}
+testNoArgument();
+
+
+// Test Math.round() with a very polymorphic input. All test cases are seen at each iteration.
+function opaqueAllTypesRound(argument) {
+    return Math.round(argument);
+}
+noInline(opaqueAllTypesRound);
+noOSRExitFuzzing(opaqueAllTypesRound);
+
+function testAllTypesCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesRound(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1]))
+                throw "Failed testAllTypesCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesRound) > 3)
+        throw "We should have detected round() was polymorphic and generated a generic version.";
+}
+testAllTypesCall();
+
+
+// Polymorphic input but negative zero is not observable.
+function opaqueAllTypesRoundWithoutNegativeZero(argument) {
+    return Math.round(argument) + 0;
+}
+noInline(opaqueAllTypesRoundWithoutNegativeZero);
+noOSRExitFuzzing(opaqueAllTypesRoundWithoutNegativeZero);
+
+function testAllTypesWithoutNegativeZeroCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesRoundWithoutNegativeZero(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1] + 0))
+                throw "Failed testAllTypesWithoutNegativeZeroCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesRound) > 3)
+        throw "We should have detected round() was polymorphic and generated a generic version.";
+}
+testAllTypesWithoutNegativeZeroCall();
+
+
+// Test Math.round() on a completely typed input. Every call see only one type.
+function testSingleTypeCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueRound(argument) {
+                return Math.round(argument);
+            }
+            noInline(opaqueRound);
+            noOSRExitFuzzing(opaqueRound);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueRound(${testCaseInput[0]}), ${testCaseInput[1]})) {
+                    throw "Failed testSingleTypeCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueRound) > 1)
+                throw "We should have compiled a single round for the expected type.";
+        `);
+    }
+}
+testSingleTypeCall();
+
+
+// Test Math.round() on a completely typed input, but without negative zero.
+function testSingleTypeWithoutNegativeZeroCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueRound(argument) {
+                return Math.round(argument) + 0;
+            }
+            noInline(opaqueRound);
+            noOSRExitFuzzing(opaqueRound);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueRound(${testCaseInput[0]}), ${testCaseInput[1]} + 0)) {
+                    throw "Failed testSingleTypeWithoutNegativeZeroCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueRound) > 1)
+                throw "We should have compiled a single round for the expected type.";
+        `);
+    }
+}
+testSingleTypeWithoutNegativeZeroCall();
+
+
+// Test Math.round() on constants
+function testConstant() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueRoundOnConstant() {
+                return Math.round(${testCaseInput[0]});
+            }
+            noInline(opaqueRoundOnConstant);
+            noOSRExitFuzzing(opaqueRoundOnConstant);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueRoundOnConstant(), ${testCaseInput[1]})) {
+                    throw "Failed testConstant()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueRoundOnConstant) > 1)
+                throw "We should have compiled a single round for the expected type.";
+        `);
+    }
+}
+testConstant();
+
+
+// Verify we call valueOf() exactly once per call.
+function opaqueRoundForSideEffects(argument) {
+    return Math.round(argument);
+}
+noInline(opaqueRoundForSideEffects);
+noOSRExitFuzzing(opaqueRoundForSideEffects);
+
+function testSideEffect() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let round16 = Math.round(16);
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueRoundForSideEffects(testObject) !== round16)
+            throw "Incorrect result in testSideEffect()";
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testSideEffect()";
+    if (numberOfDFGCompiles(opaqueRoundForSideEffects) > 1)
+        throw "opaqueRoundForSideEffects() is predictable, it should only be compiled once.";
+}
+testSideEffect();
+
+
+// Verify round() is not subject to CSE if the argument has side effects.
+function opaqueRoundForCSE(argument) {
+    return Math.round(argument) + Math.round(argument) + Math.round(argument);
+}
+noInline(opaqueRoundForCSE);
+noOSRExitFuzzing(opaqueRoundForCSE);
+
+function testCSE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let round16 = Math.round(16);
+    let threeRound16 = round16 + round16 + round16;
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueRoundForCSE(testObject) !== threeRound16)
+            throw "Incorrect result in testCSE()";
+    }
+    if (testObject.counter !== 3e4)
+        throw "Failed testCSE()";
+    if (numberOfDFGCompiles(opaqueRoundForCSE) > 1)
+        throw "opaqueRoundForCSE() is predictable, it should only be compiled once.";
+}
+testCSE();
+
+
+// Verify round() is not subject to DCE if the argument has side effects.
+function opaqueRoundForDCE(argument) {
+    Math.round(argument);
+}
+noInline(opaqueRoundForDCE);
+noOSRExitFuzzing(opaqueRoundForDCE);
+
+function testDCE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    for (let i = 0; i < 1e4; ++i) {
+        opaqueRoundForDCE(testObject);
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testDCE()";
+    if (numberOfDFGCompiles(opaqueRoundForDCE) > 1)
+        throw "opaqueRoundForDCE() is predictable, it should only be compiled once.";
+}
+testDCE();
+
+
+// Test exceptions in the argument.
+function testException() {
+    let counter = 0;
+    function opaqueRoundWithException(argument) {
+        let result = Math.round(argument);
+        ++counter;
+        return result;
+    }
+    noInline(opaqueRoundWithException);
+
+    let testObject = { valueOf: () => {  return 64; } };
+    let round64 = Math.round(64);
+
+    // Warm up without exception.
+    for (let i = 0; i < 1e3; ++i) {
+        if (opaqueRoundWithException(testObject) !== round64)
+            throw "Incorrect result in opaqueRoundWithException()";
+    }
+
+    let testThrowObject = { valueOf: () => { throw testObject; return 64; } };
+
+    for (let i = 0; i < 1e2; ++i) {
+        try {
+            if (opaqueRoundWithException(testThrowObject) !== 8)
+                throw "This code should not be reached!!";
+        } catch (e) {
+            if (e !== testObject) {
+                throw "Wrong object thrown from opaqueRoundWithException."
+            }
+        }
+    }
+
+    if (counter !== 1e3) {
+        throw "Invalid count in testException()";
+    }
+}
+testException();
diff --git a/JSTests/stress/arith-trunc-on-various-types.js b/JSTests/stress/arith-trunc-on-various-types.js
new file mode 100644 (file)
index 0000000..54c535b
--- /dev/null
@@ -0,0 +1,295 @@
+"use strict";
+
+let validInputTestCases = [
+    // input as string, expected result as string.
+    ["undefined", "NaN"],
+    ["null", "0"],
+    ["0", "0"],
+    ["-0.", "-0"],
+    ["0.5", "0"],
+    ["-0.5", "-0"],
+    ["4", "4"],
+    ["42.1", "42"],
+    ["42.5", "42"],
+    ["42.9", "42"],
+    ["-42.1", "-42"],
+    ["-42.5", "-42"],
+    ["-42.9", "-42"],
+    ["Math.PI", "3"],
+    ["Infinity", "Infinity"],
+    ["-Infinity", "-Infinity"],
+    ["NaN", "NaN"],
+    ["\"WebKit\"", "NaN"],
+    ["\"4\"", "4"],
+    ["\"42.5\"", "42"],
+    ["{ valueOf: () => { return 4; } }", "4"],
+    ["{ valueOf: () => { return 0; } }", "0"],
+    ["{ valueOf: () => { return -0; } }", "-0"],
+    ["{ valueOf: () => { return 0.5; } }", "0"],
+    ["{ valueOf: () => { return -0.5; } }", "-0"],
+    ["{ valueOf: () => { return Number.MIN_SAFE_INTEGER; } }", "-9007199254740991"],
+    ["{ valueOf: () => { return Number.MAX_SAFE_INTEGER; } }", "9007199254740991"],
+    ["{ valueOf: () => { return 0x80000000|0; } }", "-2147483648"],
+    ["{ valueOf: () => { return 0x7fffffff|0; } }", "2147483647"],
+    ["{ valueOf: () => { return (0x80000000|0) - 0.5; } }", "-2147483648"],
+    ["{ valueOf: () => { return (0x7fffffff|0) + 0.5; } }", "2147483647"],
+];
+
+let validInputTypedTestCases = validInputTestCases.map((element) => { return [eval("(" + element[0] + ")"), eval(element[1])] });
+
+function isIdentical(result, expected)
+{
+    if (expected === expected) {
+        if (result !== expected)
+            return false;
+        if (!expected && (1 / expected) !== (1 / result))
+            return false;
+
+        return true;
+    }
+    return result !== result;
+}
+
+
+// Test Math.trunc() without arguments.
+function opaqueTruncNoArgument() {
+    return Math.trunc();
+}
+noInline(opaqueTruncNoArgument);
+noOSRExitFuzzing(opaqueTruncNoArgument);
+
+function testNoArgument() {
+    for (let i = 0; i < 1e4; ++i) {
+        let output = opaqueTruncNoArgument();
+        if (!isIdentical(output, NaN)) {
+            throw "Failed opaqueTruncNoArgument";
+        }
+    }
+    if (numberOfDFGCompiles(opaqueTruncNoArgument) > 1)
+        throw "The call without arguments should never exit.";
+}
+testNoArgument();
+
+
+// Test Math.trunc() with a very polymorphic input. All test cases are seen at each iteration.
+function opaqueAllTypesTrunc(argument) {
+    return Math.trunc(argument);
+}
+noInline(opaqueAllTypesTrunc);
+noOSRExitFuzzing(opaqueAllTypesTrunc);
+
+function testAllTypesCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesTrunc(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1]))
+                throw "Failed testAllTypesCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesTrunc) > 3)
+        throw "We should have detected trunc() was polymorphic and generated a generic version.";
+}
+testAllTypesCall();
+
+
+// Polymorphic input but negative zero is not observable.
+function opaqueAllTypesTruncWithoutNegativeZero(argument) {
+    return Math.trunc(argument) + 0;
+}
+noInline(opaqueAllTypesTruncWithoutNegativeZero);
+noOSRExitFuzzing(opaqueAllTypesTruncWithoutNegativeZero);
+
+function testAllTypesWithoutNegativeZeroCall() {
+    for (let i = 0; i < 1e3; ++i) {
+        for (let testCaseInput of validInputTypedTestCases) {
+            let output = opaqueAllTypesTruncWithoutNegativeZero(testCaseInput[0]);
+            if (!isIdentical(output, testCaseInput[1] + 0))
+                throw "Failed testAllTypesWithoutNegativeZeroCall for input " + testCaseInput[0] + " expected " + testCaseInput[1] + " got " + output;
+        }
+    }
+    if (numberOfDFGCompiles(opaqueAllTypesTrunc) > 3)
+        throw "We should have detected trunc() was polymorphic and generated a generic version.";
+}
+testAllTypesWithoutNegativeZeroCall();
+
+
+// Test Math.trunc() on a completely typed input. Every call see only one type.
+function testSingleTypeCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueTrunc(argument) {
+                return Math.trunc(argument);
+            }
+            noInline(opaqueTrunc);
+            noOSRExitFuzzing(opaqueTrunc);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueTrunc(${testCaseInput[0]}), ${testCaseInput[1]})) {
+                    throw "Failed testSingleTypeCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueTrunc) > 1)
+                throw "We should have compiled a single trunc for the expected type.";
+        `);
+    }
+}
+testSingleTypeCall();
+
+
+// Test Math.trunc() on a completely typed input, but without negative zero.
+function testSingleTypeWithoutNegativeZeroCall() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueTrunc(argument) {
+                return Math.trunc(argument) - 0;
+            }
+            noInline(opaqueTrunc);
+            noOSRExitFuzzing(opaqueTrunc);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueTrunc(${testCaseInput[0]}), ${testCaseInput[1]} - 0)) {
+                    throw "Failed testSingleTypeWithoutNegativeZeroCall()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueTrunc) > 1)
+                throw "We should have compiled a single trunc for the expected type.";
+        `);
+    }
+}
+testSingleTypeWithoutNegativeZeroCall();
+
+
+// Test Math.trunc() on constants
+function testConstant() {
+    for (let testCaseInput of validInputTestCases) {
+        eval(`
+            function opaqueTruncOnConstant() {
+                return Math.trunc(${testCaseInput[0]});
+            }
+            noInline(opaqueTruncOnConstant);
+            noOSRExitFuzzing(opaqueTruncOnConstant);
+
+            for (let i = 0; i < 1e4; ++i) {
+                if (!isIdentical(opaqueTruncOnConstant(), ${testCaseInput[1]})) {
+                    throw "Failed testConstant()";
+                }
+            }
+            if (numberOfDFGCompiles(opaqueTruncOnConstant) > 1)
+                throw "We should have compiled a single trunc for the expected type.";
+        `);
+    }
+}
+testConstant();
+
+
+// Verify we call valueOf() exactly once per call.
+function opaqueTruncForSideEffects(argument) {
+    return Math.trunc(argument);
+}
+noInline(opaqueTruncForSideEffects);
+noOSRExitFuzzing(opaqueTruncForSideEffects);
+
+function testSideEffect() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let trunc16 = Math.trunc(16);
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueTruncForSideEffects(testObject) !== trunc16)
+            throw "Incorrect result in testSideEffect()";
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testSideEffect()";
+    if (numberOfDFGCompiles(opaqueTruncForSideEffects) > 1)
+        throw "opaqueTruncForSideEffects() is predictable, it should only be compiled once.";
+}
+testSideEffect();
+
+
+// Verify trunc() is not subject to CSE if the argument has side effects.
+function opaqueTruncForCSE(argument) {
+    return Math.trunc(argument) + Math.trunc(argument) + Math.trunc(argument);
+}
+noInline(opaqueTruncForCSE);
+noOSRExitFuzzing(opaqueTruncForCSE);
+
+function testCSE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    let trunc16 = Math.trunc(16);
+    let threeTrunc16 = trunc16 + trunc16 + trunc16;
+    for (let i = 0; i < 1e4; ++i) {
+        if (opaqueTruncForCSE(testObject) !== threeTrunc16)
+            throw "Incorrect result in testCSE()";
+    }
+    if (testObject.counter !== 3e4)
+        throw "Failed testCSE()";
+    if (numberOfDFGCompiles(opaqueTruncForCSE) > 1)
+        throw "opaqueTruncForCSE() is predictable, it should only be compiled once.";
+}
+testCSE();
+
+
+// Verify trunc() is not subject to DCE if the argument has side effects.
+function opaqueTruncForDCE(argument) {
+    Math.trunc(argument);
+}
+noInline(opaqueTruncForDCE);
+noOSRExitFuzzing(opaqueTruncForDCE);
+
+function testDCE() {
+    let testObject = {
+        counter: 0,
+        valueOf: function() { ++this.counter; return 16; }
+    };
+    for (let i = 0; i < 1e4; ++i) {
+        opaqueTruncForDCE(testObject);
+    }
+    if (testObject.counter !== 1e4)
+        throw "Failed testDCE()";
+    if (numberOfDFGCompiles(opaqueTruncForDCE) > 1)
+        throw "opaqueTruncForDCE() is predictable, it should only be compiled once.";
+}
+testDCE();
+
+
+// Test exceptions in the argument.
+function testException() {
+    let counter = 0;
+    function opaqueTruncWithException(argument) {
+        let result = Math.trunc(argument);
+        ++counter;
+        return result;
+    }
+    noInline(opaqueTruncWithException);
+
+    let testObject = { valueOf: () => {  return 64; } };
+    let trunc64 = Math.trunc(64);
+
+    // Warm up without exception.
+    for (let i = 0; i < 1e3; ++i) {
+        if (opaqueTruncWithException(testObject) !== trunc64)
+            throw "Incorrect result in opaqueTruncWithException()";
+    }
+
+    let testThrowObject = { valueOf: () => { throw testObject; return 64; } };
+
+    for (let i = 0; i < 1e2; ++i) {
+        try {
+            if (opaqueTruncWithException(testThrowObject) !== 8)
+                throw "This code should not be reached!!";
+        } catch (e) {
+            if (e !== testObject) {
+                throw "Wrong object thrown from opaqueTruncWithException."
+            }
+        }
+    }
+
+    if (counter !== 1e3) {
+        throw "Invalid count in testException()";
+    }
+}
+testException();
index 510b84d..55d8031 100644 (file)
@@ -1,3 +1,44 @@
+2016-09-14  Benjamin Poulain  <bpoulain@apple.com>
+
+        [JSC] Make the rounding-related nodes support any type
+        https://bugs.webkit.org/show_bug.cgi?id=161895
+
+        Reviewed by Geoffrey Garen.
+
+        This patch changes ArithRound, ArithFloor, ArithCeil and ArithTrunc
+        to support polymorphic input without exiting on entry.
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        The 4 functions ignore any input past the first argument. It is okay
+        to use the nodes with the first argument and let the Phantoms keep
+        the remaining arguments live.
+
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        Our fixup had the issue we have seen on previous nodes: unaryArithShouldSpeculateInt32()
+        prevents us from picking a good type if we do not see any double.
+
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        Prediction propagation of those nodes are fully determined
+        from their flags and results's prediction. They are moved
+        to the invariant processing.
+
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileArithRounding):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileArithRound):
+        (JSC::FTL::DFG::LowerDFGToB3::compileArithFloor):
+        (JSC::FTL::DFG::LowerDFGToB3::compileArithCeil):
+        (JSC::FTL::DFG::LowerDFGToB3::compileArithTrunc):
+
 2016-09-14  Filip Pizlo  <fpizlo@apple.com>
 
         Remove Heap::setMarked()
index e7af071..218d5d9 100644 (file)
@@ -905,19 +905,23 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
     case ArithCeil:
     case ArithTrunc: {
         JSValue operand = forNode(node->child1()).value();
-        if (operand && operand.isNumber()) {
+        if (Optional<double> number = operand.toNumberFromPrimitive()) {
             double roundedValue = 0;
             if (node->op() == ArithRound)
-                roundedValue = jsRound(operand.asNumber());
+                roundedValue = jsRound(*number);
             else if (node->op() == ArithFloor)
-                roundedValue = floor(operand.asNumber());
+                roundedValue = floor(*number);
             else if (node->op() == ArithCeil)
-                roundedValue = ceil(operand.asNumber());
+                roundedValue = ceil(*number);
             else {
                 ASSERT(node->op() == ArithTrunc);
-                roundedValue = trunc(operand.asNumber());
+                roundedValue = trunc(*number);
             }
 
+            if (node->child1().useKind() == UntypedUse) {
+                setConstant(node, jsNumber(roundedValue));
+                break;
+            }
             if (producesInteger(node->arithRoundingMode())) {
                 int32_t roundedValueAsInt32 = static_cast<int32_t>(roundedValue);
                 if (roundedValueAsInt32 == roundedValue) {
@@ -936,10 +940,15 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
                 break;
             }
         }
-        if (producesInteger(node->arithRoundingMode()))
-            forNode(node).setType(SpecInt32Only);
-        else
-            forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
+        if (node->child1().useKind() == DoubleRepUse) {
+            if (producesInteger(node->arithRoundingMode()))
+                forNode(node).setType(SpecInt32Only);
+            else if (node->child1().useKind() == DoubleRepUse)
+                forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
+        } else {
+            DFG_ASSERT(m_graph, node, node->child1().useKind() == UntypedUse);
+            forNode(node).setType(SpecFullNumber);
+        }
         break;
     }
             
index 80a1c2e..0b0d2dd 100644 (file)
@@ -2450,25 +2450,22 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
             set(VirtualRegister(resultOperand), addToGraph(JSConstant, OpInfo(m_constantNaN)));
             return true;
         }
-        if (argumentCountIncludingThis == 2) {
-            insertChecks();
-            Node* operand = get(virtualRegisterForArgument(1, registerOffset));
-            NodeType op;
-            if (intrinsic == RoundIntrinsic)
-                op = ArithRound;
-            else if (intrinsic == FloorIntrinsic)
-                op = ArithFloor;
-            else if (intrinsic == CeilIntrinsic)
-                op = ArithCeil;
-            else {
-                ASSERT(intrinsic == TruncIntrinsic);
-                op = ArithTrunc;
-            }
-            Node* roundNode = addToGraph(op, OpInfo(0), OpInfo(prediction), operand);
-            set(VirtualRegister(resultOperand), roundNode);
-            return true;
+        insertChecks();
+        Node* operand = get(virtualRegisterForArgument(1, registerOffset));
+        NodeType op;
+        if (intrinsic == RoundIntrinsic)
+            op = ArithRound;
+        else if (intrinsic == FloorIntrinsic)
+            op = ArithFloor;
+        else if (intrinsic == CeilIntrinsic)
+            op = ArithCeil;
+        else {
+            ASSERT(intrinsic == TruncIntrinsic);
+            op = ArithTrunc;
         }
-        return false;
+        Node* roundNode = addToGraph(op, OpInfo(0), OpInfo(prediction), operand);
+        set(VirtualRegister(resultOperand), roundNode);
+        return true;
     }
     case IMulIntrinsic: {
         if (argumentCountIncludingThis != 3)
index 55db094..04f3d12 100644 (file)
@@ -358,7 +358,12 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case ArithFloor:
     case ArithCeil:
     case ArithTrunc:
-        def(PureValue(node, static_cast<uintptr_t>(node->arithRoundingMode())));
+        if (node->child1().useKind() == DoubleRepUse)
+            def(PureValue(node, static_cast<uintptr_t>(node->arithRoundingMode())));
+        else {
+            read(World);
+            write(Heap);
+        }
         return;
 
     case CheckCell:
index 27a0f30..50f36c7 100644 (file)
@@ -376,24 +376,28 @@ private:
         case ArithFloor:
         case ArithCeil:
         case ArithTrunc: {
-            if (m_graph.unaryArithShouldSpeculateInt32(node, FixupPass)) {
+            if (node->child1()->shouldSpeculateInt32OrBoolean() && m_graph.roundShouldSpeculateInt32(node, FixupPass)) {
                 fixIntOrBooleanEdge(node->child1());
                 insertCheck<Int32Use>(m_indexInBlock, node->child1().node());
                 node->convertToIdentity();
                 break;
             }
-            fixDoubleOrBooleanEdge(node->child1());
+            if (node->child1()->shouldSpeculateNotCell()) {
+                fixDoubleOrBooleanEdge(node->child1());
 
-            if (isInt32OrBooleanSpeculation(node->getHeapPrediction()) && m_graph.roundShouldSpeculateInt32(node, FixupPass)) {
-                node->setResult(NodeResultInt32);
-                if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
-                    node->setArithRoundingMode(Arith::RoundingMode::Int32);
-                else
-                    node->setArithRoundingMode(Arith::RoundingMode::Int32WithNegativeZeroCheck);
-            } else {
-                node->setResult(NodeResultDouble);
-                node->setArithRoundingMode(Arith::RoundingMode::Double);
-            }
+                if (isInt32OrBooleanSpeculation(node->getHeapPrediction()) && m_graph.roundShouldSpeculateInt32(node, FixupPass)) {
+                    node->setResult(NodeResultInt32);
+                    if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
+                        node->setArithRoundingMode(Arith::RoundingMode::Int32);
+                    else
+                        node->setArithRoundingMode(Arith::RoundingMode::Int32WithNegativeZeroCheck);
+                } else {
+                    node->setResult(NodeResultDouble);
+                    node->setArithRoundingMode(Arith::RoundingMode::Double);
+                }
+                node->clearFlags(NodeMustGenerate);
+            } else
+                fixEdge<UntypedUse>(node->child1());
             break;
         }
 
index dca1ca5..ffe41e7 100644 (file)
@@ -155,10 +155,10 @@ namespace JSC { namespace DFG {
     macro(ArithFRound, NodeResultDouble | NodeMustGenerate) \
     macro(ArithPow, NodeResultDouble) \
     macro(ArithRandom, NodeResultDouble | NodeMustGenerate) \
-    macro(ArithRound, NodeResultNumber) \
-    macro(ArithFloor, NodeResultNumber) \
-    macro(ArithCeil, NodeResultNumber) \
-    macro(ArithTrunc, NodeResultNumber) \
+    macro(ArithRound, NodeResultNumber | NodeMustGenerate) \
+    macro(ArithFloor, NodeResultNumber | NodeMustGenerate) \
+    macro(ArithCeil, NodeResultNumber | NodeMustGenerate) \
+    macro(ArithTrunc, NodeResultNumber | NodeMustGenerate) \
     macro(ArithSqrt, NodeResultDouble | NodeMustGenerate) \
     macro(ArithSin, NodeResultDouble | NodeMustGenerate) \
     macro(ArithCos, NodeResultDouble | NodeMustGenerate) \
index 562c8a4..ce546eb 100644 (file)
@@ -442,6 +442,58 @@ double JIT_OPERATION operationArithTan(ExecState* exec, EncodedJSValue encodedOp
     return tan(a);
 }
 
+EncodedJSValue JIT_OPERATION operationArithRound(ExecState* exec, EncodedJSValue encodedArgument)
+{
+    VM* vm = &exec->vm();
+    NativeCallFrameTracer tracer(vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(*vm);
+
+    JSValue argument = JSValue::decode(encodedArgument);
+    double valueOfArgument = argument.toNumber(exec);
+    if (UNLIKELY(scope.exception()))
+        return JSValue::encode(JSValue());
+    return JSValue::encode(jsNumber(jsRound(valueOfArgument)));
+}
+
+EncodedJSValue JIT_OPERATION operationArithFloor(ExecState* exec, EncodedJSValue encodedArgument)
+{
+    VM* vm = &exec->vm();
+    NativeCallFrameTracer tracer(vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(*vm);
+
+    JSValue argument = JSValue::decode(encodedArgument);
+    double valueOfArgument = argument.toNumber(exec);
+    if (UNLIKELY(scope.exception()))
+        return JSValue::encode(JSValue());
+    return JSValue::encode(jsNumber(floor(valueOfArgument)));
+}
+
+EncodedJSValue JIT_OPERATION operationArithCeil(ExecState* exec, EncodedJSValue encodedArgument)
+{
+    VM* vm = &exec->vm();
+    NativeCallFrameTracer tracer(vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(*vm);
+
+    JSValue argument = JSValue::decode(encodedArgument);
+    double valueOfArgument = argument.toNumber(exec);
+    if (UNLIKELY(scope.exception()))
+        return JSValue::encode(JSValue());
+    return JSValue::encode(jsNumber(ceil(valueOfArgument)));
+}
+
+EncodedJSValue JIT_OPERATION operationArithTrunc(ExecState* exec, EncodedJSValue encodedArgument)
+{
+    VM* vm = &exec->vm();
+    NativeCallFrameTracer tracer(vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(*vm);
+
+    JSValue argument = JSValue::decode(encodedArgument);
+    double truncatedValueOfArgument = argument.toIntegerPreserveNaN(exec);
+    if (UNLIKELY(scope.exception()))
+        return JSValue::encode(JSValue());
+    return JSValue::encode(jsNumber(truncatedValueOfArgument));
+}
+
 static ALWAYS_INLINE EncodedJSValue getByVal(ExecState* exec, JSCell* base, uint32_t index)
 {
     VM& vm = exec->vm();
index 86e191e..6ab7675 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2013-2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2011, 2013-2016 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -61,6 +61,10 @@ double JIT_OPERATION operationArithFRound(ExecState*, EncodedJSValue encodedOp1)
 double JIT_OPERATION operationArithLog(ExecState*, EncodedJSValue encodedOp1) WTF_INTERNAL;
 double JIT_OPERATION operationArithSin(ExecState*, EncodedJSValue encodedOp1) WTF_INTERNAL;
 double JIT_OPERATION operationArithSqrt(ExecState*, EncodedJSValue encodedOp1) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationArithRound(ExecState*, EncodedJSValue) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationArithFloor(ExecState*, EncodedJSValue) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationArithCeil(ExecState*, EncodedJSValue) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationArithTrunc(ExecState*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetByVal(ExecState*, EncodedJSValue encodedBase, EncodedJSValue encodedProperty) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetByValCell(ExecState*, JSCell*, EncodedJSValue encodedProperty) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetByValArrayInt(ExecState*, JSArray*, int32_t) WTF_INTERNAL;
index 9344f62..3cd52b9 100644 (file)
@@ -318,17 +318,6 @@ private:
             break;
         }
 
-        case ArithRound:
-        case ArithFloor:
-        case ArithCeil:
-        case ArithTrunc: {
-            if (isInt32OrBooleanSpeculation(node->getHeapPrediction()) && m_graph.roundShouldSpeculateInt32(node, m_pass))
-                changed |= setPrediction(SpecInt32Only);
-            else
-                changed |= setPrediction(SpecBytecodeDouble);
-            break;
-        }
-
         case ArithAbs: {
             SpeculatedType childPrediction = node->child1()->prediction();
             if (isInt32OrBooleanSpeculation(childPrediction)
@@ -776,6 +765,18 @@ private:
             break;
         }
 
+        case ArithRound:
+        case ArithFloor:
+        case ArithCeil:
+        case ArithTrunc: {
+            if (isInt32OrBooleanSpeculation(m_currentNode->getHeapPrediction())
+                && m_graph.roundShouldSpeculateInt32(m_currentNode, m_pass))
+                setPrediction(SpecInt32Only);
+            else
+                setPrediction(SpecBytecodeDouble);
+            break;
+        }
+
         case ArithRandom: {
             setPrediction(SpecDoubleReal);
             break;
@@ -950,10 +951,6 @@ private:
         case ArithMul:
         case ArithDiv:
         case ArithMod:
-        case ArithRound:
-        case ArithFloor:
-        case ArithCeil:
-        case ArithTrunc:
         case ArithAbs:
         case GetByVal:
         case ToThis:
index 6f172c7..16b3677 100644 (file)
@@ -4910,101 +4910,129 @@ void SpeculativeJIT::compileArithMod(Node* node)
 
 void SpeculativeJIT::compileArithRounding(Node* node)
 {
-    ASSERT(node->child1().useKind() == DoubleRepUse);
+    if (node->child1().useKind() == DoubleRepUse) {
+        SpeculateDoubleOperand value(this, node->child1());
+        FPRReg valueFPR = value.fpr();
 
-    SpeculateDoubleOperand value(this, node->child1());
-    FPRReg valueFPR = value.fpr();
+        auto setResult = [&] (FPRReg resultFPR) {
+            if (producesInteger(node->arithRoundingMode())) {
+                GPRTemporary roundedResultAsInt32(this);
+                FPRTemporary scratch(this);
+                FPRReg scratchFPR = scratch.fpr();
+                GPRReg resultGPR = roundedResultAsInt32.gpr();
+                JITCompiler::JumpList failureCases;
+                m_jit.branchConvertDoubleToInt32(resultFPR, resultGPR, failureCases, scratchFPR, shouldCheckNegativeZero(node->arithRoundingMode()));
+                speculationCheck(Overflow, JSValueRegs(), node, failureCases);
 
-    auto setResult = [&] (FPRReg resultFPR) {
-        if (producesInteger(node->arithRoundingMode())) {
-            GPRTemporary roundedResultAsInt32(this);
-            FPRTemporary scratch(this);
-            FPRReg scratchFPR = scratch.fpr();
-            GPRReg resultGPR = roundedResultAsInt32.gpr();
-            JITCompiler::JumpList failureCases;
-            m_jit.branchConvertDoubleToInt32(resultFPR, resultGPR, failureCases, scratchFPR, shouldCheckNegativeZero(node->arithRoundingMode()));
-            speculationCheck(Overflow, JSValueRegs(), node, failureCases);
+                int32Result(resultGPR, node);
+            } else
+                doubleResult(resultFPR, node);
+        };
+
+        if (m_jit.supportsFloatingPointRounding()) {
+            switch (node->op()) {
+            case ArithRound: {
+                FPRTemporary result(this);
+                FPRReg resultFPR = result.fpr();
+                if (producesInteger(node->arithRoundingMode()) && !shouldCheckNegativeZero(node->arithRoundingMode())) {
+                    static const double halfConstant = 0.5;
+                    m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&halfConstant), resultFPR);
+                    m_jit.addDouble(valueFPR, resultFPR);
+                    m_jit.floorDouble(resultFPR, resultFPR);
+                } else {
+                    m_jit.ceilDouble(valueFPR, resultFPR);
+                    FPRTemporary realPart(this);
+                    FPRReg realPartFPR = realPart.fpr();
+                    m_jit.subDouble(resultFPR, valueFPR, realPartFPR);
+
+                    FPRTemporary scratch(this);
+                    FPRReg scratchFPR = scratch.fpr();
+                    static const double halfConstant = 0.5;
+                    m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&halfConstant), scratchFPR);
+
+                    JITCompiler::Jump shouldUseCeiled = m_jit.branchDouble(JITCompiler::DoubleLessThanOrEqual, realPartFPR, scratchFPR);
+                    static const double oneConstant = -1.0;
+                    m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&oneConstant), scratchFPR);
+                    m_jit.addDouble(scratchFPR, resultFPR);
+                    shouldUseCeiled.link(&m_jit);
+                }
+                setResult(resultFPR);
+                return;
+            }
 
-            int32Result(resultGPR, node);
-        } else
-            doubleResult(resultFPR, node);
-    };
+            case ArithFloor: {
+                FPRTemporary rounded(this);
+                FPRReg resultFPR = rounded.fpr();
+                m_jit.floorDouble(valueFPR, resultFPR);
+                setResult(resultFPR);
+                return;
+            }
 
-    if (m_jit.supportsFloatingPointRounding()) {
-        switch (node->op()) {
-        case ArithRound: {
-            FPRTemporary result(this);
-            FPRReg resultFPR = result.fpr();
-            if (producesInteger(node->arithRoundingMode()) && !shouldCheckNegativeZero(node->arithRoundingMode())) {
-                static const double halfConstant = 0.5;
-                m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&halfConstant), resultFPR);
-                m_jit.addDouble(valueFPR, resultFPR);
-                m_jit.floorDouble(resultFPR, resultFPR);
-            } else {
+            case ArithCeil: {
+                FPRTemporary rounded(this);
+                FPRReg resultFPR = rounded.fpr();
                 m_jit.ceilDouble(valueFPR, resultFPR);
-                FPRTemporary realPart(this);
-                FPRReg realPartFPR = realPart.fpr();
-                m_jit.subDouble(resultFPR, valueFPR, realPartFPR);
-
-                FPRTemporary scratch(this);
-                FPRReg scratchFPR = scratch.fpr();
-                static const double halfConstant = 0.5;
-                m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&halfConstant), scratchFPR);
-
-                JITCompiler::Jump shouldUseCeiled = m_jit.branchDouble(JITCompiler::DoubleLessThanOrEqual, realPartFPR, scratchFPR);
-                static const double oneConstant = -1.0;
-                m_jit.loadDouble(MacroAssembler::TrustedImmPtr(&oneConstant), scratchFPR);
-                m_jit.addDouble(scratchFPR, resultFPR);
-                shouldUseCeiled.link(&m_jit);
+                setResult(resultFPR);
+                return;
             }
-            setResult(resultFPR);
-            return;
-        }
 
-        case ArithFloor: {
-            FPRTemporary rounded(this);
-            FPRReg resultFPR = rounded.fpr();
-            m_jit.floorDouble(valueFPR, resultFPR);
-            setResult(resultFPR);
-            return;
-        }
+            case ArithTrunc: {
+                FPRTemporary rounded(this);
+                FPRReg resultFPR = rounded.fpr();
+                m_jit.roundTowardZeroDouble(valueFPR, resultFPR);
+                setResult(resultFPR);
+                return;
+            }
 
-        case ArithCeil: {
-            FPRTemporary rounded(this);
-            FPRReg resultFPR = rounded.fpr();
-            m_jit.ceilDouble(valueFPR, resultFPR);
+            default:
+                RELEASE_ASSERT_NOT_REACHED();
+            }
+        } else {
+            flushRegisters();
+            FPRResult roundedResultAsDouble(this);
+            FPRReg resultFPR = roundedResultAsDouble.fpr();
+            if (node->op() == ArithRound)
+                callOperation(jsRound, resultFPR, valueFPR);
+            else if (node->op() == ArithFloor)
+                callOperation(floor, resultFPR, valueFPR);
+            else if (node->op() == ArithCeil)
+                callOperation(ceil, resultFPR, valueFPR);
+            else {
+                ASSERT(node->op() == ArithTrunc);
+                callOperation(trunc, resultFPR, valueFPR);
+            }
             setResult(resultFPR);
-            return;
         }
+        return;
+    }
 
-        case ArithTrunc: {
-            FPRTemporary rounded(this);
-            FPRReg resultFPR = rounded.fpr();
-            m_jit.roundTowardZeroDouble(valueFPR, resultFPR);
-            setResult(resultFPR);
-            return;
-        }
+    DFG_ASSERT(m_jit.graph(), node, node->child1().useKind() == UntypedUse);
 
-        default:
-            RELEASE_ASSERT_NOT_REACHED();
-        }
-    } else {
-        flushRegisters();
-        FPRResult roundedResultAsDouble(this);
-        FPRReg resultFPR = roundedResultAsDouble.fpr();
-        if (node->op() == ArithRound)
-            callOperation(jsRound, resultFPR, valueFPR);
-        else if (node->op() == ArithFloor)
-            callOperation(floor, resultFPR, valueFPR);
-        else if (node->op() == ArithCeil)
-            callOperation(ceil, resultFPR, valueFPR);
-        else {
-            ASSERT(node->op() == ArithTrunc);
-            callOperation(trunc, resultFPR, valueFPR);
-        }
-        m_jit.exceptionCheck();
-        setResult(resultFPR);
+    JSValueOperand argument(this, node->child1());
+    JSValueRegs argumentRegs = argument.jsValueRegs();
+#if USE(JSVALUE64)
+    GPRTemporary result(this);
+    JSValueRegs resultRegs = JSValueRegs(result.gpr());
+#else
+    GPRTemporary resultTag(this);
+    GPRTemporary resultPayload(this);
+    JSValueRegs resultRegs = JSValueRegs(resultPayload.gpr(), resultTag.gpr());
+#endif
+    flushRegisters();
+    J_JITOperation_EJ operation = nullptr;
+    if (node->op() == ArithRound)
+        operation = operationArithRound;
+    else if (node->op() == ArithFloor)
+        operation = operationArithFloor;
+    else if (node->op() == ArithCeil)
+        operation = operationArithCeil;
+    else {
+        ASSERT(node->op() == ArithTrunc);
+        operation = operationArithTrunc;
     }
+    callOperation(operation, resultRegs, argumentRegs);
+    m_jit.exceptionCheck();
+    jsValueResult(resultRegs, node);
 }
 
 void SpeculativeJIT::compileArithSin(Node* node)
index 0b1a02f..08037d8 100644 (file)
@@ -2266,67 +2266,91 @@ private:
 
     void compileArithRound()
     {
-        LValue result = nullptr;
+        if (m_node->child1().useKind() == DoubleRepUse) {
+            LValue result = nullptr;
+            if (producesInteger(m_node->arithRoundingMode()) && !shouldCheckNegativeZero(m_node->arithRoundingMode())) {
+                LValue value = lowDouble(m_node->child1());
+                result = m_out.doubleFloor(m_out.doubleAdd(value, m_out.constDouble(0.5)));
+            } else {
+                LBasicBlock realPartIsMoreThanHalf = m_out.newBlock();
+                LBasicBlock continuation = m_out.newBlock();
 
-        if (producesInteger(m_node->arithRoundingMode()) && !shouldCheckNegativeZero(m_node->arithRoundingMode())) {
-            LValue value = lowDouble(m_node->child1());
-            result = m_out.doubleFloor(m_out.doubleAdd(value, m_out.constDouble(0.5)));
-        } else {
-            LBasicBlock realPartIsMoreThanHalf = m_out.newBlock();
-            LBasicBlock continuation = m_out.newBlock();
+                LValue value = lowDouble(m_node->child1());
+                LValue integerValue = m_out.doubleCeil(value);
+                ValueFromBlock integerValueResult = m_out.anchor(integerValue);
 
-            LValue value = lowDouble(m_node->child1());
-            LValue integerValue = m_out.doubleCeil(value);
-            ValueFromBlock integerValueResult = m_out.anchor(integerValue);
+                LValue realPart = m_out.doubleSub(integerValue, value);
 
-            LValue realPart = m_out.doubleSub(integerValue, value);
+                m_out.branch(m_out.doubleGreaterThanOrUnordered(realPart, m_out.constDouble(0.5)), unsure(realPartIsMoreThanHalf), unsure(continuation));
 
-            m_out.branch(m_out.doubleGreaterThanOrUnordered(realPart, m_out.constDouble(0.5)), unsure(realPartIsMoreThanHalf), unsure(continuation));
+                LBasicBlock lastNext = m_out.appendTo(realPartIsMoreThanHalf, continuation);
+                LValue integerValueRoundedDown = m_out.doubleSub(integerValue, m_out.constDouble(1));
+                ValueFromBlock integerValueRoundedDownResult = m_out.anchor(integerValueRoundedDown);
+                m_out.jump(continuation);
+                m_out.appendTo(continuation, lastNext);
 
-            LBasicBlock lastNext = m_out.appendTo(realPartIsMoreThanHalf, continuation);
-            LValue integerValueRoundedDown = m_out.doubleSub(integerValue, m_out.constDouble(1));
-            ValueFromBlock integerValueRoundedDownResult = m_out.anchor(integerValueRoundedDown);
-            m_out.jump(continuation);
-            m_out.appendTo(continuation, lastNext);
+                result = m_out.phi(Double, integerValueResult, integerValueRoundedDownResult);
+            }
 
-            result = m_out.phi(Double, integerValueResult, integerValueRoundedDownResult);
+            if (producesInteger(m_node->arithRoundingMode())) {
+                LValue integerValue = convertDoubleToInt32(result, shouldCheckNegativeZero(m_node->arithRoundingMode()));
+                setInt32(integerValue);
+            } else
+                setDouble(result);
+            return;
         }
 
-        if (producesInteger(m_node->arithRoundingMode())) {
-            LValue integerValue = convertDoubleToInt32(result, shouldCheckNegativeZero(m_node->arithRoundingMode()));
-            setInt32(integerValue);
-        } else
-            setDouble(result);
+        DFG_ASSERT(m_graph, m_node, m_node->child1().useKind() == UntypedUse);
+        LValue argument = lowJSValue(m_node->child1());
+        setJSValue(vmCall(Int64, m_out.operation(operationArithRound), m_callFrame, argument));
     }
 
     void compileArithFloor()
     {
-        LValue value = lowDouble(m_node->child1());
-        LValue integerValue = m_out.doubleFloor(value);
-        if (producesInteger(m_node->arithRoundingMode()))
-            setInt32(convertDoubleToInt32(integerValue, shouldCheckNegativeZero(m_node->arithRoundingMode())));
-        else
-            setDouble(integerValue);
+        if (m_node->child1().useKind() == DoubleRepUse) {
+            LValue value = lowDouble(m_node->child1());
+            LValue integerValue = m_out.doubleFloor(value);
+            if (producesInteger(m_node->arithRoundingMode()))
+                setInt32(convertDoubleToInt32(integerValue, shouldCheckNegativeZero(m_node->arithRoundingMode())));
+            else
+                setDouble(integerValue);
+            return;
+        }
+        DFG_ASSERT(m_graph, m_node, m_node->child1().useKind() == UntypedUse);
+        LValue argument = lowJSValue(m_node->child1());
+        setJSValue(vmCall(Int64, m_out.operation(operationArithFloor), m_callFrame, argument));
     }
 
     void compileArithCeil()
     {
-        LValue value = lowDouble(m_node->child1());
-        LValue integerValue = m_out.doubleCeil(value);
-        if (producesInteger(m_node->arithRoundingMode()))
-            setInt32(convertDoubleToInt32(integerValue, shouldCheckNegativeZero(m_node->arithRoundingMode())));
-        else
-            setDouble(integerValue);
+        if (m_node->child1().useKind() == DoubleRepUse) {
+            LValue value = lowDouble(m_node->child1());
+            LValue integerValue = m_out.doubleCeil(value);
+            if (producesInteger(m_node->arithRoundingMode()))
+                setInt32(convertDoubleToInt32(integerValue, shouldCheckNegativeZero(m_node->arithRoundingMode())));
+            else
+                setDouble(integerValue);
+            return;
+        }
+        DFG_ASSERT(m_graph, m_node, m_node->child1().useKind() == UntypedUse);
+        LValue argument = lowJSValue(m_node->child1());
+        setJSValue(vmCall(Int64, m_out.operation(operationArithCeil), m_callFrame, argument));
     }
 
     void compileArithTrunc()
     {
-        LValue value = lowDouble(m_node->child1());
-        LValue result = m_out.doubleTrunc(value);
-        if (producesInteger(m_node->arithRoundingMode()))
-            setInt32(convertDoubleToInt32(result, shouldCheckNegativeZero(m_node->arithRoundingMode())));
-        else
-            setDouble(result);
+        if (m_node->child1().useKind() == DoubleRepUse) {
+            LValue value = lowDouble(m_node->child1());
+            LValue result = m_out.doubleTrunc(value);
+            if (producesInteger(m_node->arithRoundingMode()))
+                setInt32(convertDoubleToInt32(result, shouldCheckNegativeZero(m_node->arithRoundingMode())));
+            else
+                setDouble(result);
+            return;
+        }
+        DFG_ASSERT(m_graph, m_node, m_node->child1().useKind() == UntypedUse);
+        LValue argument = lowJSValue(m_node->child1());
+        setJSValue(vmCall(Int64, m_out.operation(operationArithTrunc), m_callFrame, argument));
     }
 
     void compileArithSqrt()