Ignore:
Timestamp:
Sep 26, 2017, 1:32:18 PM (8 years ago)
Author:
Yusuke Suzuki
Message:

Add Above/Below comparisons for UInt32 patterns
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.

  1. 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):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

    r222473 r222518  
    19821982    OpcodeID opcodeID = this->opcodeID();
    19831983
     1984    if (opcodeID == op_less || opcodeID == op_lesseq || opcodeID == op_greater || opcodeID == op_greatereq) {
     1985        enum class UInt32Result {
     1986            UInt32,
     1987            Constant,
     1988        };
     1989        auto isUInt32 = [&] (ExpressionNode* node) -> std::optional<UInt32Result> {
     1990            if (node->isBinaryOpNode() && static_cast<BinaryOpNode*>(node)->opcodeID() == op_urshift)
     1991                return UInt32Result::UInt32;
     1992            if (node->isNumber() && static_cast<NumberNode*>(node)->isIntegerNode()) {
     1993                int32_t value = static_cast<int32_t>(static_cast<IntegerNode*>(node)->value());
     1994                if (value >= 0)
     1995                    return UInt32Result::Constant;
     1996            }
     1997            return std::nullopt;
     1998        };
     1999        auto leftResult = isUInt32(m_expr1);
     2000        auto rightResult = isUInt32(m_expr2);
     2001        if ((leftResult && rightResult) && (leftResult.value() == UInt32Result::UInt32 || rightResult.value() == UInt32Result::UInt32)) {
     2002            auto* left = m_expr1;
     2003            auto* right = m_expr2;
     2004            if (left->isBinaryOpNode()) {
     2005                ASSERT(static_cast<BinaryOpNode*>(left)->opcodeID() == op_urshift);
     2006                static_cast<BinaryOpNode*>(left)->m_shouldToUnsignedResult = false;
     2007            }
     2008            if (right->isBinaryOpNode()) {
     2009                ASSERT(static_cast<BinaryOpNode*>(right)->opcodeID() == op_urshift);
     2010                static_cast<BinaryOpNode*>(right)->m_shouldToUnsignedResult = false;
     2011            }
     2012            RefPtr<RegisterID> src1 = generator.emitNodeForLeftHandSide(left, m_rightHasAssignments, right->isPure(generator));
     2013            RefPtr<RegisterID> src2 = generator.emitNode(right);
     2014            generator.emitExpressionInfo(position(), position(), position());
     2015
     2016            // Since the both sides only accept Int32, replacing operands is not observable to users.
     2017            bool replaceOperands = false;
     2018            OpcodeID resultOp = opcodeID;
     2019            switch (opcodeID) {
     2020            case op_less:
     2021                resultOp = op_below;
     2022                break;
     2023            case op_lesseq:
     2024                resultOp = op_beloweq;
     2025                break;
     2026            case op_greater:
     2027                resultOp = op_below;
     2028                replaceOperands = true;
     2029                break;
     2030            case op_greatereq:
     2031                resultOp = op_beloweq;
     2032                replaceOperands = true;
     2033                break;
     2034            default:
     2035                RELEASE_ASSERT_NOT_REACHED();
     2036            }
     2037            OperandTypes operandTypes(left->resultDescriptor(), right->resultDescriptor());
     2038            if (replaceOperands) {
     2039                std::swap(src1, src2);
     2040                operandTypes = OperandTypes(right->resultDescriptor(), left->resultDescriptor());
     2041            }
     2042            return generator.emitBinaryOp(resultOp, generator.finalDestination(dst, src1.get()), src1.get(), src2.get(), operandTypes);
     2043        }
     2044    }
     2045
    19842046    if (opcodeID == op_add && m_expr1->isAdd() && m_expr1->resultDescriptor().definitelyIsString()) {
    19852047        generator.emitExpressionInfo(position(), position(), position());
     
    20172079    }
    20182080    RegisterID* result = generator.emitBinaryOp(opcodeID, generator.finalDestination(dst, src1.get()), src1.get(), src2.get(), OperandTypes(left->resultDescriptor(), right->resultDescriptor()));
    2019     if (opcodeID == op_urshift && dst != generator.ignoredResult())
    2020         return generator.emitUnaryOp(op_unsigned, result, result);
     2081    if (m_shouldToUnsignedResult) {
     2082        if (opcodeID == op_urshift && dst != generator.ignoredResult())
     2083            return generator.emitUnaryOp(op_unsigned, result, result);
     2084    }
    20212085    return result;
    20222086}
Note: See TracChangeset for help on using the changeset viewer.