Add Above/Below comparisons for UInt32 patterns
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Sep 2017 18:51:12 +0000 (18:51 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Sep 2017 18:51:12 +0000 (18:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177281

Reviewed by Saam Barati.

JSTests:

* stress/uint32-comparison-jump.js: Added.
(shouldBe):
(above):
(aboveOrEqual):
(below):
(belowOrEqual):
(notAbove):
(notAboveOrEqual):
(notBelow):
(notBelowOrEqual):
* stress/uint32-comparison.js: Added.
(shouldBe):
(above):
(aboveOrEqual):
(below):
(belowOrEqual):
(aboveTest):
(aboveOrEqualTest):
(belowTest):
(belowOrEqualTest):

Source/JavaScriptCore:

Sometimes, we would like to have UInt32 operations in JS. While VM does
not support UInt32 nicely, VM supports efficient Int32 operations. As long
as signedness does not matter, we can just perform Int32 operations instead
and recognize its bit pattern as UInt32.

But of course, some operations respect signedness. The most frequently
used one is comparison. Octane/zlib performs UInt32 comparison by performing
`val >>> 0`. It emits op_urshift and op_unsigned. op_urshift produces
UInt32 in Int32 form. And op_unsigned will generate Double value if
the generated Int32 is < 0 (which should be UInt32).

There is a chance for optimization. The given code pattern is the following.

    op_unsigned(op_urshift(@1)) lessThan:< op_unsigned(op_urshift(@2))

This can be converted to the following.

    op_urshift(@1) below:< op_urshift(@2)

The above conversion is nice since

1. We can avoid op_unsigned. This could be unsignedness check in DFG. Since
this check depends on the value of Int32, dropping this check is not as easy as
removing Int32 edge filters.

2. We can perform unsigned comparison in Int32 form. We do not need to convert
them to DoubleRep.

Since the above comparison exists in Octane/zlib's *super* hot path, dropping
op_unsigned offers huge win.

At first, my patch attempts to convert the above thing in DFG pipeline.
However it poses several problems.

1. MovHint is not well removed. It makes UInt32ToNumber (which is for op_unsigned) live.
2. UInt32ToNumber could cause an OSR exit. So if we have the following nodes,

    2: UInt32ToNumber(@0)
    3: MovHint(@2, xxx)
    4: UInt32ToNumber(@1)
    5: MovHint(@1, xxx)

we could drop @5's MovHint. But @3 is difficult since @4 can exit.

So, instead, we start introducing a simple optimization in the bytecode compiler.
It performs pattern matching for op_urshift and comparison to drop op_unsigned.
We adds op_below and op_above families to bytecodes. They only accept Int32 and
perform unsigned comparison.

This offers 4% performance improvement in Octane/zlib.

                            baseline                  patched

zlib           x2     431.07483+-16.28434       414.33407+-9.38375         might be 1.0404x faster

* bytecode/BytecodeDumper.cpp:
(JSC::BytecodeDumper<Block>::printCompareJump):
(JSC::BytecodeDumper<Block>::dumpBytecode):
* bytecode/BytecodeDumper.h:
* bytecode/BytecodeList.json:
* bytecode/BytecodeUseDef.h:
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecode/Opcode.h:
(JSC::isBranch):
* bytecode/PreciseJumpTargetsInlines.h:
(JSC::extractStoredJumpTargetsForBytecodeOffset):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitJumpIfTrue):
(JSC::BytecodeGenerator::emitJumpIfFalse):
* bytecompiler/NodesCodegen.cpp:
(JSC::BinaryOpNode::emitBytecode):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGIntegerRangeOptimizationPhase.cpp:
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileCompareUnsigned):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode):
* dfg/DFGValidate.cpp:
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileCompareBelow):
(JSC::FTL::DFG::LowerDFGToB3::compileCompareBelowEq):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* jit/JIT.h:
* jit/JITArithmetic.cpp:
(JSC::JIT::emit_op_below):
(JSC::JIT::emit_op_beloweq):
(JSC::JIT::emit_op_jbelow):
(JSC::JIT::emit_op_jbeloweq):
(JSC::JIT::emit_compareUnsignedAndJump):
(JSC::JIT::emit_compareUnsigned):
* jit/JITArithmetic32_64.cpp:
(JSC::JIT::emit_compareUnsignedAndJump):
(JSC::JIT::emit_compareUnsigned):
* llint/LowLevelInterpreter.asm:
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* parser/Nodes.h:
(JSC::ExpressionNode::isBinaryOpNode const):

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

38 files changed:
JSTests/ChangeLog
JSTests/stress/uint32-comparison-jump.js [new file with mode: 0644]
JSTests/stress/uint32-comparison.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/BytecodeDumper.cpp
Source/JavaScriptCore/bytecode/BytecodeDumper.h
Source/JavaScriptCore/bytecode/BytecodeList.json
Source/JavaScriptCore/bytecode/BytecodeUseDef.h
Source/JavaScriptCore/bytecode/Opcode.h
Source/JavaScriptCore/bytecode/PreciseJumpTargetsInlines.h
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGCapabilities.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
Source/JavaScriptCore/dfg/DFGValidate.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JIT.cpp
Source/JavaScriptCore/jit/JIT.h
Source/JavaScriptCore/jit/JITArithmetic.cpp
Source/JavaScriptCore/jit/JITArithmetic32_64.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter.asm
Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
Source/JavaScriptCore/parser/Nodes.h

index 5a7b02d..c62fedb 100644 (file)
@@ -1,3 +1,31 @@
+2017-09-27  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Add Above/Below comparisons for UInt32 patterns
+        https://bugs.webkit.org/show_bug.cgi?id=177281
+
+        Reviewed by Saam Barati.
+
+        * stress/uint32-comparison-jump.js: Added.
+        (shouldBe):
+        (above):
+        (aboveOrEqual):
+        (below):
+        (belowOrEqual):
+        (notAbove):
+        (notAboveOrEqual):
+        (notBelow):
+        (notBelowOrEqual):
+        * stress/uint32-comparison.js: Added.
+        (shouldBe):
+        (above):
+        (aboveOrEqual):
+        (below):
+        (belowOrEqual):
+        (aboveTest):
+        (aboveOrEqualTest):
+        (belowTest):
+        (belowOrEqualTest):
+
 2017-09-25  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DFG] Support ArrayPush with multiple args
diff --git a/JSTests/stress/uint32-comparison-jump.js b/JSTests/stress/uint32-comparison-jump.js
new file mode 100644 (file)
index 0000000..8a6be9f
--- /dev/null
@@ -0,0 +1,141 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function above(a, trap) {
+    let result = 0;
+    for (let i = 0; (a >>> 0) > (i >>> 0); ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(above);
+
+function aboveOrEqual(a, trap) {
+    let result = 0;
+    for (let i = 0; (a >>> 0) >= (i >>> 0); ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(aboveOrEqual);
+
+function below(a, trap) {
+    let result = 0;
+    for (let i = 0; (i >>> 0) < (a >>> 0); ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(below);
+
+function belowOrEqual(a, trap) {
+    let result = 0;
+    for (let i = 0; (i >>> 0) <= (a >>> 0); ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(belowOrEqual);
+
+function notAbove(a, trap) {
+    let result = 0;
+    for (let i = 0; (a >>> 0) > (i >>> 0) && a; ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(notAbove);
+
+function notAboveOrEqual(a, trap) {
+    let result = 0;
+    for (let i = 0; (a >>> 0) >= (i >>> 0) && a; ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(notAboveOrEqual);
+
+function notBelow(a, trap) {
+    let result = 0;
+    for (let i = 0; (i >>> 0) < (a >>> 0) && a; ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(notBelow);
+
+function notBelowOrEqual(a, trap) {
+    let result = 0;
+    for (let i = 0; (i >>> 0) <= (a >>> 0) && a; ++i) {
+        result += i;
+        if (i === trap)
+            break;
+    }
+    return result;
+}
+noInline(notBelowOrEqual);
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(above(0, -1), 0);
+    shouldBe(above(20000, -1), 199990000);
+    shouldBe(above(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(aboveOrEqual(0, -1), 0);
+    shouldBe(aboveOrEqual(20000, -1), 200010000);
+    shouldBe(aboveOrEqual(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(below(0, -1), 0);
+    shouldBe(below(20000, -1), 199990000);
+    shouldBe(below(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(belowOrEqual(0, -1), 0);
+    shouldBe(belowOrEqual(20000, -1), 200010000);
+    shouldBe(belowOrEqual(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(notAbove(0, -1), 0);
+    shouldBe(notAbove(20000, -1), 199990000);
+    shouldBe(notAbove(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(notAboveOrEqual(0, -1), 0);
+    shouldBe(notAboveOrEqual(20000, -1), 200010000);
+    shouldBe(notAboveOrEqual(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(notBelow(0, -1), 0);
+    shouldBe(notBelow(20000, -1), 199990000);
+    shouldBe(notBelow(-1, 10000), 50005000);
+}
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(notBelowOrEqual(0, -1), 0);
+    shouldBe(notBelowOrEqual(20000, -1), 200010000);
+    shouldBe(notBelowOrEqual(-1, 10000), 50005000);
+}
+
diff --git a/JSTests/stress/uint32-comparison.js b/JSTests/stress/uint32-comparison.js
new file mode 100644 (file)
index 0000000..07bec6a
--- /dev/null
@@ -0,0 +1,88 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function above(a, b) {
+    return (a >>> 0) > (b >>> 0);
+}
+noInline(above);
+
+function aboveOrEqual(a, b) {
+    return (a >>> 0) >= (b >>> 0);
+}
+noInline(aboveOrEqual);
+
+function below(a, b) {
+    return (a >>> 0) < (b >>> 0);
+}
+noInline(below);
+
+function belowOrEqual(a, b) {
+    return (a >>> 0) <= (b >>> 0);
+}
+noInline(belowOrEqual);
+
+(function aboveTest() {
+    for (let i = 0; i < 1e5; ++i) {
+        shouldBe(above(0, 20), false);
+        shouldBe(above(0, 0), false);
+        shouldBe(above(0, -0), false);
+        shouldBe(above(-1, 0), true);
+        shouldBe(above(-1, -1), false);
+        shouldBe(above(-1, 1), true);
+        shouldBe(above(1, -1), false);
+        shouldBe(above(1, 0xffffffff), false);
+        shouldBe(above(0xffffffff, 0xffffffff), false);
+        shouldBe(above(-1, 0xffffffff), false);
+        shouldBe(above(-1, 0xfffffffff), false);
+    }
+}());
+
+(function aboveOrEqualTest() {
+    for (let i = 0; i < 1e5; ++i) {
+        shouldBe(aboveOrEqual(0, 20), false);
+        shouldBe(aboveOrEqual(0, 0), true);
+        shouldBe(aboveOrEqual(0, -0), true);
+        shouldBe(aboveOrEqual(-1, 0), true);
+        shouldBe(aboveOrEqual(-1, -1), true);
+        shouldBe(aboveOrEqual(-1, 1), true);
+        shouldBe(aboveOrEqual(1, -1), false);
+        shouldBe(aboveOrEqual(1, 0xffffffff), false);
+        shouldBe(aboveOrEqual(0xffffffff, 0xffffffff), true);
+        shouldBe(aboveOrEqual(-1, 0xffffffff), true);
+        shouldBe(aboveOrEqual(-1, 0xfffffffff), true);
+    }
+}());
+
+(function belowTest() {
+    for (let i = 0; i < 1e5; ++i) {
+        shouldBe(below(0, 20), true);
+        shouldBe(below(0, 0), false);
+        shouldBe(below(0, -0), false);
+        shouldBe(below(-1, 0), false);
+        shouldBe(below(-1, -1), false);
+        shouldBe(below(-1, 1), false);
+        shouldBe(below(1, -1), true);
+        shouldBe(below(1, 0xffffffff), true);
+        shouldBe(below(0xffffffff, 0xffffffff), false);
+        shouldBe(below(-1, 0xffffffff), false);
+        shouldBe(below(-1, 0xfffffffff), false);
+    }
+}());
+
+(function belowOrEqualTest() {
+    for (let i = 0; i < 1e5; ++i) {
+        shouldBe(belowOrEqual(0, 20), true);
+        shouldBe(belowOrEqual(0, 0), true);
+        shouldBe(belowOrEqual(0, -0), true);
+        shouldBe(belowOrEqual(-1, 0), false);
+        shouldBe(belowOrEqual(-1, -1), true);
+        shouldBe(belowOrEqual(-1, 1), false);
+        shouldBe(belowOrEqual(1, -1), true);
+        shouldBe(belowOrEqual(1, 0xffffffff), true);
+        shouldBe(belowOrEqual(0xffffffff, 0xffffffff), true);
+        shouldBe(belowOrEqual(-1, 0xffffffff), true);
+        shouldBe(belowOrEqual(-1, 0xfffffffff), true);
+    }
+}());
index 282338e..f5a2bec 100644 (file)
@@ -1,3 +1,134 @@
+2017-09-27  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Add Above/Below comparisons for UInt32 patterns
+        https://bugs.webkit.org/show_bug.cgi?id=177281
+
+        Reviewed by Saam Barati.
+
+        Sometimes, we would like to have UInt32 operations in JS. While VM does
+        not support UInt32 nicely, VM supports efficient Int32 operations. As long
+        as signedness does not matter, we can just perform Int32 operations instead
+        and recognize its bit pattern as UInt32.
+
+        But of course, some operations respect signedness. The most frequently
+        used one is comparison. Octane/zlib performs UInt32 comparison by performing
+        `val >>> 0`. It emits op_urshift and op_unsigned. op_urshift produces
+        UInt32 in Int32 form. And op_unsigned will generate Double value if
+        the generated Int32 is < 0 (which should be UInt32).
+
+        There is a chance for optimization. The given code pattern is the following.
+
+            op_unsigned(op_urshift(@1)) lessThan:< op_unsigned(op_urshift(@2))
+
+        This can be converted to the following.
+
+            op_urshift(@1) below:< op_urshift(@2)
+
+        The above conversion is nice since
+
+        1. We can avoid op_unsigned. This could be unsignedness check in DFG. Since
+        this check depends on the value of Int32, dropping this check is not as easy as
+        removing Int32 edge filters.
+
+        2. We can perform unsigned comparison in Int32 form. We do not need to convert
+        them to DoubleRep.
+
+        Since the above comparison exists in Octane/zlib's *super* hot path, dropping
+        op_unsigned offers huge win.
+
+        At first, my patch attempts to convert the above thing in DFG pipeline.
+        However it poses several problems.
+
+        1. MovHint is not well removed. It makes UInt32ToNumber (which is for op_unsigned) live.
+        2. UInt32ToNumber could cause an OSR exit. So if we have the following nodes,
+
+            2: UInt32ToNumber(@0)
+            3: MovHint(@2, xxx)
+            4: UInt32ToNumber(@1)
+            5: MovHint(@1, xxx)
+
+        we could drop @5's MovHint. But @3 is difficult since @4 can exit.
+
+        So, instead, we start introducing a simple optimization in the bytecode compiler.
+        It performs pattern matching for op_urshift and comparison to drop op_unsigned.
+        We adds op_below and op_above families to bytecodes. They only accept Int32 and
+        perform unsigned comparison.
+
+        This offers 4% performance improvement in Octane/zlib.
+
+                                    baseline                  patched
+
+        zlib           x2     431.07483+-16.28434       414.33407+-9.38375         might be 1.0404x faster
+
+        * bytecode/BytecodeDumper.cpp:
+        (JSC::BytecodeDumper<Block>::printCompareJump):
+        (JSC::BytecodeDumper<Block>::dumpBytecode):
+        * bytecode/BytecodeDumper.h:
+        * bytecode/BytecodeList.json:
+        * bytecode/BytecodeUseDef.h:
+        (JSC::computeUsesForBytecodeOffset):
+        (JSC::computeDefsForBytecodeOffset):
+        * bytecode/Opcode.h:
+        (JSC::isBranch):
+        * bytecode/PreciseJumpTargetsInlines.h:
+        (JSC::extractStoredJumpTargetsForBytecodeOffset):
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitJumpIfTrue):
+        (JSC::BytecodeGenerator::emitJumpIfFalse):
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::BinaryOpNode::emitBytecode):
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGCapabilities.cpp:
+        (JSC::DFG::capabilityLevel):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGIntegerRangeOptimizationPhase.cpp:
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileCompareUnsigned):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGStrengthReductionPhase.cpp:
+        (JSC::DFG::StrengthReductionPhase::handleNode):
+        * dfg/DFGValidate.cpp:
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileCompareBelow):
+        (JSC::FTL::DFG::LowerDFGToB3::compileCompareBelowEq):
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileMainPass):
+        * jit/JIT.h:
+        * jit/JITArithmetic.cpp:
+        (JSC::JIT::emit_op_below):
+        (JSC::JIT::emit_op_beloweq):
+        (JSC::JIT::emit_op_jbelow):
+        (JSC::JIT::emit_op_jbeloweq):
+        (JSC::JIT::emit_compareUnsignedAndJump):
+        (JSC::JIT::emit_compareUnsigned):
+        * jit/JITArithmetic32_64.cpp:
+        (JSC::JIT::emit_compareUnsignedAndJump):
+        (JSC::JIT::emit_compareUnsigned):
+        * llint/LowLevelInterpreter.asm:
+        * llint/LowLevelInterpreter32_64.asm:
+        * llint/LowLevelInterpreter64.asm:
+        * parser/Nodes.h:
+        (JSC::ExpressionNode::isBinaryOpNode const):
+
 2017-09-25  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DFG] Support ArrayPush with multiple args
index 5ab7b81..9bc117f 100644 (file)
@@ -360,6 +360,16 @@ void BytecodeDumper<Block>::printConditionalJump(PrintStream& out, const typenam
 }
 
 template<class Block>
+void BytecodeDumper<Block>::printCompareJump(PrintStream& out, const typename Block::Instruction*, const typename Block::Instruction*& it, int location, const char* op)
+{
+    int r0 = (++it)->u.operand;
+    int r1 = (++it)->u.operand;
+    int offset = (++it)->u.operand;
+    printLocationAndOp(out, location, it, op);
+    out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+}
+
+template<class Block>
 void BytecodeDumper<Block>::printGetByIdOp(PrintStream& out, int location, const typename Block::Instruction*& it)
 {
     const char* op;
@@ -834,6 +844,14 @@ void BytecodeDumper<Block>::dumpBytecode(PrintStream& out, const typename Block:
         printBinaryOp(out, location, it, "greatereq");
         break;
     }
+    case op_below: {
+        printBinaryOp(out, location, it, "below");
+        break;
+    }
+    case op_beloweq: {
+        printBinaryOp(out, location, it, "beloweq");
+        break;
+    }
     case op_inc: {
         int r0 = (++it)->u.operand;
         printLocationOpAndRegisterOperand(out, location, it, "inc", r0);
@@ -1197,67 +1215,43 @@ void BytecodeDumper<Block>::dumpBytecode(PrintStream& out, const typename Block:
         break;
     }
     case op_jless: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jless");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jless");
         break;
     }
     case op_jlesseq: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jlesseq");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jlesseq");
         break;
     }
     case op_jgreater: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jgreater");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jgreater");
         break;
     }
     case op_jgreatereq: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jgreatereq");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jgreatereq");
         break;
     }
     case op_jnless: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jnless");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jnless");
         break;
     }
     case op_jnlesseq: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jnlesseq");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jnlesseq");
         break;
     }
     case op_jngreater: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jngreater");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jngreater");
         break;
     }
     case op_jngreatereq: {
-        int r0 = (++it)->u.operand;
-        int r1 = (++it)->u.operand;
-        int offset = (++it)->u.operand;
-        printLocationAndOp(out, location, it, "jngreatereq");
-        out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset);
+        printCompareJump(out, begin, it, location, "jngreatereq");
+        break;
+    }
+    case op_jbelow: {
+        printCompareJump(out, begin, it, location, "jbelow");
+        break;
+    }
+    case op_jbeloweq: {
+        printCompareJump(out, begin, it, location, "jbeloweq");
         break;
     }
     case op_loop_hint: {
index 8fe6033..7cd08c9 100644 (file)
@@ -67,6 +67,7 @@ private:
     void printUnaryOp(PrintStream& out, int location, const Instruction*& it, const char* op);
     void printBinaryOp(PrintStream& out, int location, const Instruction*& it, const char* op);
     void printConditionalJump(PrintStream& out, const Instruction*, const Instruction*& it, int location, const char* op);
+    void printCompareJump(PrintStream& out, const Instruction*, const Instruction*& it, int location, const char* op);
     void printGetByIdOp(PrintStream& out, int location, const Instruction*& it);
     void printGetByIdCacheStatus(PrintStream& out, int location, const StubInfoMap&);
     void printPutByIdCacheStatus(PrintStream& out, int location, const StubInfoMap&);
index 91270d6..94f3ffb 100644 (file)
@@ -32,6 +32,8 @@
             { "name" : "op_lesseq", "length" : 4 },
             { "name" : "op_greater", "length" : 4 },
             { "name" : "op_greatereq", "length" : 4 },
+            { "name" : "op_below", "length" : 4 },
+            { "name" : "op_beloweq", "length" : 4 },
             { "name" : "op_inc", "length" : 2 },
             { "name" : "op_dec", "length" : 2 },
             { "name" : "op_to_number", "length" : 4 },
             { "name" : "op_jnlesseq", "length" : 4 },
             { "name" : "op_jngreater", "length" : 4 },
             { "name" : "op_jngreatereq", "length" : 4 },
+            { "name" : "op_jbelow", "length" : 4 },
+            { "name" : "op_jbeloweq", "length" : 4 },
             { "name" : "op_loop_hint", "length" : 1 },
             { "name" : "op_switch_imm", "length" : 4 },
             { "name" : "op_switch_char", "length" : 4 },
index 22ed8df..9749af7 100644 (file)
@@ -85,6 +85,8 @@ void computeUsesForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, Instructi
     case op_jngreater:
     case op_jngreatereq:
     case op_jless:
+    case op_jbelow:
+    case op_jbeloweq:
     case op_set_function_name:
     case op_log_shadow_chicken_tail: {
         ASSERT(opcodeLengths[opcodeID] > 2);
@@ -237,6 +239,8 @@ void computeUsesForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, Instructi
     case op_lesseq:
     case op_greater:
     case op_greatereq:
+    case op_below:
+    case op_beloweq:
     case op_nstricteq:
     case op_stricteq:
     case op_neq:
@@ -343,6 +347,8 @@ void computeDefsForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, Instructi
     case op_jnlesseq:
     case op_jngreater:
     case op_jngreatereq:
+    case op_jbelow:
+    case op_jbeloweq:
     case op_loop_hint:
     case op_switch_imm:
     case op_switch_char:
@@ -463,6 +469,8 @@ void computeDefsForBytecodeOffset(Block* codeBlock, OpcodeID opcodeID, Instructi
     case op_lesseq:
     case op_greater:
     case op_greatereq:
+    case op_below:
+    case op_beloweq:
     case op_neq_null:
     case op_eq_null:
     case op_not:
index 79b5d7f..758d340 100644 (file)
@@ -151,6 +151,8 @@ inline bool isBranch(OpcodeID opcodeID)
     case op_jnlesseq:
     case op_jngreater:
     case op_jngreatereq:
+    case op_jbelow:
+    case op_jbeloweq:
     case op_switch_imm:
     case op_switch_char:
     case op_switch_string:
index 42ac5ed..36725f9 100644 (file)
@@ -55,6 +55,8 @@ inline void extractStoredJumpTargetsForBytecodeOffset(Block* codeBlock, Instruct
     case op_jnlesseq:
     case op_jngreater:
     case op_jngreatereq:
+    case op_jbelow:
+    case op_jbeloweq:
         function(current[3].u.operand);
         break;
     case op_switch_imm:
index 2ef3456..7f687c6 100644 (file)
@@ -1364,7 +1364,7 @@ void BytecodeGenerator::emitJump(Label& target)
 
 void BytecodeGenerator::emitJumpIfTrue(RegisterID* cond, Label& target)
 {
-    if (m_lastOpcodeID == op_less) {
+    auto fuseCompareAndJump = [&] (OpcodeID jumpID) {
         int dstIndex;
         int src1Index;
         int src2Index;
@@ -1375,63 +1375,33 @@ void BytecodeGenerator::emitJumpIfTrue(RegisterID* cond, Label& target)
             rewindBinaryOp();
 
             size_t begin = instructions().size();
-            emitOpcode(op_jless);
+            emitOpcode(jumpID);
             instructions().append(src1Index);
             instructions().append(src2Index);
             instructions().append(target.bind(begin, instructions().size()));
-            return;
+            return true;
         }
-    } else if (m_lastOpcodeID == op_lesseq) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
+        return false;
+    };
 
-            size_t begin = instructions().size();
-            emitOpcode(op_jlesseq);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+    if (m_lastOpcodeID == op_less) {
+        if (fuseCompareAndJump(op_jless))
+            return;
+    } else if (m_lastOpcodeID == op_lesseq) {
+        if (fuseCompareAndJump(op_jlesseq))
             return;
-        }
     } else if (m_lastOpcodeID == op_greater) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
-
-            size_t begin = instructions().size();
-            emitOpcode(op_jgreater);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+        if (fuseCompareAndJump(op_jgreater))
             return;
-        }
     } else if (m_lastOpcodeID == op_greatereq) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
-
-            size_t begin = instructions().size();
-            emitOpcode(op_jgreatereq);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+        if (fuseCompareAndJump(op_jgreatereq))
+            return;
+    } else if (m_lastOpcodeID == op_below) {
+        if (fuseCompareAndJump(op_jbelow))
+            return;
+    } else if (m_lastOpcodeID == op_beloweq) {
+        if (fuseCompareAndJump(op_jbeloweq))
             return;
-        }
     } else if (m_lastOpcodeID == op_eq_null && target.isForward()) {
         int dstIndex;
         int srcIndex;
@@ -1473,7 +1443,7 @@ void BytecodeGenerator::emitJumpIfTrue(RegisterID* cond, Label& target)
 
 void BytecodeGenerator::emitJumpIfFalse(RegisterID* cond, Label& target)
 {
-    if (m_lastOpcodeID == op_less && target.isForward()) {
+    auto fuseCompareAndJump = [&] (OpcodeID jumpID, bool replaceOperands) {
         int dstIndex;
         int src1Index;
         int src2Index;
@@ -1484,63 +1454,36 @@ void BytecodeGenerator::emitJumpIfFalse(RegisterID* cond, Label& target)
             rewindBinaryOp();
 
             size_t begin = instructions().size();
-            emitOpcode(op_jnless);
+            emitOpcode(jumpID);
+            // Since op_below and op_beloweq only accepts Int32, replacing operands is not observable to users.
+            if (replaceOperands)
+                std::swap(src1Index, src2Index);
             instructions().append(src1Index);
             instructions().append(src2Index);
             instructions().append(target.bind(begin, instructions().size()));
-            return;
+            return true;
         }
-    } else if (m_lastOpcodeID == op_lesseq && target.isForward()) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
+        return false;
+    };
 
-            size_t begin = instructions().size();
-            emitOpcode(op_jnlesseq);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+    if (m_lastOpcodeID == op_less && target.isForward()) {
+        if (fuseCompareAndJump(op_jnless, false))
+            return;
+    } else if (m_lastOpcodeID == op_lesseq && target.isForward()) {
+        if (fuseCompareAndJump(op_jnlesseq, false))
             return;
-        }
     } else if (m_lastOpcodeID == op_greater && target.isForward()) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
-
-            size_t begin = instructions().size();
-            emitOpcode(op_jngreater);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+        if (fuseCompareAndJump(op_jngreater, false))
             return;
-        }
     } else if (m_lastOpcodeID == op_greatereq && target.isForward()) {
-        int dstIndex;
-        int src1Index;
-        int src2Index;
-
-        retrieveLastBinaryOp(dstIndex, src1Index, src2Index);
-
-        if (cond->index() == dstIndex && cond->isTemporary() && !cond->refCount()) {
-            rewindBinaryOp();
-
-            size_t begin = instructions().size();
-            emitOpcode(op_jngreatereq);
-            instructions().append(src1Index);
-            instructions().append(src2Index);
-            instructions().append(target.bind(begin, instructions().size()));
+        if (fuseCompareAndJump(op_jngreatereq, false))
+            return;
+    } else if (m_lastOpcodeID == op_below && target.isForward()) {
+        if (fuseCompareAndJump(op_jbeloweq, true))
+            return;
+    } else if (m_lastOpcodeID == op_beloweq && target.isForward()) {
+        if (fuseCompareAndJump(op_jbelow, true))
             return;
-        }
     } else if (m_lastOpcodeID == op_not) {
         int dstIndex;
         int srcIndex;
index 0b6a94a..d428cde 100644 (file)
@@ -1981,6 +1981,64 @@ RegisterID* BinaryOpNode::emitBytecode(BytecodeGenerator& generator, RegisterID*
 {
     OpcodeID opcodeID = this->opcodeID();
 
+    if (opcodeID == op_less || opcodeID == op_lesseq || opcodeID == op_greater || opcodeID == op_greatereq) {
+        auto isUInt32 = [&] (ExpressionNode* node) -> std::optional<UInt32Result> {
+            if (node->isBinaryOpNode() && static_cast<BinaryOpNode*>(node)->opcodeID() == op_urshift)
+                return UInt32Result::UInt32;
+            if (node->isNumber() && static_cast<NumberNode*>(node)->isIntegerNode()) {
+                int32_t value = static_cast<int32_t>(static_cast<IntegerNode*>(node)->value());
+                if (value >= 0)
+                    return UInt32Result::Constant;
+            }
+            return std::nullopt;
+        };
+        auto leftResult = isUInt32(m_expr1);
+        auto rightResult = isUInt32(m_expr2);
+        if ((leftResult && rightResult) && (leftResult.value() == UInt32Result::UInt32 || rightResult.value() == UInt32Result::UInt32)) {
+            auto* left = m_expr1;
+            auto* right = m_expr2;
+            if (left->isBinaryOpNode()) {
+                ASSERT(static_cast<BinaryOpNode*>(left)->opcodeID() == op_urshift);
+                static_cast<BinaryOpNode*>(left)->m_shouldToUnsignedResult = false;
+            }
+            if (right->isBinaryOpNode()) {
+                ASSERT(static_cast<BinaryOpNode*>(right)->opcodeID() == op_urshift);
+                static_cast<BinaryOpNode*>(right)->m_shouldToUnsignedResult = false;
+            }
+            RefPtr<RegisterID> src1 = generator.emitNodeForLeftHandSide(left, m_rightHasAssignments, right->isPure(generator));
+            RefPtr<RegisterID> src2 = generator.emitNode(right);
+            generator.emitExpressionInfo(position(), position(), position());
+
+            // Since the both sides only accept Int32, replacing operands is not observable to users.
+            bool replaceOperands = false;
+            OpcodeID resultOp = opcodeID;
+            switch (opcodeID) {
+            case op_less:
+                resultOp = op_below;
+                break;
+            case op_lesseq:
+                resultOp = op_beloweq;
+                break;
+            case op_greater:
+                resultOp = op_below;
+                replaceOperands = true;
+                break;
+            case op_greatereq:
+                resultOp = op_beloweq;
+                replaceOperands = true;
+                break;
+            default:
+                RELEASE_ASSERT_NOT_REACHED();
+            }
+            OperandTypes operandTypes(left->resultDescriptor(), right->resultDescriptor());
+            if (replaceOperands) {
+                std::swap(src1, src2);
+                operandTypes = OperandTypes(right->resultDescriptor(), left->resultDescriptor());
+            }
+            return generator.emitBinaryOp(resultOp, generator.finalDestination(dst, src1.get()), src1.get(), src2.get(), operandTypes);
+        }
+    }
+
     if (opcodeID == op_add && m_expr1->isAdd() && m_expr1->resultDescriptor().definitelyIsString()) {
         generator.emitExpressionInfo(position(), position(), position());
         return emitStrcat(generator, dst);
@@ -2016,8 +2074,10 @@ RegisterID* BinaryOpNode::emitBytecode(BytecodeGenerator& generator, RegisterID*
         return generator.emitUnaryOp(op_not, generator.finalDestination(dst, tmp.get()), tmp.get());
     }
     RegisterID* result = generator.emitBinaryOp(opcodeID, generator.finalDestination(dst, src1.get()), src1.get(), src2.get(), OperandTypes(left->resultDescriptor(), right->resultDescriptor()));
-    if (opcodeID == op_urshift && dst != generator.ignoredResult())
-        return generator.emitUnaryOp(op_unsigned, result, result);
+    if (m_shouldToUnsignedResult) {
+        if (opcodeID == op_urshift && dst != generator.ignoredResult())
+            return generator.emitUnaryOp(op_unsigned, result, result);
+    }
     return result;
 }
 
index bea1689..56e5ac5 100644 (file)
@@ -1407,7 +1407,48 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         forNode(node).setType(m_graph, SpecStringIdent);
         break;
     }
-            
+
+    case CompareBelow:
+    case CompareBelowEq: {
+        JSValue leftConst = forNode(node->child1()).value();
+        JSValue rightConst = forNode(node->child2()).value();
+        if (leftConst && rightConst) {
+            if (leftConst.isInt32() && rightConst.isInt32()) {
+                uint32_t a = static_cast<uint32_t>(leftConst.asInt32());
+                uint32_t b = static_cast<uint32_t>(rightConst.asInt32());
+                switch (node->op()) {
+                case CompareBelow:
+                    setConstant(node, jsBoolean(a < b));
+                    break;
+                case CompareBelowEq:
+                    setConstant(node, jsBoolean(a <= b));
+                    break;
+                default:
+                    RELEASE_ASSERT_NOT_REACHED();
+                    break;
+                }
+                break;
+            }
+        }
+
+        if (node->child1() == node->child2()) {
+            switch (node->op()) {
+            case CompareBelow:
+                setConstant(node, jsBoolean(false));
+                break;
+            case CompareBelowEq:
+                setConstant(node, jsBoolean(true));
+                break;
+            default:
+                DFG_CRASH(m_graph, node, "Unexpected node type");
+                break;
+            }
+            break;
+        }
+        forNode(node).setType(SpecBoolean);
+        break;
+    }
+
     case CompareLess:
     case CompareLessEq:
     case CompareGreater:
index 30f4d1d..af528e1 100644 (file)
@@ -4753,6 +4753,20 @@ bool ByteCodeParser::parseBlock(unsigned limit)
             NEXT_OPCODE(op_greatereq);
         }
 
+        case op_below: {
+            Node* op1 = get(VirtualRegister(currentInstruction[2].u.operand));
+            Node* op2 = get(VirtualRegister(currentInstruction[3].u.operand));
+            set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(CompareBelow, op1, op2));
+            NEXT_OPCODE(op_below);
+        }
+
+        case op_beloweq: {
+            Node* op1 = get(VirtualRegister(currentInstruction[2].u.operand));
+            Node* op2 = get(VirtualRegister(currentInstruction[3].u.operand));
+            set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(CompareBelowEq, op1, op2));
+            NEXT_OPCODE(op_beloweq);
+        }
+
         case op_eq: {
             Node* op1 = get(VirtualRegister(currentInstruction[2].u.operand));
             Node* op2 = get(VirtualRegister(currentInstruction[3].u.operand));
@@ -5198,7 +5212,25 @@ bool ByteCodeParser::parseBlock(unsigned limit)
             addToGraph(Branch, OpInfo(branchData(m_currentIndex + OPCODE_LENGTH(op_jngreatereq), m_currentIndex + relativeOffset)), condition);
             LAST_OPCODE(op_jngreatereq);
         }
-            
+
+        case op_jbelow: {
+            unsigned relativeOffset = currentInstruction[3].u.operand;
+            Node* op1 = get(VirtualRegister(currentInstruction[1].u.operand));
+            Node* op2 = get(VirtualRegister(currentInstruction[2].u.operand));
+            Node* condition = addToGraph(CompareBelow, op1, op2);
+            addToGraph(Branch, OpInfo(branchData(m_currentIndex + relativeOffset, m_currentIndex + OPCODE_LENGTH(op_jbelow))), condition);
+            LAST_OPCODE(op_jbelow);
+        }
+
+        case op_jbeloweq: {
+            unsigned relativeOffset = currentInstruction[3].u.operand;
+            Node* op1 = get(VirtualRegister(currentInstruction[1].u.operand));
+            Node* op2 = get(VirtualRegister(currentInstruction[2].u.operand));
+            Node* condition = addToGraph(CompareBelowEq, op1, op2);
+            addToGraph(Branch, OpInfo(branchData(m_currentIndex + relativeOffset, m_currentIndex + OPCODE_LENGTH(op_jbeloweq))), condition);
+            LAST_OPCODE(op_jbeloweq);
+        }
+
         case op_switch_imm: {
             SwitchData& data = *m_graph.m_switchData.add();
             data.kind = SwitchImm;
index 424a63b..a004949 100644 (file)
@@ -151,6 +151,8 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, Instruc
     case op_lesseq:
     case op_greater:
     case op_greatereq:
+    case op_below:
+    case op_beloweq:
     case op_eq:
     case op_eq_null:
     case op_stricteq:
@@ -192,6 +194,8 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, Instruc
     case op_jnlesseq:
     case op_jngreater:
     case op_jngreatereq:
+    case op_jbelow:
+    case op_jbeloweq:
     case op_loop_hint:
     case op_check_traps:
     case op_nop:
index 566490b..68ab243 100644 (file)
@@ -1486,6 +1486,11 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         }
         def(PureValue(node));
         return;
+
+    case CompareBelow:
+    case CompareBelowEq:
+        def(PureValue(node));
+        return;
         
     case CompareEq:
     case CompareLess:
index 58a75f5..b44d778 100644 (file)
@@ -143,6 +143,8 @@ bool doesGC(Graph& graph, Node* node)
     case CompareLessEq:
     case CompareGreater:
     case CompareGreaterEq:
+    case CompareBelow:
+    case CompareBelowEq:
     case CompareEq:
     case CompareStrictEq:
     case CompareEqPtr:
index 098ded3..fae8fdf 100644 (file)
@@ -142,7 +142,6 @@ private:
                 node->setArithMode(Arith::CheckOverflow);
             else {
                 node->setArithMode(Arith::DoOverflow);
-                node->clearFlags(NodeMustGenerate);
                 node->setResult(enableInt52() ? NodeResultInt52 : NodeResultDouble);
             }
             break;
@@ -1598,6 +1597,13 @@ private:
             break;
         }
 
+        case CompareBelow:
+        case CompareBelowEq: {
+            fixEdge<Int32Use>(node->child1());
+            fixEdge<Int32Use>(node->child2());
+            break;
+        }
+
         case Phi:
         case Upsilon:
         case EntrySwitch:
index 4a3831e..fb3f4d9 100644 (file)
@@ -1140,6 +1140,7 @@ public:
                         relationshipForTrue = Relationship::safeCreate(
                             terminal->child1().node(), m_zero, Relationship::NotEqual, 0);
                     } else {
+                        // FIXME: Handle CompareBelow and CompareBelowEq.
                         Node* compare = terminal->child1().node();
                         switch (compare->op()) {
                         case CompareEq:
index 6dcec24..354ad37 100644 (file)
@@ -121,7 +121,7 @@ namespace JSC { namespace DFG {
     /* Bitwise operators call ToInt32 on their operands. */\
     macro(ValueToInt32, NodeResultInt32) \
     /* Used to box the result of URShift nodes (result has range 0..2^32-1). */\
-    macro(UInt32ToNumber, NodeResultNumber | NodeMustGenerate) \
+    macro(UInt32ToNumber, NodeResultNumber) \
     /* Converts booleans to numbers but passes everything else through. */\
     macro(BooleanToNumber, NodeResultJS) \
     \
@@ -286,6 +286,8 @@ namespace JSC { namespace DFG {
     macro(CompareLessEq, NodeResultBoolean | NodeMustGenerate) \
     macro(CompareGreater, NodeResultBoolean | NodeMustGenerate) \
     macro(CompareGreaterEq, NodeResultBoolean | NodeMustGenerate) \
+    macro(CompareBelow, NodeResultBoolean) \
+    macro(CompareBelowEq, NodeResultBoolean) \
     macro(CompareEq, NodeResultBoolean | NodeMustGenerate) \
     macro(CompareStrictEq, NodeResultBoolean) \
     macro(CompareEqPtr, NodeResultBoolean) \
index bb3a11f..d64154f 100644 (file)
@@ -818,6 +818,8 @@ private:
         case CompareLessEq:
         case CompareGreater:
         case CompareGreaterEq:
+        case CompareBelow:
+        case CompareBelowEq:
         case CompareEq:
         case CompareStrictEq:
         case CompareEqPtr:
index 2e54cb0..5c71d62 100644 (file)
@@ -266,6 +266,8 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case CompareLessEq:
     case CompareGreater:
     case CompareGreaterEq:
+    case CompareBelow:
+    case CompareBelowEq:
     case CompareEq:
     case CompareStrictEq:
     case CompareEqPtr:
index 0eaaac1..d1dcb15 100644 (file)
@@ -5710,6 +5710,11 @@ bool SpeculativeJIT::compare(Node* node, MacroAssembler::RelationalCondition con
     return false;
 }
 
+void SpeculativeJIT::compileCompareUnsigned(Node* node, MacroAssembler::RelationalCondition condition)
+{
+    compileInt32Compare(node, condition);
+}
+
 bool SpeculativeJIT::compileStrictEq(Node* node)
 {
     // FIXME: Currently, we have op_jless, op_jgreater etc. But we don't have op_jeq, op_jstricteq etc.
index 7f81b54..11276bf 100644 (file)
@@ -2740,6 +2740,7 @@ public:
     }
     
     bool compare(Node*, MacroAssembler::RelationalCondition, MacroAssembler::DoubleCondition, S_JITOperation_EJJ);
+    void compileCompareUnsigned(Node*, MacroAssembler::RelationalCondition);
     bool compilePeepHoleBranch(Node*, MacroAssembler::RelationalCondition, MacroAssembler::DoubleCondition, S_JITOperation_EJJ);
     void compilePeepHoleInt32Branch(Node*, Node* branchNode, JITCompiler::RelationalCondition);
     void compilePeepHoleInt52Branch(Node*, Node* branchNode, JITCompiler::RelationalCondition);
index 43d0dac..a23b8a1 100644 (file)
@@ -2522,6 +2522,14 @@ void SpeculativeJIT::compile(Node* node)
             return;
         break;
 
+    case CompareBelow:
+        compileCompareUnsigned(node, JITCompiler::Below);
+        break;
+
+    case CompareBelowEq:
+        compileCompareUnsigned(node, JITCompiler::BelowOrEqual);
+        break;
+
     case CompareEq:
         if (compare(node, JITCompiler::Equal, JITCompiler::DoubleEqual, operationCompareEq))
             return;
index 516caa7..da01a25 100644 (file)
@@ -2664,6 +2664,14 @@ void SpeculativeJIT::compile(Node* node)
             return;
         break;
 
+    case CompareBelow:
+        compileCompareUnsigned(node, JITCompiler::Below);
+        break;
+
+    case CompareBelowEq:
+        compileCompareUnsigned(node, JITCompiler::BelowOrEqual);
+        break;
+
     case CompareEq:
         if (compare(node, JITCompiler::Equal, JITCompiler::DoubleEqual, operationCompareEq))
             return;
index f915841..9437a3b 100644 (file)
@@ -75,6 +75,8 @@ public:
     }
 
 private:
+    enum class UInt32Result { UInt32, Constant };
+
     void handleNode()
     {
         switch (m_node->op()) {
@@ -265,6 +267,66 @@ private:
             }
             break;
         }
+
+        case CompareEq:
+        case CompareLess:
+        case CompareLessEq:
+        case CompareGreater:
+        case CompareGreaterEq: {
+            if (m_node->isBinaryUseKind(Int32Use)) {
+                auto isUInt32 = [&] (Edge& edge) -> std::optional<UInt32Result> {
+                    if (edge->op() == UInt32ToNumber)
+                        return UInt32Result::UInt32;
+                    if (edge->isInt32Constant()) {
+                        if (edge->asInt32() >= 0)
+                            return UInt32Result::Constant;
+                    }
+                    return std::nullopt;
+                };
+
+                auto child1Result = isUInt32(m_node->child1());
+                auto child2Result = isUInt32(m_node->child2());
+                if ((child1Result && child2Result) && (child1Result.value() == UInt32Result::UInt32 || child2Result.value() == UInt32Result::UInt32)) {
+                    NodeType op = m_node->op();
+                    bool replaceOperands = false;
+                    switch (m_node->op()) {
+                    case CompareEq:
+                        op = CompareEq;
+                        break;
+                    case CompareLess:
+                        op = CompareBelow;
+                        break;
+                    case CompareLessEq:
+                        op = CompareBelowEq;
+                        break;
+                    case CompareGreater:
+                        op = CompareBelow;
+                        replaceOperands = true;
+                        break;
+                    case CompareGreaterEq:
+                        op = CompareBelowEq;
+                        replaceOperands = true;
+                        break;
+                    default:
+                        RELEASE_ASSERT_NOT_REACHED();
+                    }
+
+                    auto extractChild = [&] (Edge& edge) {
+                        if (edge->op() == UInt32ToNumber)
+                            return edge->child1().node();
+                        return edge.node();
+                    };
+
+                    m_node->setOp(op);
+                    m_node->child1() = Edge(extractChild(m_node->child1()), Int32Use);
+                    m_node->child2() = Edge(extractChild(m_node->child2()), Int32Use);
+                    if (replaceOperands)
+                        std::swap(m_node->child1(), m_node->child2());
+                    m_changed = true;
+                }
+            }
+            break;
+        }
             
         case Flush: {
             ASSERT(m_graph.m_form != SSA);
index c9ab2b4..64386b6 100644 (file)
@@ -267,6 +267,8 @@ public:
                 case CompareLessEq:
                 case CompareGreater:
                 case CompareGreaterEq:
+                case CompareBelow:
+                case CompareBelowEq:
                 case CompareEq:
                 case CompareStrictEq:
                 case StrCat:
index 1309e9e..6926e1e 100644 (file)
@@ -284,6 +284,8 @@ inline CapabilityLevel canCompile(Node* node)
     case CompareLessEq:
     case CompareGreater:
     case CompareGreaterEq:
+    case CompareBelow:
+    case CompareBelowEq:
     case CompareStrictEq:
     case DefineDataProperty:
     case DefineAccessorProperty:
index c7934e3..0f12a37 100644 (file)
@@ -932,6 +932,12 @@ private:
         case CompareGreaterEq:
             compileCompareGreaterEq();
             break;
+        case CompareBelow:
+            compileCompareBelow();
+            break;
+        case CompareBelowEq:
+            compileCompareBelowEq();
+            break;
         case CompareEqPtr:
             compileCompareEqPtr();
             break;
@@ -6404,6 +6410,16 @@ private:
             operationCompareStringGreaterEq,
             operationCompareGreaterEq);
     }
+
+    void compileCompareBelow()
+    {
+        setBoolean(m_out.below(lowInt32(m_node->child1()), lowInt32(m_node->child2())));
+    }
+    
+    void compileCompareBelowEq()
+    {
+        setBoolean(m_out.belowOrEqual(lowInt32(m_node->child1()), lowInt32(m_node->child2())));
+    }
     
     void compileLogicalNot()
     {
index 5ecf3a6..4a4bb8d 100644 (file)
@@ -301,6 +301,8 @@ void JIT::privateCompileMainPass()
         DEFINE_OP(op_get_scope)
         DEFINE_OP(op_eq)
         DEFINE_OP(op_eq_null)
+        DEFINE_OP(op_below)
+        DEFINE_OP(op_beloweq)
         DEFINE_OP(op_try_get_by_id)
         case op_get_array_length:
         case op_get_by_id_proto_load:
@@ -331,6 +333,8 @@ void JIT::privateCompileMainPass()
         DEFINE_OP(op_jnlesseq)
         DEFINE_OP(op_jngreater)
         DEFINE_OP(op_jngreatereq)
+        DEFINE_OP(op_jbelow)
+        DEFINE_OP(op_jbeloweq)
         DEFINE_OP(op_jtrue)
         DEFINE_OP(op_loop_hint)
         DEFINE_OP(op_check_traps)
index 7cbae35..6d086c4 100644 (file)
@@ -460,6 +460,8 @@ namespace JSC {
 #endif // USE(JSVALUE32_64)
 
         void emit_compareAndJump(OpcodeID, int op1, int op2, unsigned target, RelationalCondition);
+        void emit_compareUnsigned(int dst, int op1, int op2, RelationalCondition);
+        void emit_compareUnsignedAndJump(int op1, int op2, unsigned target, RelationalCondition);
         void emit_compareAndJumpSlow(int op1, int op2, unsigned target, DoubleCondition, size_t (JIT_OPERATION *operation)(ExecState*, EncodedJSValue, EncodedJSValue), bool invert, Vector<SlowCaseEntry>::iterator&);
         
         void assertStackPointerOffset();
@@ -499,6 +501,8 @@ namespace JSC {
         void emit_op_get_scope(Instruction*);
         void emit_op_eq(Instruction*);
         void emit_op_eq_null(Instruction*);
+        void emit_op_below(Instruction*);
+        void emit_op_beloweq(Instruction*);
         void emit_op_try_get_by_id(Instruction*);
         void emit_op_get_by_id(Instruction*);
         void emit_op_get_by_id_with_this(Instruction*);
@@ -529,6 +533,8 @@ namespace JSC {
         void emit_op_jnlesseq(Instruction*);
         void emit_op_jngreater(Instruction*);
         void emit_op_jngreatereq(Instruction*);
+        void emit_op_jbelow(Instruction*);
+        void emit_op_jbeloweq(Instruction*);
         void emit_op_jtrue(Instruction*);
         void emit_op_loop_hint(Instruction*);
         void emit_op_check_traps(Instruction*);
index 58fdb10..c1ca275 100644 (file)
@@ -197,6 +197,40 @@ void JIT::emitSlow_op_jngreatereq(Instruction* currentInstruction, Vector<SlowCa
     emit_compareAndJumpSlow(op1, op2, target, DoubleLessThanOrUnordered, operationCompareGreaterEq, true, iter);
 }
 
+void JIT::emit_op_below(Instruction* currentInstruction)
+{
+    int dst = currentInstruction[1].u.operand;
+    int op1 = currentInstruction[2].u.operand;
+    int op2 = currentInstruction[3].u.operand;
+    emit_compareUnsigned(dst, op1, op2, Below);
+}
+
+void JIT::emit_op_beloweq(Instruction* currentInstruction)
+{
+    int dst = currentInstruction[1].u.operand;
+    int op1 = currentInstruction[2].u.operand;
+    int op2 = currentInstruction[3].u.operand;
+    emit_compareUnsigned(dst, op1, op2, BelowOrEqual);
+}
+
+void JIT::emit_op_jbelow(Instruction* currentInstruction)
+{
+    int op1 = currentInstruction[1].u.operand;
+    int op2 = currentInstruction[2].u.operand;
+    unsigned target = currentInstruction[3].u.operand;
+
+    emit_compareUnsignedAndJump(op1, op2, target, Below);
+}
+
+void JIT::emit_op_jbeloweq(Instruction* currentInstruction)
+{
+    int op1 = currentInstruction[1].u.operand;
+    int op2 = currentInstruction[2].u.operand;
+    unsigned target = currentInstruction[3].u.operand;
+
+    emit_compareUnsignedAndJump(op1, op2, target, BelowOrEqual);
+}
+
 #if USE(JSVALUE64)
 
 void JIT::emit_op_unsigned(Instruction* currentInstruction)
@@ -264,6 +298,40 @@ void JIT::emit_compareAndJump(OpcodeID, int op1, int op2, unsigned target, Relat
     }
 }
 
+void JIT::emit_compareUnsignedAndJump(int op1, int op2, unsigned target, RelationalCondition condition)
+{
+    if (isOperandConstantInt(op2)) {
+        emitGetVirtualRegister(op1, regT0);
+        int32_t op2imm = getOperandConstantInt(op2);
+        addJump(branch32(condition, regT0, Imm32(op2imm)), target);
+    } else if (isOperandConstantInt(op1)) {
+        emitGetVirtualRegister(op2, regT1);
+        int32_t op1imm = getOperandConstantInt(op1);
+        addJump(branch32(commute(condition), regT1, Imm32(op1imm)), target);
+    } else {
+        emitGetVirtualRegisters(op1, regT0, op2, regT1);
+        addJump(branch32(condition, regT0, regT1), target);
+    }
+}
+
+void JIT::emit_compareUnsigned(int dst, int op1, int op2, RelationalCondition condition)
+{
+    if (isOperandConstantInt(op2)) {
+        emitGetVirtualRegister(op1, regT0);
+        int32_t op2imm = getOperandConstantInt(op2);
+        compare32(condition, regT0, Imm32(op2imm), regT0);
+    } else if (isOperandConstantInt(op1)) {
+        emitGetVirtualRegister(op2, regT0);
+        int32_t op1imm = getOperandConstantInt(op1);
+        compare32(commute(condition), regT0, Imm32(op1imm), regT0);
+    } else {
+        emitGetVirtualRegisters(op1, regT0, op2, regT1);
+        compare32(condition, regT0, regT1, regT0);
+    }
+    emitTagBool(regT0);
+    emitPutVirtualRegister(dst);
+}
+
 void JIT::emit_compareAndJumpSlow(int op1, int op2, unsigned target, DoubleCondition condition, size_t (JIT_OPERATION *operation)(ExecState*, EncodedJSValue, EncodedJSValue), bool invert, Vector<SlowCaseEntry>::iterator& iter)
 {
     COMPILE_ASSERT(OPCODE_LENGTH(op_jless) == OPCODE_LENGTH(op_jlesseq), OPCODE_LENGTH_op_jlesseq_equals_op_jless);
index d271d48..0e187c8 100644 (file)
@@ -92,6 +92,36 @@ void JIT::emit_compareAndJump(OpcodeID opcode, int op1, int op2, unsigned target
     end.link(this);
 }
 
+void JIT::emit_compareUnsignedAndJump(int op1, int op2, unsigned target, RelationalCondition condition)
+{
+    if (isOperandConstantInt(op1)) {
+        emitLoad(op2, regT3, regT2);
+        addJump(branch32(commute(condition), regT2, Imm32(getConstantOperand(op1).asInt32())), target);
+    } else if (isOperandConstantInt(op2)) {
+        emitLoad(op1, regT1, regT0);
+        addJump(branch32(condition, regT0, Imm32(getConstantOperand(op2).asInt32())), target);
+    } else {
+        emitLoad2(op1, regT1, regT0, op2, regT3, regT2);
+        addJump(branch32(condition, regT0, regT2), target);
+    }
+}
+
+
+void JIT::emit_compareUnsigned(int dst, int op1, int op2, RelationalCondition condition)
+{
+    if (isOperandConstantInt(op1)) {
+        emitLoad(op2, regT3, regT2);
+        compare32(commute(condition), regT2, Imm32(getConstantOperand(op1).asInt32()), regT0);
+    } else if (isOperandConstantInt(op2)) {
+        emitLoad(op1, regT1, regT0);
+        compare32(condition, regT0, Imm32(getConstantOperand(op2).asInt32()), regT0);
+    } else {
+        emitLoad2(op1, regT1, regT0, op2, regT3, regT2);
+        compare32(condition, regT0, regT2, regT0);
+    }
+    emitStoreBool(dst, regT0);
+}
+
 void JIT::emit_compareAndJumpSlow(int op1, int op2, unsigned target, DoubleCondition, size_t (JIT_OPERATION *operation)(ExecState*, EncodedJSValue, EncodedJSValue), bool invert, Vector<SlowCaseEntry>::iterator& iter)
 {
     if (isOperandConstantChar(op1) || isOperandConstantChar(op2)) {
index 5dde664..ab888d3 100644 (file)
@@ -1398,6 +1398,18 @@ _llint_op_greatereq:
     dispatch(constexpr op_greatereq_length)
 
 
+_llint_op_below:
+    traceExecution()
+    compareUnsigned(
+        macro (left, right, result) cib left, right, result end)
+
+
+_llint_op_beloweq:
+    traceExecution()
+    compareUnsigned(
+        macro (left, right, result) cibeq left, right, result end)
+
+
 _llint_op_mod:
     traceExecution()
     callOpcodeSlowPath(_slow_path_mod)
@@ -1577,6 +1589,18 @@ _llint_op_jngreatereq:
         _llint_slow_path_jngreatereq)
 
 
+_llint_op_jbelow:
+    traceExecution()
+    compareUnsignedJump(
+        macro (left, right, target) bib left, right, target end)
+
+
+_llint_op_jbeloweq:
+    traceExecution()
+    compareUnsignedJump(
+        macro (left, right, target) bibeq left, right, target end)
+
+
 _llint_op_loop_hint:
     traceExecution()
     checkSwitchToJITForLoop()
index 114a978..4a47582 100644 (file)
@@ -1796,6 +1796,32 @@ _llint_op_jneq_ptr:
     dispatch(constexpr op_jneq_ptr_length)
 
 
+macro compareUnsignedJump(integerCompare)
+    loadi 4[PC], t2
+    loadi 8[PC], t3
+    loadConstantOrVariable(t2, t0, t1)
+    loadConstantOrVariable2Reg(t3, t2, t3)
+    integerCompare(t1, t3, .jumpTarget)
+    dispatch(4)
+
+.jumpTarget:
+    dispatchBranch(12[PC])
+end
+
+
+macro compareUnsigned(integerCompareAndSet)
+    loadi 12[PC], t2
+    loadi 8[PC], t0
+    loadConstantOrVariable(t2, t3, t1)
+    loadConstantOrVariable2Reg(t0, t2, t0)
+    integerCompareAndSet(t0, t1, t0)
+    loadi 4[PC], t2
+    storei BooleanTag, TagOffset[cfr, t2, 8]
+    storei t0, PayloadOffset[cfr, t2, 8]
+    dispatch(4)
+end
+
+
 macro compare(integerCompare, doubleCompare, slowPath)
     loadi 4[PC], t2
     loadi 8[PC], t3
index 5d62987..85c77d2 100644 (file)
@@ -1813,6 +1813,33 @@ macro compare(integerCompare, doubleCompare, slowPath)
 end
 
 
+macro compareUnsignedJump(integerCompare)
+    loadisFromInstruction(1, t2)
+    loadisFromInstruction(2, t3)
+    loadConstantOrVariable(t2, t0)
+    loadConstantOrVariable(t3, t1)
+    integerCompare(t0, t1, .jumpTarget)
+    dispatch(4)
+
+.jumpTarget:
+    dispatchIntIndirect(3)
+end
+
+
+macro compareUnsigned(integerCompareAndSet)
+    traceExecution()
+    loadisFromInstruction(3, t0)
+    loadisFromInstruction(2, t2)
+    loadisFromInstruction(1, t3)
+    loadConstantOrVariable(t0, t1)
+    loadConstantOrVariable(t2, t0)
+    integerCompareAndSet(t0, t1, t0)
+    orq ValueFalse, t0
+    storeq t0, [cfr, t3, 8]
+    dispatch(4)
+end
+
+
 _llint_op_switch_imm:
     traceExecution()
     loadisFromInstruction(3, t2)
index d6fec30..4551cb9 100644 (file)
@@ -188,6 +188,7 @@ namespace JSC {
         virtual bool isImportNode() const { return false; }
         virtual bool isNewTarget() const { return false; }
         virtual bool isBytecodeIntrinsicNode() const { return false; }
+        virtual bool isBinaryOpNode() const { return false; }
 
         virtual void emitBytecodeInConditionContext(BytecodeGenerator&, Label&, Label&, FallThroughMode);
 
@@ -1064,7 +1065,11 @@ namespace JSC {
         ExpressionNode* lhs() { return m_expr1; };
         ExpressionNode* rhs() { return m_expr2; };
 
+        bool isBinaryOpNode() const override { return true; }
+
     private:
+        enum class UInt32Result { UInt32, Constant, };
+
         void tryFoldToBranch(BytecodeGenerator&, TriState& branchCondition, ExpressionNode*& branchExpression);
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
@@ -1078,6 +1083,7 @@ namespace JSC {
         OpcodeID m_opcodeID;
     protected:
         bool m_rightHasAssignments;
+        bool m_shouldToUnsignedResult { true };
     };
 
     class PowNode : public BinaryOpNode {