[DFG][FTL][B3] Support floor and ceil
https://bugs.webkit.org/show_bug.cgi?id=154683

Reviewed by Filip Pizlo.

Source/JavaScriptCore:

This patch implements and fixes the following things.

1. Implement Ceil and Floor in DFG, FTL and B3

x86 SSE 4.2 and ARM64 have round instructions that can directly perform Ceil or Floor.
This patch leverages this functionality. We introduce ArithFloor and ArithCeil.
During DFG phase, these nodes attempt to convert itself to Identity (in Fixup phase).
As the same to ArithRound, it tracks arith rounding mode.
And if these nodes are required to emit machine codes, we emit rounding machine code
if it is supported in the current machine. For example, in x86, we emit `round`.

This `Floor` functionality is nice for @toInteger in builtin.
That is used for Array.prototype.{forEach, map, every, some, reduce...}
And according to the benchmark results, Kraken audio-oscillator is slightly improved
due to its frequent Math.round and Math.floor calls.

2. Implement Floor in B3 and Air

As the same to Ceil in B3, we add a new B3 IR and Air opcode, Floor.
This Floor is leveraged to implement ArithFloor in DFG.

3. Fix ArithRound operation

Currently, we used cvtsd2si (in x86) to convert double value to int32.
And we also used this to implement Math.round, like, cvtsd2si(value + 0.5).
However, this implementation is not correct. Because cvtsd2si is not floor operation.
It is trucate operation. This is OK for positive numbers. But NG for negative numbers.
For example, the current implementation accidentally rounds `-0.6` to `-0.0`. This should be `-1.0`.
Using Ceil and Floor instructions, we implement correct ArithRound.

* assembler/MacroAssemblerARM.h:
(JSC::MacroAssemblerARM::supportsFloatingPointRounding):
(JSC::MacroAssemblerARM::ceilDouble):
(JSC::MacroAssemblerARM::floorDouble):
(JSC::MacroAssemblerARM::supportsFloatingPointCeil): Deleted.
* assembler/MacroAssemblerARM64.h:
(JSC::MacroAssemblerARM64::supportsFloatingPointRounding):
(JSC::MacroAssemblerARM64::floorFloat):
(JSC::MacroAssemblerARM64::supportsFloatingPointCeil): Deleted.
* assembler/MacroAssemblerARMv7.h:
(JSC::MacroAssemblerARMv7::supportsFloatingPointRounding):
(JSC::MacroAssemblerARMv7::ceilDouble):
(JSC::MacroAssemblerARMv7::floorDouble):
(JSC::MacroAssemblerARMv7::supportsFloatingPointCeil): Deleted.
* assembler/MacroAssemblerMIPS.h:
(JSC::MacroAssemblerMIPS::ceilDouble):
(JSC::MacroAssemblerMIPS::floorDouble):
(JSC::MacroAssemblerMIPS::supportsFloatingPointRounding):
(JSC::MacroAssemblerMIPS::supportsFloatingPointCeil): Deleted.
* assembler/MacroAssemblerSH4.h:
(JSC::MacroAssemblerSH4::supportsFloatingPointRounding):
(JSC::MacroAssemblerSH4::ceilDouble):
(JSC::MacroAssemblerSH4::floorDouble):
(JSC::MacroAssemblerSH4::supportsFloatingPointCeil): Deleted.
* assembler/MacroAssemblerX86Common.h:
(JSC::MacroAssemblerX86Common::floorDouble):
(JSC::MacroAssemblerX86Common::floorFloat):
(JSC::MacroAssemblerX86Common::supportsFloatingPointRounding):
(JSC::MacroAssemblerX86Common::supportsFloatingPointCeil): Deleted.
* b3/B3ConstDoubleValue.cpp:
(JSC::B3::ConstDoubleValue::floorConstant):
* b3/B3ConstDoubleValue.h:
* b3/B3ConstFloatValue.cpp:
(JSC::B3::ConstFloatValue::floorConstant):
* b3/B3ConstFloatValue.h:
* b3/B3LowerMacrosAfterOptimizations.cpp:
* b3/B3LowerToAir.cpp:
(JSC::B3::Air::LowerToAir::lower):
* b3/B3Opcode.cpp:
(WTF::printInternal):
* b3/B3Opcode.h:
* b3/B3ReduceDoubleToFloat.cpp:
* b3/B3ReduceStrength.cpp:
* b3/B3Validate.cpp:
* b3/B3Value.cpp:
(JSC::B3::Value::floorConstant):
(JSC::B3::Value::isRounded):
(JSC::B3::Value::effects):
(JSC::B3::Value::key):
(JSC::B3::Value::typeFor):
* b3/B3Value.h:
* b3/air/AirFixPartialRegisterStalls.cpp:
* b3/air/AirOpcode.opcodes:
* b3/testb3.cpp:
(JSC::B3::testFloorCeilArg):
(JSC::B3::testFloorArg):
(JSC::B3::testFloorImm):
(JSC::B3::testFloorMem):
(JSC::B3::testFloorFloorArg):
(JSC::B3::testCeilFloorArg):
(JSC::B3::testFloorIToD64):
(JSC::B3::testFloorIToD32):
(JSC::B3::testFloorArgWithUselessDoubleConversion):
(JSC::B3::testFloorArgWithEffectfulDoubleConversion):
(JSC::B3::run):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGArithMode.cpp:
(WTF::printInternal):
* dfg/DFGArithMode.h:
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::dump):
* dfg/DFGGraph.h:
(JSC::DFG::Graph::roundShouldSpeculateInt32):
* dfg/DFGNode.h:
(JSC::DFG::Node::arithNodeFlags):
(JSC::DFG::Node::hasHeapPrediction):
(JSC::DFG::Node::hasArithRoundingMode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileArithRounding):
(JSC::DFG::SpeculativeJIT::compileArithRound): Deleted.
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileArithRound):
(JSC::FTL::DFG::LowerDFGToB3::compileArithFloor):
(JSC::FTL::DFG::LowerDFGToB3::compileArithCeil):
* ftl/FTLOutput.h:
(JSC::FTL::Output::doubleFloor):
* jit/ThunkGenerators.cpp:
(JSC::ceilThunkGenerator):
* tests/stress/math-ceil-arith-rounding-mode.js: Added.
(firstCareAboutZeroSecondDoesNot):
(firstDoNotCareAboutZeroSecondDoes):
(warmup):
(verifyNegativeZeroIsPreserved):
* tests/stress/math-ceil-basics.js: Added.
(mathCeilOnIntegers):
(mathCeilOnDoubles):
(mathCeilOnBooleans):
(uselessMathCeil):
(mathCeilWithOverflow):
(mathCeilConsumedAsDouble):
(mathCeilDoesNotCareAboutMinusZero):
(mathCeilNoArguments):
(mathCeilTooManyArguments):
(testMathCeilOnConstants):
(mathCeilStructTransition):
(Math.ceil):
* tests/stress/math-floor-arith-rounding-mode.js: Added.
(firstCareAboutZeroSecondDoesNot):
(firstDoNotCareAboutZeroSecondDoes):
(warmup):
(verifyNegativeZeroIsPreserved):
* tests/stress/math-floor-basics.js: Added.
(mathFloorOnIntegers):
(mathFloorOnDoubles):
(mathFloorOnBooleans):
(uselessMathFloor):
(mathFloorWithOverflow):
(mathFloorConsumedAsDouble):
(mathFloorDoesNotCareAboutMinusZero):
(mathFloorNoArguments):
(mathFloorTooManyArguments):
(testMathFloorOnConstants):
(mathFloorStructTransition):
(Math.floor):
* tests/stress/math-round-should-not-use-truncate.js: Added.
(mathRoundDoesNotCareAboutMinusZero):
* tests/stress/math-rounding-infinity.js: Added.
(shouldBe):
(testRound):
(testFloor):
(testCeil):
* tests/stress/math-rounding-nan.js: Added.
(shouldBe):
(testRound):
(testFloor):
(testCeil):
* tests/stress/math-rounding-negative-zero.js: Added.
(shouldBe):
(testRound):
(testFloor):
(testCeil):
(testRoundNonNegativeZero):
(testRoundNonNegativeZero2):

Websites/webkit.org:

* docs/b3/intermediate-representation.html:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@197380 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/tests/stress/math-floor-basics.js b/Source/JavaScriptCore/tests/stress/math-floor-basics.js
new file mode 100644
index 0000000..7448eb9
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/math-floor-basics.js
@@ -0,0 +1,257 @@
+
+function mathFloorOnIntegers(value)
+{
+    return Math.floor(value);
+}
+noInline(mathFloorOnIntegers);
+
+function mathFloorOnDoubles(value)
+{
+    return Math.floor(value);
+}
+noInline(mathFloorOnDoubles);
+
+function mathFloorOnBooleans(value)
+{
+    return Math.floor(value);
+}
+noInline(mathFloorOnBooleans);
+
+// The trivial cases first.
+for (var i = 1; i < 1e4; ++i) {
+    var flooredValue = mathFloorOnIntegers(i);
+    if (flooredValue !== i)
+        throw new Error("mathFloorOnIntegers(" + i + ") = " + flooredValue);
+
+    var flooredValue = mathFloorOnIntegers(-i);
+    if (flooredValue !== -i)
+        throw new Error("mathFloorOnIntegers(" + -i + ") = " + flooredValue);
+
+    var doubleLow = i + 0.4;
+    var flooredValue = mathFloorOnDoubles(doubleLow);
+    if (flooredValue !== i)
+        throw new Error("mathFloorOnDoubles(" + doubleLow + ") = " + flooredValue);
+
+    var doubleHigh = i + 0.6;
+    var flooredValue = mathFloorOnDoubles(doubleHigh);
+    if (flooredValue !== i)
+        throw new Error("mathFloorOnDoubles(" + doubleHigh + ") = " + flooredValue);
+
+    var doubleMid = i + 0.5;
+    var flooredValue = mathFloorOnDoubles(doubleMid);
+    if (flooredValue !== i)
+        throw new Error("mathFloorOnDoubles(" + doubleMid + ") = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(-0.6);
+    if (flooredValue !== -1.0)
+        throw new Error("mathFloorOnDoubles(-0.6) = " + flooredValue);
+}
+
+// Some more interesting cases, some of them well OSR exit when the return value is zero.
+for (var i = 0; i < 1e4; ++i) {
+    var flooredValue = mathFloorOnIntegers(i);
+    if (flooredValue !== i)
+        throw new Error("mathFloorOnIntegers(" + i + ") = " + flooredValue);
+
+    var flooredValue = mathFloorOnIntegers(-i);
+    if (flooredValue !== -i)
+        throw new Error("mathFloorOnIntegers(-" + i + ") = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(-0.4);
+    if (flooredValue !== -1.00)
+        throw new Error("mathFloorOnDoubles(-0.4) = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(-0.5);
+    if (flooredValue !== -1.0)
+        throw new Error("mathFloorOnDoubles(-0.5) = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(-0);
+    if (!(flooredValue === 0 && (1/flooredValue) === -Infinity))
+        throw new Error("mathFloorOnDoubles(-0) = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(NaN);
+    if (flooredValue === flooredValue)
+        throw new Error("mathFloorOnDoubles(NaN) = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(Number.POSITIVE_INFINITY);
+    if (flooredValue !== Number.POSITIVE_INFINITY)
+        throw new Error("mathFloorOnDoubles(Number.POSITIVE_INFINITY) = " + flooredValue);
+
+    var flooredValue = mathFloorOnDoubles(Number.NEGATIVE_INFINITY);
+    if (flooredValue !== Number.NEGATIVE_INFINITY)
+        throw new Error("mathFloorOnDoubles(Number.NEGATIVE_INFINITY) = " + flooredValue);
+
+    var boolean = !!(i % 2);
+    var flooredBoolean = mathFloorOnBooleans(boolean);
+    if (flooredBoolean != boolean)
+        throw new Error("mathFloorOnDoubles(" + boolean + ") = " + flooredBoolean);
+}
+
+function uselessMathFloor(value)
+{
+    return Math.floor(value|0);
+}
+noInline(uselessMathFloor);
+
+for (var i = 0; i < 1e4; ++i) {
+    var flooredValue = uselessMathFloor(i);
+    if (flooredValue !== i)
+        throw new Error("uselessMathFloor(" + i + ") = " + flooredValue);
+
+    var doubleLow = i + 0.4;
+    var flooredValue = uselessMathFloor(doubleLow);
+    if (flooredValue !== i)
+        throw new Error("uselessMathFloor(" + doubleLow + ") = " + flooredValue);
+
+    var doubleHigh = i + 0.6;
+    var flooredValue = uselessMathFloor(doubleHigh);
+    if (flooredValue !== i)
+        throw new Error("uselessMathFloor(" + doubleHigh + ") = " + flooredValue);
+
+    var doubleMid = i + 0.5;
+    var flooredValue = uselessMathFloor(doubleMid);
+    if (flooredValue !== i)
+        throw new Error("uselessMathFloor(" + doubleMid + ") = " + flooredValue);
+
+    var flooredValue = uselessMathFloor(-0.4);
+    if (flooredValue !== 0)
+        throw new Error("uselessMathFloor(-0.4) = " + flooredValue);
+
+    var flooredValue = uselessMathFloor(-0.5);
+    if (flooredValue !== 0)
+        throw new Error("uselessMathFloor(-0.5) = " + flooredValue);
+
+    var flooredValue = uselessMathFloor(-0.6);
+    if (flooredValue !== 0)
+        throw new Error("uselessMathFloor(-0.6) = " + flooredValue);
+}
+
+function mathFloorWithOverflow(value)
+{
+    return Math.floor(value);
+}
+noInline(mathFloorWithOverflow);
+
+for (var i = 0; i < 1e4; ++i) {
+    var bigValue = 1000000000000;
+    var flooredValue = mathFloorWithOverflow(bigValue);
+    if (flooredValue !== bigValue)
+        throw new Error("mathFloorWithOverflow(" + bigValue + ") = " + flooredValue);
+}
+
+function mathFloorConsumedAsDouble(value)
+{
+    return Math.floor(value) * 0.5;
+}
+noInline(mathFloorConsumedAsDouble);
+
+for (var i = 0; i < 1e4; ++i) {
+    var doubleValue = i + 0.1;
+    var flooredValue = mathFloorConsumedAsDouble(doubleValue);
+    if (flooredValue !== (i * 0.5))
+        throw new Error("mathFloorConsumedAsDouble(" + doubleValue + ") = " + flooredValue);
+
+    var doubleValue = i + 0.6;
+    var flooredValue = mathFloorConsumedAsDouble(doubleValue);
+    if (flooredValue !== (i * 0.5))
+        throw new Error("mathFloorConsumedAsDouble(" + doubleValue + ") = " + flooredValue);
+
+}
+
+function mathFloorDoesNotCareAboutMinusZero(value)
+{
+    return Math.floor(value)|0;
+}
+noInline(mathFloorDoesNotCareAboutMinusZero);
+
+for (var i = 0; i < 1e4; ++i) {
+    var doubleMid = i + 0.5;
+    var flooredValue = mathFloorDoesNotCareAboutMinusZero(doubleMid);
+    if (flooredValue !== i)
+        throw new Error("mathFloorDoesNotCareAboutMinusZero(" + doubleMid + ") = " + flooredValue);
+}
+
+
+// *** Function arguments. ***
+function mathFloorNoArguments()
+{
+    return Math.floor();
+}
+noInline(mathFloorNoArguments);
+
+function mathFloorTooManyArguments(a, b, c)
+{
+    return Math.floor(a, b, c);
+}
+noInline(mathFloorTooManyArguments);
+
+for (var i = 0; i < 1e4; ++i) {
+    var value = mathFloorNoArguments();
+    if (value === value)
+        throw new Error("mathFloorNoArguments() = " + value);
+
+    var value = mathFloorTooManyArguments(2.1, 3, 5);
+    if (value !== 2)
+        throw new Error("mathFloorTooManyArguments() = " + value);
+}
+
+
+// *** Constant as arguments. ***
+function testMathFloorOnConstants()
+{
+    var value = Math.floor(0);
+    if (value !== 0)
+        throw new Error("Math.floor(0) = " + value);
+    var value = Math.floor(-0);
+    if (!(value === 0 && (1/value) === -Infinity))
+        throw new Error("Math.floor(-0) = " + value);
+    var value = Math.floor(1);
+    if (value !== 1)
+        throw new Error("Math.floor(1) = " + value);
+    var value = Math.floor(-1);
+    if (value !== -1)
+        throw new Error("Math.floor(-1) = " + value);
+    var value = Math.floor(42);
+    if (value !== 42)
+        throw new Error("Math.floor(42) = " + value);
+    var value = Math.floor(-42.2);
+    if (value !== -43)
+        throw new Error("Math.floor(-42.2) = " + value);
+    var value = Math.floor(NaN);
+    if (value === value)
+        throw new Error("Math.floor(NaN) = " + value);
+    var value = Math.floor(Number.POSITIVE_INFINITI);
+    if (value === value)
+        throw new Error("Math.floor(Number.POSITIVE_INFINITI) = " + value);
+    var value = Math.floor(Number.NEGATIVE_INFINITI);
+    if (value === value)
+        throw new Error("Math.floor(Number.NEGATIVE_INFINITI) = " + value);
+    var value = Math.floor(Math.E);
+    if (value !== 2)
+        throw new Error("Math.floor(Math.E) = " + value);
+}
+noInline(testMathFloorOnConstants);
+
+for (var i = 0; i < 1e4; ++i) {
+    testMathFloorOnConstants();
+}
+
+
+// *** Struct transition. ***
+function mathFloorStructTransition(value)
+{
+    return Math.floor(value);
+}
+noInline(mathFloorStructTransition);
+
+for (var i = 0; i < 1e4; ++i) {
+    var value = mathFloorStructTransition(42.5);
+    if (value !== 42)
+        throw new Error("mathFloorStructTransition(42.5) = " + value);
+}
+
+Math.floor = function() { return arguments[0] + 5; }
+
+var value = mathFloorStructTransition(42);
+if (value !== 47)
+    throw new Error("mathFloorStructTransition(42) after transition = " + value);