Title: [225212] trunk/Source/_javascript_Core
Revision
225212
Author
[email protected]
Date
2017-11-28 05:11:32 -0800 (Tue, 28 Nov 2017)

Log Message

Support recursive tail call optimization for polymorphic calls
https://bugs.webkit.org/show_bug.cgi?id=178390

Reviewed by Saam Barati.

Comes with a large but fairly simple refactoring: the inlining path for varargs and non-varargs calls now converge a lot later,
eliminating some redundant checks, and simplifying a few parts of the inlining pipeline.

Also removes some dead code from inlineCall(): there was a special path for when m_continuationBlock is null, but it should never be (now checked with RELEASE_ASSERT).

* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleCall):
(JSC::DFG::ByteCodeParser::handleVarargsCall):
(JSC::DFG::ByteCodeParser::handleRecursiveTailCall):
(JSC::DFG::ByteCodeParser::inlineCall):
(JSC::DFG::ByteCodeParser::handleCallVariant):
(JSC::DFG::ByteCodeParser::handleVarargsInlining):
(JSC::DFG::ByteCodeParser::getInliningBalance):
(JSC::DFG::ByteCodeParser::handleInlining):
(JSC::DFG::ByteCodeParser::attemptToInlineCall): Deleted.

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (225211 => 225212)


--- trunk/Source/_javascript_Core/ChangeLog	2017-11-28 12:45:30 UTC (rev 225211)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-11-28 13:11:32 UTC (rev 225212)
@@ -1,3 +1,26 @@
+2017-11-28  Robin Morisset  <[email protected]>
+
+        Support recursive tail call optimization for polymorphic calls
+        https://bugs.webkit.org/show_bug.cgi?id=178390
+
+        Reviewed by Saam Barati.
+
+        Comes with a large but fairly simple refactoring: the inlining path for varargs and non-varargs calls now converge a lot later,
+        eliminating some redundant checks, and simplifying a few parts of the inlining pipeline.
+
+        Also removes some dead code from inlineCall(): there was a special path for when m_continuationBlock is null, but it should never be (now checked with RELEASE_ASSERT).
+
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleCall):
+        (JSC::DFG::ByteCodeParser::handleVarargsCall):
+        (JSC::DFG::ByteCodeParser::handleRecursiveTailCall):
+        (JSC::DFG::ByteCodeParser::inlineCall):
+        (JSC::DFG::ByteCodeParser::handleCallVariant):
+        (JSC::DFG::ByteCodeParser::handleVarargsInlining):
+        (JSC::DFG::ByteCodeParser::getInliningBalance):
+        (JSC::DFG::ByteCodeParser::handleInlining):
+        (JSC::DFG::ByteCodeParser::attemptToInlineCall): Deleted.
+
 2017-11-27  Saam Barati  <[email protected]>
 
         Spread can escape when CreateRest does not

Modified: trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp (225211 => 225212)


--- trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2017-11-28 12:45:30 UTC (rev 225211)
+++ trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2017-11-28 13:11:32 UTC (rev 225212)
@@ -224,14 +224,17 @@
     void emitFunctionChecks(CallVariant, Node* callTarget, VirtualRegister thisArgumnt);
     void emitArgumentPhantoms(int registerOffset, int argumentCountIncludingThis);
     Node* getArgumentCount();
-    bool handleRecursiveTailCall(Node* callTargetNode, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis);
+    template<typename ChecksFunctor>
+    bool handleRecursiveTailCall(CallVariant, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& emitFunctionCheckIfNeeded);
     unsigned inliningCost(CallVariant, int argumentCountIncludingThis, InlineCallFrame::Kind); // Return UINT_MAX if it's not an inlining candidate. By convention, intrinsics have a cost of 1.
     // Handle inlining. Return true if it succeeded, false if we need to plant a call.
-    bool handleInlining(Node* callTargetNode, int resultOperand, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, VirtualRegister argumentsArgument, unsigned argumentsOffset, int argumentCountIncludingThis, unsigned nextOffset, NodeType callOp, InlineCallFrame::Kind, SpeculatedType prediction);
+    bool handleVarargsInlining(Node* callTargetNode, int resultOperand, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, VirtualRegister argumentsArgument, unsigned argumentsOffset, NodeType callOp, InlineCallFrame::Kind);
+    unsigned getInliningBalance(const CallLinkStatus&, CodeSpecializationKind);
+    enum class CallOptimizationResult { OptimizedToJump, Inlined, DidNothing };
+    CallOptimizationResult handleCallVariant(Node* callTargetNode, int resultOperand, CallVariant, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, bool needsToCheckCallee);
+    CallOptimizationResult handleInlining(Node* callTargetNode, int resultOperand, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, unsigned nextOffset, NodeType callOp, InlineCallFrame::Kind, SpeculatedType prediction);
     template<typename ChecksFunctor>
-    bool attemptToInlineCall(Node* callTargetNode, int resultOperand, CallVariant, int registerOffset, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks);
-    template<typename ChecksFunctor>
-    void inlineCall(Node* callTargetNode, int resultOperand, CallVariant, int registerOffset, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks);
+    void inlineCall(Node* callTargetNode, int resultOperand, CallVariant, int registerOffset, int argumentCountIncludingThis, InlineCallFrame::Kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks);
     // Handle intrinsic functions. Return true if it succeeded, false if we need to plant a call.
     template<typename ChecksFunctor>
     bool handleIntrinsicCall(Node* callee, int resultOperand, Intrinsic, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, const ChecksFunctor& insertChecks);
@@ -1234,7 +1237,7 @@
 
 void ByteCodeParser::makeBlockTargetable(BasicBlock* block, unsigned bytecodeIndex)
 {
-    ASSERT(block->bytecodeBegin == UINT_MAX);
+    RELEASE_ASSERT(block->bytecodeBegin == UINT_MAX);
     block->bytecodeBegin = bytecodeIndex;
     // m_blockLinkingTargets must always be sorted in increasing order of bytecodeBegin
     if (m_inlineStackTop->m_blockLinkingTargets.size())
@@ -1296,21 +1299,15 @@
     
     VERBOSE_LOG("    Handling call at ", currentCodeOrigin(), ": ", callLinkStatus, "\n");
     
-    // We first check that we have profiling information about this call, and that it did not behave too polymorphically.
+    // If we have profiling information about this call, and it did not behave too polymorphically,
+    // we may be able to inline it, or in the case of recursive tail calls turn it into a jump.
     if (callLinkStatus.canOptimize()) {
         VirtualRegister thisArgument = virtualRegisterForArgument(0, registerOffset);
-
-        if (op == TailCall && handleRecursiveTailCall(callTarget, callLinkStatus, registerOffset, thisArgument, argumentCountIncludingThis))
+        auto optimizationResult = handleInlining(callTarget, result, callLinkStatus, registerOffset, thisArgument,
+            argumentCountIncludingThis, m_currentIndex + instructionSize, op, kind, prediction);
+        if (optimizationResult == CallOptimizationResult::OptimizedToJump)
             return Terminal;
-
-        // Inlining is quite complex, and managed by a pipeline of functions:
-        // handle(Varargs)Call -> handleInlining -> attemptToInlineCall -> inlineCall
-        // - handleCall and handleVarargsCall deal with the case where no inlining happens, and do some sanity checks on their arguments
-        // - handleInlining checks whether the call is polymorphic, and if so is responsible for inserting a switch on the callee
-        // - attemptToInlineCall deals with special cases such as intrinsics, it also checks the inlining heuristic (through inliningCost)
-        // - inlineCall finally does the actual inlining, after a complicated procedure to setup the stack correctly
-        unsigned nextOffset = m_currentIndex + instructionSize;
-        if (handleInlining(callTarget, result, callLinkStatus, registerOffset, thisArgument, VirtualRegister(), 0, argumentCountIncludingThis, nextOffset, op, kind, prediction)) {
+        if (optimizationResult == CallOptimizationResult::Inlined) {
             if (UNLIKELY(m_graph.compilation()))
                 m_graph.compilation()->noticeInlinedCall();
             return NonTerminal;
@@ -1347,11 +1344,15 @@
     
     VERBOSE_LOG("    Varargs call link status at ", currentCodeOrigin(), ": ", callLinkStatus, "\n");
     
-    if (callLinkStatus.canOptimize()
-        && handleInlining(callTarget, result, callLinkStatus, firstFreeReg, VirtualRegister(thisReg), VirtualRegister(arguments), firstVarArgOffset, 0, m_currentIndex + OPCODE_LENGTH(op_call_varargs), op, InlineCallFrame::varargsKindFor(callMode), prediction)) {
-        if (UNLIKELY(m_graph.compilation()))
-            m_graph.compilation()->noticeInlinedCall();
-        return NonTerminal;
+    if (callLinkStatus.canOptimize()) {
+        if (handleVarargsInlining(callTarget, result,
+            callLinkStatus, firstFreeReg, VirtualRegister(thisReg), VirtualRegister(arguments),
+            firstVarArgOffset, op,
+            InlineCallFrame::varargsKindFor(callMode))) {
+            if (UNLIKELY(m_graph.compilation()))
+                m_graph.compilation()->noticeInlinedCall();
+            return NonTerminal;
+        }
     }
     
     CallVarargsData* data = ""
@@ -1420,17 +1421,13 @@
         addToGraph(Phantom, get(virtualRegisterForArgument(i, registerOffset)));
 }
 
-bool ByteCodeParser::handleRecursiveTailCall(Node* callTargetNode, const CallLinkStatus& callLinkStatus, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis)
+template<typename ChecksFunctor>
+bool ByteCodeParser::handleRecursiveTailCall(CallVariant callVariant, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& emitFunctionCheckIfNeeded)
 {
     if (UNLIKELY(!Options::optimizeRecursiveTailCalls()))
         return false;
 
-    // FIXME: We currently only do this optimisation in the simple, non-polymorphic case.
-    // https://bugs.webkit.org/show_bug.cgi?id=178390
-    if (callLinkStatus.couldTakeSlowPath() || callLinkStatus.size() != 1)
-        return false;
-
-    auto targetExecutable = callLinkStatus[0].executable();
+    auto targetExecutable = callVariant.executable();
     InlineStackEntry* stackEntry = m_inlineStackTop;
     do {
         if (targetExecutable != stackEntry->executable())
@@ -1450,7 +1447,7 @@
         }
 
         // We must add some check that the profiling information was correct and the target of this call is what we thought
-        emitFunctionChecks(callLinkStatus[0], callTargetNode, thisArgument);
+        emitFunctionCheckIfNeeded();
 
         // We must set the arguments to the right values
         int argIndex = 0;
@@ -1591,8 +1588,9 @@
 }
 
 template<typename ChecksFunctor>
-void ByteCodeParser::inlineCall(Node* callTargetNode, int resultOperand, CallVariant callee, int registerOffset, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks)
+void ByteCodeParser::inlineCall(Node* callTargetNode, int resultOperand, CallVariant callee, int registerOffset, int argumentCountIncludingThis, InlineCallFrame::Kind kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks)
 {
+    Instruction* savedCurrentInstruction = m_currentInstruction;
     CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
     
     ASSERT(inliningCost(callee, argumentCountIncludingThis, kind) != UINT_MAX);
@@ -1722,240 +1720,242 @@
 
     linkBlocks(inlineStackEntry.m_unlinkedBlocks, inlineStackEntry.m_blockLinkingTargets);
     
-    // If the callee returned at least once, it prepared a continuation block for us.
-    if (inlineStackEntry.m_continuationBlock)
-        m_currentBlock = inlineStackEntry.m_continuationBlock;
-    else {
-        // We are in the case where the callee never returns (for example it loops forever).
-        // In that case, all blocks should end in a terminal.
-        ASSERT(m_graph.lastBlock()->terminal());
-        // We then allocate a new block to continue in.
-        m_currentBlock = allocateTargetableBlock(nextOffset);
-    }
-    ASSERT(m_currentBlock);
+    // There is an invariant that we use here: every function has at least one op_ret somewhere.
+    // And when parseBlock encounters an op_ret, it setups a continuation block if there was none.
+    // If this invariant was to be changed, we would need to allocate a new block here in the case where there was no continuation block ready.
+    RELEASE_ASSERT(inlineStackEntry.m_continuationBlock);
+    m_currentBlock = inlineStackEntry.m_continuationBlock;
     ASSERT(!m_currentBlock->terminal());
 
     prepareToParseBlock();
+    m_currentInstruction = savedCurrentInstruction;
 }
 
-template<typename ChecksFunctor>
-bool ByteCodeParser::attemptToInlineCall(Node* callTargetNode, int resultOperand, CallVariant callee, int registerOffset, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks)
+ByteCodeParser::CallOptimizationResult ByteCodeParser::handleCallVariant(Node* callTargetNode, int resultOperand, CallVariant callee, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, unsigned nextOffset, InlineCallFrame::Kind kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, bool needsToCheckCallee)
 {
-    CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
-    
-    if (!inliningBalance)
-        return false;
-    
     VERBOSE_LOG("    Considering callee ", callee, "\n");
-    
-    // Intrinsics and internal functions can only be inlined if we're not doing varargs. This is because
-    // we currently don't have any way of getting profiling information for arguments to non-JS varargs
-    // calls. The prediction propagator won't be of any help because LoadVarargs obscures the data flow,
-    // and there are no callsite value profiles and native function won't have callee value profiles for
-    // those arguments. Even worse, if the intrinsic decides to exit, it won't really have anywhere to
-    // exit to: LoadVarargs is effectful and it's part of the op_call_varargs, so we can't exit without
-    // calling LoadVarargs twice.
-    if (!InlineCallFrame::isVarargs(kind)) {
 
-        bool didInsertChecks = false;
-        auto insertChecksWithAccounting = [&] () {
-            insertChecks(nullptr);
-            didInsertChecks = true;
-        };
+    bool didInsertChecks = false;
+    auto insertChecksWithAccounting = [&] () {
+        if (needsToCheckCallee)
+            emitFunctionChecks(callee, callTargetNode, thisArgument);
+        didInsertChecks = true;
+    };
 
-        auto endSpecialCase = [&] () {
-            RELEASE_ASSERT(didInsertChecks);
-            addToGraph(Phantom, callTargetNode);
-            emitArgumentPhantoms(registerOffset, argumentCountIncludingThis);
-            inliningBalance--;
-            if (continuationBlock) {
-                m_currentIndex = nextOffset;
-                m_exitOK = true;
-                processSetLocalQueue();
-                addJumpTo(continuationBlock);
-            }
-        };
-    
-        if (InternalFunction* function = callee.internalFunction()) {
-            if (handleConstantInternalFunction(callTargetNode, resultOperand, function, registerOffset, argumentCountIncludingThis, specializationKind, prediction, insertChecksWithAccounting)) {
-                endSpecialCase();
-                return true;
-            }
-            RELEASE_ASSERT(!didInsertChecks);
-            return false;
+    if (kind == InlineCallFrame::TailCall && ByteCodeParser::handleRecursiveTailCall(callee, registerOffset, argumentCountIncludingThis, insertChecksWithAccounting)) {
+        RELEASE_ASSERT(didInsertChecks);
+        return CallOptimizationResult::OptimizedToJump;
+    }
+    RELEASE_ASSERT(!didInsertChecks);
+
+    if (!inliningBalance)
+        return CallOptimizationResult::DidNothing;
+
+    CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
+
+    auto endSpecialCase = [&] () {
+        RELEASE_ASSERT(didInsertChecks);
+        addToGraph(Phantom, callTargetNode);
+        emitArgumentPhantoms(registerOffset, argumentCountIncludingThis);
+        inliningBalance--;
+        if (continuationBlock) {
+            m_currentIndex = nextOffset;
+            m_exitOK = true;
+            processSetLocalQueue();
+            addJumpTo(continuationBlock);
         }
-    
-        Intrinsic intrinsic = callee.intrinsicFor(specializationKind);
-        if (intrinsic != NoIntrinsic) {
-            if (handleIntrinsicCall(callTargetNode, resultOperand, intrinsic, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) {
-                endSpecialCase();
-                return true;
-            }
+    };
 
-            RELEASE_ASSERT(!didInsertChecks);
-            // We might still try to inline the Intrinsic because it might be a builtin JS function.
+    if (InternalFunction* function = callee.internalFunction()) {
+        if (handleConstantInternalFunction(callTargetNode, resultOperand, function, registerOffset, argumentCountIncludingThis, specializationKind, prediction, insertChecksWithAccounting)) {
+            endSpecialCase();
+            return CallOptimizationResult::Inlined;
         }
+        RELEASE_ASSERT(!didInsertChecks);
+        return CallOptimizationResult::DidNothing;
+    }
 
-        if (Options::useDOMJIT()) {
-            if (const DOMJIT::Signature* signature = callee.signatureFor(specializationKind)) {
-                if (handleDOMJITCall(callTargetNode, resultOperand, signature, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) {
-                    endSpecialCase();
-                    return true;
-                }
-                RELEASE_ASSERT(!didInsertChecks);
+    Intrinsic intrinsic = callee.intrinsicFor(specializationKind);
+    if (intrinsic != NoIntrinsic) {
+        if (handleIntrinsicCall(callTargetNode, resultOperand, intrinsic, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) {
+            endSpecialCase();
+            return CallOptimizationResult::Inlined;
+        }
+        RELEASE_ASSERT(!didInsertChecks);
+        // We might still try to inline the Intrinsic because it might be a builtin JS function.
+    }
+
+    if (Options::useDOMJIT()) {
+        if (const DOMJIT::Signature* signature = callee.signatureFor(specializationKind)) {
+            if (handleDOMJITCall(callTargetNode, resultOperand, signature, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) {
+                endSpecialCase();
+                return CallOptimizationResult::Inlined;
             }
+            RELEASE_ASSERT(!didInsertChecks);
         }
     }
     
     unsigned myInliningCost = inliningCost(callee, argumentCountIncludingThis, kind);
     if (myInliningCost > inliningBalance)
-        return false;
+        return CallOptimizationResult::DidNothing;
 
-    Instruction* savedCurrentInstruction = m_currentInstruction;
-    inlineCall(callTargetNode, resultOperand, callee, registerOffset, argumentCountIncludingThis, nextOffset, kind, continuationBlock, insertChecks);
+    auto insertCheck = [&] (CodeBlock*) {
+        if (needsToCheckCallee)
+            emitFunctionChecks(callee, callTargetNode, thisArgument);
+    };
+    inlineCall(callTargetNode, resultOperand, callee, registerOffset, argumentCountIncludingThis, kind, continuationBlock, insertCheck);
     inliningBalance -= myInliningCost;
-    m_currentInstruction = savedCurrentInstruction;
-    return true;
+    return CallOptimizationResult::Inlined;
 }
 
-bool ByteCodeParser::handleInlining(
-    Node* callTargetNode, int resultOperand, const CallLinkStatus& callLinkStatus,
-    int registerOffsetOrFirstFreeReg, VirtualRegister thisArgument,
-    VirtualRegister argumentsArgument, unsigned argumentsOffset, int argumentCountIncludingThis,
-    unsigned nextOffset, NodeType callOp, InlineCallFrame::Kind kind, SpeculatedType prediction)
+bool ByteCodeParser::handleVarargsInlining(Node* callTargetNode, int resultOperand,
+    const CallLinkStatus& callLinkStatus, int firstFreeReg, VirtualRegister thisArgument,
+    VirtualRegister argumentsArgument, unsigned argumentsOffset,
+    NodeType callOp, InlineCallFrame::Kind kind)
 {
-    VERBOSE_LOG("Handling inlining...\nStack: ", currentCodeOrigin(), "\n");
-    CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
-    
-    if (!callLinkStatus.size()) {
-        VERBOSE_LOG("Bailing inlining.\n");
+    VERBOSE_LOG("Handling inlining (Varargs)...\nStack: ", currentCodeOrigin(), "\n");
+    if (callLinkStatus.maxNumArguments() > Options::maximumVarargsForInlining()) {
+        VERBOSE_LOG("Bailing inlining: too many arguments for varargs inlining.\n");
         return false;
     }
+    if (callLinkStatus.couldTakeSlowPath() || callLinkStatus.size() != 1) {
+        VERBOSE_LOG("Bailing inlining: polymorphic inlining is not yet supported for varargs.\n");
+        return false;
+    }
+
+    CallVariant callVariant = callLinkStatus[0];
+
+    unsigned mandatoryMinimum;
+    if (FunctionExecutable* functionExecutable = callVariant.functionExecutable())
+        mandatoryMinimum = functionExecutable->parameterCount();
+    else
+        mandatoryMinimum = 0;
     
-    if (InlineCallFrame::isVarargs(kind)
-        && callLinkStatus.maxNumArguments() > Options::maximumVarargsForInlining()) {
-        VERBOSE_LOG("Bailing inlining because of varargs.\n");
+    // includes "this"
+    unsigned maxNumArguments = std::max(callLinkStatus.maxNumArguments(), mandatoryMinimum + 1);
+
+    CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
+    if (inliningCost(callVariant, maxNumArguments, kind) > getInliningBalance(callLinkStatus, specializationKind)) {
+        VERBOSE_LOG("Bailing inlining: inlining cost too high.\n");
         return false;
     }
+    
+    int registerOffset = firstFreeReg + 1;
+    registerOffset -= maxNumArguments; // includes "this"
+    registerOffset -= CallFrame::headerSizeInRegisters;
+    registerOffset = -WTF::roundUpToMultipleOf(stackAlignmentRegisters(), -registerOffset);
+    
+    auto insertChecks = [&] (CodeBlock* codeBlock) {
+        emitFunctionChecks(callVariant, callTargetNode, thisArgument);
         
+        int remappedRegisterOffset =
+        m_inlineStackTop->remapOperand(VirtualRegister(registerOffset)).offset();
+        
+        ensureLocals(VirtualRegister(remappedRegisterOffset).toLocal());
+        
+        int argumentStart = registerOffset + CallFrame::headerSizeInRegisters;
+        int remappedArgumentStart =
+        m_inlineStackTop->remapOperand(VirtualRegister(argumentStart)).offset();
+        
+        LoadVarargsData* data = ""
+        data->start = VirtualRegister(remappedArgumentStart + 1);
+        data->count = VirtualRegister(remappedRegisterOffset + CallFrameSlot::argumentCount);
+        data->offset = argumentsOffset;
+        data->limit = maxNumArguments;
+        data->mandatoryMinimum = mandatoryMinimum;
+        
+        if (callOp == TailCallForwardVarargs)
+            addToGraph(ForwardVarargs, OpInfo(data));
+        else
+            addToGraph(LoadVarargs, OpInfo(data), get(argumentsArgument));
+        
+        // LoadVarargs may OSR exit. Hence, we need to keep alive callTargetNode, thisArgument
+        // and argumentsArgument for the baseline JIT. However, we only need a Phantom for
+        // callTargetNode because the other 2 are still in use and alive at this point.
+        addToGraph(Phantom, callTargetNode);
+        
+        // In DFG IR before SSA, we cannot insert control flow between after the
+        // LoadVarargs and the last SetArgument. This isn't a problem once we get to DFG
+        // SSA. Fortunately, we also have other reasons for not inserting control flow
+        // before SSA.
+        
+        VariableAccessData* countVariable = newVariableAccessData(VirtualRegister(remappedRegisterOffset + CallFrameSlot::argumentCount));
+        // This is pretty lame, but it will force the count to be flushed as an int. This doesn't
+        // matter very much, since our use of a SetArgument and Flushes for this local slot is
+        // mostly just a formality.
+        countVariable->predict(SpecInt32Only);
+        countVariable->mergeIsProfitableToUnbox(true);
+        Node* setArgumentCount = addToGraph(SetArgument, OpInfo(countVariable));
+        m_currentBlock->variablesAtTail.setOperand(countVariable->local(), setArgumentCount);
+        
+        set(VirtualRegister(argumentStart), get(thisArgument), ImmediateNakedSet);
+        for (unsigned argument = 1; argument < maxNumArguments; ++argument) {
+            VariableAccessData* variable = newVariableAccessData(VirtualRegister(remappedArgumentStart + argument));
+            variable->mergeShouldNeverUnbox(true); // We currently have nowhere to put the type check on the LoadVarargs. LoadVarargs is effectful, so after it finishes, we cannot exit.
+            
+            // For a while it had been my intention to do things like this inside the
+            // prediction injection phase. But in this case it's really best to do it here,
+            // because it's here that we have access to the variable access datas for the
+            // inlining we're about to do.
+            //
+            // Something else that's interesting here is that we'd really love to get
+            // predictions from the arguments loaded at the callsite, rather than the
+            // arguments received inside the callee. But that probably won't matter for most
+            // calls.
+            if (codeBlock && argument < static_cast<unsigned>(codeBlock->numParameters())) {
+                ConcurrentJSLocker locker(codeBlock->m_lock);
+                ValueProfile& profile = ""
+                variable->predict(profile.computeUpdatedPrediction(locker));
+            }
+            
+            Node* setArgument = addToGraph(SetArgument, OpInfo(variable));
+            m_currentBlock->variablesAtTail.setOperand(variable->local(), setArgument);
+        }
+    };
+
+    // Intrinsics and internal functions can only be inlined if we're not doing varargs. This is because
+    // we currently don't have any way of getting profiling information for arguments to non-JS varargs
+    // calls. The prediction propagator won't be of any help because LoadVarargs obscures the data flow,
+    // and there are no callsite value profiles and native function won't have callee value profiles for
+    // those arguments. Even worse, if the intrinsic decides to exit, it won't really have anywhere to
+    // exit to: LoadVarargs is effectful and it's part of the op_call_varargs, so we can't exit without
+    // calling LoadVarargs twice.
+    inlineCall(callTargetNode, resultOperand, callVariant, registerOffset, maxNumArguments, kind, nullptr, insertChecks);
+
+    VERBOSE_LOG("Successful inlining (varargs, monomorphic).\nStack: ", currentCodeOrigin(), "\n");
+    return true;
+}
+
+unsigned ByteCodeParser::getInliningBalance(const CallLinkStatus& callLinkStatus, CodeSpecializationKind specializationKind)
+{
     unsigned inliningBalance = Options::maximumFunctionForCallInlineCandidateInstructionCount();
     if (specializationKind == CodeForConstruct)
         inliningBalance = std::min(inliningBalance, Options::maximumFunctionForConstructInlineCandidateInstructionCount());
     if (callLinkStatus.isClosureCall())
         inliningBalance = std::min(inliningBalance, Options::maximumFunctionForClosureCallInlineCandidateInstructionCount());
+    return inliningBalance;
+}
+
+ByteCodeParser::CallOptimizationResult ByteCodeParser::handleInlining(
+    Node* callTargetNode, int resultOperand, const CallLinkStatus& callLinkStatus,
+    int registerOffset, VirtualRegister thisArgument,
+    int argumentCountIncludingThis,
+    unsigned nextOffset, NodeType callOp, InlineCallFrame::Kind kind, SpeculatedType prediction)
+{
+    VERBOSE_LOG("Handling inlining...\nStack: ", currentCodeOrigin(), "\n");
     
+    CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind);
+    unsigned inliningBalance = getInliningBalance(callLinkStatus, specializationKind);
+
     // First check if we can avoid creating control flow. Our inliner does some CFG
     // simplification on the fly and this helps reduce compile times, but we can only leverage
     // this in cases where we don't need control flow diamonds to check the callee.
     if (!callLinkStatus.couldTakeSlowPath() && callLinkStatus.size() == 1) {
-        int registerOffset;
-        
-        // Only used for varargs calls.
-        unsigned mandatoryMinimum = 0;
-        unsigned maxNumArguments = 0;
+        return handleCallVariant(
+            callTargetNode, resultOperand, callLinkStatus[0], registerOffset, thisArgument,
+            argumentCountIncludingThis, nextOffset, kind, prediction, inliningBalance, nullptr, true);
+    }
 
-        if (InlineCallFrame::isVarargs(kind)) {
-            if (FunctionExecutable* functionExecutable = callLinkStatus[0].functionExecutable())
-                mandatoryMinimum = functionExecutable->parameterCount();
-            else
-                mandatoryMinimum = 0;
-            
-            // includes "this"
-            maxNumArguments = std::max(
-                callLinkStatus.maxNumArguments(),
-                mandatoryMinimum + 1);
-            
-            // We sort of pretend that this *is* the number of arguments that were passed.
-            argumentCountIncludingThis = maxNumArguments;
-            
-            registerOffset = registerOffsetOrFirstFreeReg + 1;
-            registerOffset -= maxNumArguments; // includes "this"
-            registerOffset -= CallFrame::headerSizeInRegisters;
-            registerOffset = -WTF::roundUpToMultipleOf(
-                stackAlignmentRegisters(),
-                -registerOffset);
-        } else
-            registerOffset = registerOffsetOrFirstFreeReg;
-        
-        bool result = attemptToInlineCall(
-            callTargetNode, resultOperand, callLinkStatus[0], registerOffset,
-            argumentCountIncludingThis, nextOffset, kind, prediction,
-            inliningBalance, nullptr, [&] (CodeBlock* codeBlock) {
-                emitFunctionChecks(callLinkStatus[0], callTargetNode, thisArgument);
-
-                // If we have a varargs call, we want to extract the arguments right now.
-                if (InlineCallFrame::isVarargs(kind)) {
-                    int remappedRegisterOffset =
-                        m_inlineStackTop->remapOperand(VirtualRegister(registerOffset)).offset();
-                    
-                    ensureLocals(VirtualRegister(remappedRegisterOffset).toLocal());
-                    
-                    int argumentStart = registerOffset + CallFrame::headerSizeInRegisters;
-                    int remappedArgumentStart =
-                        m_inlineStackTop->remapOperand(VirtualRegister(argumentStart)).offset();
-
-                    LoadVarargsData* data = ""
-                    data->start = VirtualRegister(remappedArgumentStart + 1);
-                    data->count = VirtualRegister(remappedRegisterOffset + CallFrameSlot::argumentCount);
-                    data->offset = argumentsOffset;
-                    data->limit = maxNumArguments;
-                    data->mandatoryMinimum = mandatoryMinimum;
-
-                    if (callOp == TailCallForwardVarargs)
-                        addToGraph(ForwardVarargs, OpInfo(data));
-                    else
-                        addToGraph(LoadVarargs, OpInfo(data), get(argumentsArgument));
-
-                    // LoadVarargs may OSR exit. Hence, we need to keep alive callTargetNode, thisArgument
-                    // and argumentsArgument for the baseline JIT. However, we only need a Phantom for
-                    // callTargetNode because the other 2 are still in use and alive at this point.
-                    addToGraph(Phantom, callTargetNode);
-
-                    // In DFG IR before SSA, we cannot insert control flow between after the
-                    // LoadVarargs and the last SetArgument. This isn't a problem once we get to DFG
-                    // SSA. Fortunately, we also have other reasons for not inserting control flow
-                    // before SSA.
-            
-                    VariableAccessData* countVariable = newVariableAccessData(
-                        VirtualRegister(remappedRegisterOffset + CallFrameSlot::argumentCount));
-                    // This is pretty lame, but it will force the count to be flushed as an int. This doesn't
-                    // matter very much, since our use of a SetArgument and Flushes for this local slot is
-                    // mostly just a formality.
-                    countVariable->predict(SpecInt32Only);
-                    countVariable->mergeIsProfitableToUnbox(true);
-                    Node* setArgumentCount = addToGraph(SetArgument, OpInfo(countVariable));
-                    m_currentBlock->variablesAtTail.setOperand(countVariable->local(), setArgumentCount);
-
-                    set(VirtualRegister(argumentStart), get(thisArgument), ImmediateNakedSet);
-                    for (unsigned argument = 1; argument < maxNumArguments; ++argument) {
-                        VariableAccessData* variable = newVariableAccessData(
-                            VirtualRegister(remappedArgumentStart + argument));
-                        variable->mergeShouldNeverUnbox(true); // We currently have nowhere to put the type check on the LoadVarargs. LoadVarargs is effectful, so after it finishes, we cannot exit.
-                        
-                        // For a while it had been my intention to do things like this inside the
-                        // prediction injection phase. But in this case it's really best to do it here,
-                        // because it's here that we have access to the variable access datas for the
-                        // inlining we're about to do.
-                        //
-                        // Something else that's interesting here is that we'd really love to get
-                        // predictions from the arguments loaded at the callsite, rather than the
-                        // arguments received inside the callee. But that probably won't matter for most
-                        // calls.
-                        if (codeBlock && argument < static_cast<unsigned>(codeBlock->numParameters())) {
-                            ConcurrentJSLocker locker(codeBlock->m_lock);
-                            ValueProfile& profile = ""
-                            variable->predict(profile.computeUpdatedPrediction(locker));
-                        }
-                        
-                        Node* setArgument = addToGraph(SetArgument, OpInfo(variable));
-                        m_currentBlock->variablesAtTail.setOperand(variable->local(), setArgument);
-                    }
-                }
-            });
-        VERBOSE_LOG("Done inlining (simple).\nStack: ", currentCodeOrigin(), "\nResult: ", result, "\n");
-        return result;
-    }
-    
     // We need to create some kind of switch over callee. For now we only do this if we believe that
     // we're in the top tier. We have two reasons for this: first, it provides us an opportunity to
     // do more detailed polyvariant/polymorphic profiling; and second, it reduces compile times in
@@ -1962,10 +1962,9 @@
     // the DFG. And by polyvariant profiling we mean polyvariant profiling of *this* call. Note that
     // we could improve that aspect of this by doing polymorphic inlining but having the profiling
     // also.
-    if (!isFTL(m_graph.m_plan.mode) || !Options::usePolymorphicCallInlining()
-        || InlineCallFrame::isVarargs(kind)) {
+    if (!isFTL(m_graph.m_plan.mode) || !Options::usePolymorphicCallInlining()) {
         VERBOSE_LOG("Bailing inlining (hard).\nStack: ", currentCodeOrigin(), "\n");
-        return false;
+        return CallOptimizationResult::DidNothing;
     }
     
     // If the claim is that this did not originate from a stub, then we don't want to emit a switch
@@ -1974,7 +1973,7 @@
     if (!Options::usePolymorphicCallInliningForNonStubStatus()
         && !callLinkStatus.isBasedOnStub()) {
         VERBOSE_LOG("Bailing inlining (non-stub polymorphism).\nStack: ", currentCodeOrigin(), "\n");
-        return false;
+        return CallOptimizationResult::DidNothing;
     }
 
     bool allAreClosureCalls = true;
@@ -1997,13 +1996,11 @@
         // closure calls.
         // https://bugs.webkit.org/show_bug.cgi?id=136020
         VERBOSE_LOG("Bailing inlining (mix).\nStack: ", currentCodeOrigin(), "\n");
-        return false;
+        return CallOptimizationResult::DidNothing;
     }
 
     VERBOSE_LOG("Doing hard inlining...\nStack: ", currentCodeOrigin(), "\n");
 
-    int registerOffset = registerOffsetOrFirstFreeReg;
-
     // This makes me wish that we were in SSA all the time. We need to pick a variable into which to
     // store the callee so that it will be accessible to all of the blocks we're about to create. We
     // get away with doing an immediate-set here because we wouldn't have performed any side effects
@@ -2041,12 +2038,12 @@
         
         Node* myCallTargetNode = getDirect(calleeReg);
         
-        bool inliningResult = attemptToInlineCall(
+        auto inliningResult = handleCallVariant(
             myCallTargetNode, resultOperand, callLinkStatus[i], registerOffset,
-            argumentCountIncludingThis, nextOffset, kind, prediction,
-            inliningBalance, continuationBlock, [&] (CodeBlock*) { });
+            thisArgument, argumentCountIncludingThis, nextOffset, kind, prediction,
+            inliningBalance, continuationBlock, false);
         
-        if (!inliningResult) {
+        if (inliningResult == CallOptimizationResult::DidNothing) {
             // That failed so we let the block die. Nothing interesting should have been added to
             // the block. We also give up on inlining any of the (less frequent) callees.
             ASSERT(m_graph.m_blocks.last() == m_currentBlock);
@@ -2067,7 +2064,7 @@
             thingToCaseOn = callLinkStatus[i].executable();
         }
         data.cases.append(SwitchCase(m_graph.freeze(thingToCaseOn), calleeEntryBlock));
-        VERBOSE_LOG("Finished inlining ", callLinkStatus[i], " at ", currentCodeOrigin(), ".\n");
+        VERBOSE_LOG("Finished optimizing ", callLinkStatus[i], " at ", currentCodeOrigin(), ".\n");
     }
 
     // Slow path block
@@ -2108,7 +2105,7 @@
     m_exitOK = true;
     
     VERBOSE_LOG("Done inlining (hard).\nStack: ", currentCodeOrigin(), "\n");
-    return true;
+    return CallOptimizationResult::Inlined;
 }
 
 template<typename ChecksFunctor>
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to