Ignore:
Timestamp:
Jan 26, 2012, 5:15:16 PM (14 years ago)
Author:
[email protected]
Message:

All DFG helpers that may call out to arbitrary JS code must know where they
were called from due to inlining and call stack walking
https://bugs.webkit.org/show_bug.cgi?id=77070
<rdar://problem/10750834>

Source/JavaScriptCore:

Reviewed by Geoff Garen.

Changed the DFG to always record a code origin index in the tag of the argument
count (which we previously left blank for the benefit of LLInt, but is still
otherwise unused by the DFG), so that if we ever need to walk the stack accurately
we know where to start. In particular, if the current ExecState* points several
semantic call frames away from the true semantic call frame because we had
performed inlining, having the code origin index recorded means that we can reify
those call frames as necessary to give runtime/library code an accurate view of
the current JS state.

This required several large but mechanical changes:

  • Calling a function from the DFG now plants a store32 instruction to store the code origin index. But the indices of code origins were previously picked by the DFG::JITCompiler after code generation completed. I changed this somewhat; even though the code origins are put into the CodeBlock after code gen, the code gen now knows a priori what their indices will be. Extensive assertions are in place to ensure that the two don't get out of sync, in the form of the DFG::CallBeginToken. Note that this mechanism has almost no effect on JS calls; those don't need the code origin index set in the call frame because we can get it by doing a binary search on the return PC.
  • Stack walking now always calls trueCallFrame() first before beginning the walk, since even the top call frame may be wrong. It still calls trueCallerFrame() as before to get to the next frame, though trueCallerFrame() is now mostly a wrapper around callerFrame()->trueCallFrame().


  • Because the mechanism for getting the code origin of a call frame is bimodal (either the call frame knows its code origin because the code origin index was set, or it's necessary to use the callee frame's return PC), I put in extra mechanisms to determine whether your caller, or your callee, corresponds to a call out of C++ code. Previously we just had the host call flag, but this is insufficient as it does not cover the case of someone calling JSC::call(). But luckily we can determine this just by looking at the return PC: if the return PC is in range of the ctiTrampiline, then two things are true: this call frame's PC will tell you nothing about where you came from in your caller, and the caller already knows where it's at because it must have set the code origin index (unless it's not DFG code, in which case we don't care because there is no inlining to worry about).


  • During testing this revealed a simple off-by-one goof in DFG::ByteCodeParser's inlining code, so I fixed it.
  • Finally because I was tired of doing random #if's for checking if I should be passing around an Instruction* or a ReturnAddressPtr, I created a class called AbstractPC that holds whatever notion of a PC is appropriate for the current execution environment. It's designed to work gracefully even if both the interpreter and the JIT are compiled in, and should integrate nicely with the LLInt.


This is neutral on all benchmarks and fixes some nasty corner-case regressions of
evil code that uses combinations of getters/setters and function.arguments.

(JSC::CodeBlock::codeOrigin):
(CodeBlock):

  • dfg/DFGByteCodeParser.cpp:

(JSC::DFG::ByteCodeParser::handleInlining):

  • dfg/DFGJITCompiler.cpp:

(JSC::DFG::JITCompiler::link):

  • dfg/DFGJITCompiler.h:

(CallBeginToken):
(JSC::DFG::CallBeginToken::CallBeginToken):
(JSC::DFG::CallBeginToken::assertCodeOriginIndex):
(JSC::DFG::CallBeginToken::assertNoCodeOriginIndex):
(DFG):
(JSC::DFG::CallExceptionRecord::CallExceptionRecord):
(CallExceptionRecord):
(JSC::DFG::JITCompiler::JITCompiler):
(JITCompiler):
(JSC::DFG::JITCompiler::nextCallBeginToken):
(JSC::DFG::JITCompiler::beginCall):
(JSC::DFG::JITCompiler::notifyCall):
(JSC::DFG::JITCompiler::addExceptionCheck):
(JSC::DFG::JITCompiler::addFastExceptionCheck):

  • dfg/DFGOperations.cpp:

():

  • dfg/DFGRepatch.cpp:

(JSC::DFG::tryBuildGetByIDList):

  • dfg/DFGSpeculativeJIT.h:

(JSC::DFG::SpeculativeJIT::appendCallWithExceptionCheck):

  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::emitCall):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::emitCall):

  • interpreter/AbstractPC.cpp: Added.

(JSC):
(JSC::AbstractPC::AbstractPC):

  • interpreter/AbstractPC.h: Added.

(JSC):
(AbstractPC):
(JSC::AbstractPC::AbstractPC):
(JSC::AbstractPC::hasJITReturnAddress):
(JSC::AbstractPC::jitReturnAddress):
(JSC::AbstractPC::hasInterpreterReturnAddress):
(JSC::AbstractPC::interpreterReturnAddress):
(JSC::AbstractPC::isSet):
(JSC::AbstractPC::operator!):
():

  • interpreter/CallFrame.cpp:

(JSC):
(JSC::CallFrame::trueCallFrame):
(JSC::CallFrame::trueCallerFrame):

  • interpreter/CallFrame.h:

(JSC::ExecState::abstractReturnPC):
(JSC::ExecState::codeOriginIndexForDFGWithInlining):
(ExecState):
(JSC::ExecState::trueCallFrame):
(JSC::ExecState::trueCallFrameFromVMCode):

  • interpreter/Interpreter.cpp:

(JSC::Interpreter::retrieveArgumentsFromVMCode):
(JSC::Interpreter::retrieveCallerFromVMCode):
(JSC::Interpreter::findFunctionCallFrameFromVMCode):

  • interpreter/Interpreter.h:

(Interpreter):
():

  • jit/JITStubs.cpp:

(JSC):
():

  • jit/JITStubs.h:

(JSC):
(JSC::returnAddressIsInCtiTrampoline):

  • runtime/JSFunction.cpp:

(JSC::JSFunction::argumentsGetter):
(JSC::JSFunction::callerGetter):
(JSC::JSFunction::getOwnPropertyDescriptor):

LayoutTests:

Reviewed by Geoff Garen.

  • fast/js/dfg-inline-arguments-use-directly-from-inlined-code-expected.txt: Added.
  • fast/js/dfg-inline-arguments-use-directly-from-inlined-code.html: Added.
  • fast/js/dfg-inline-arguments-use-from-all-the-places-broken-expected.txt: Added.
  • fast/js/dfg-inline-arguments-use-from-all-the-places-broken.html: Added.
  • fast/js/dfg-inline-arguments-use-from-all-the-places-expected.txt: Added.
  • fast/js/dfg-inline-arguments-use-from-all-the-places.html: Added.
  • fast/js/dfg-inline-arguments-use-from-getter-expected.txt: Added.
  • fast/js/dfg-inline-arguments-use-from-getter.html: Added.
  • fast/js/script-tests/dfg-inline-arguments-use-directly-from-inlined-code.js: Added.

(foo):
(bar):
(argsToStr):

  • fast/js/script-tests/dfg-inline-arguments-use-from-all-the-places-broken.js: Added.

(foo):
(fuzz):
(getter):
(bar):
(argsToStr):

  • fast/js/script-tests/dfg-inline-arguments-use-from-all-the-places.js: Added.

(foo):
(fuzz):
(getter):
(bar):
(argsToStr):

  • fast/js/script-tests/dfg-inline-arguments-use-from-getter.js: Added.

(foo):
(bar):
(argsToStr):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/interpreter/CallFrame.cpp

    r105894 r106067  
    6262    return calleeAsFunction->executable() != codeBlock()->ownerExecutable();
    6363}
     64
     65CallFrame* CallFrame::trueCallFrame(AbstractPC pc)
     66{
     67    // Am I an inline call frame? If so, we're done.
     68    if (isInlineCallFrame())
     69        return this;
     70   
     71    // If I don't have a code block, then I'm not DFG code, so I'm the true call frame.
     72    CodeBlock* machineCodeBlock = codeBlock();
     73    if (!machineCodeBlock)
     74        return this;
     75   
     76    // If the code block does not have any code origins, then there was no inlining, so
     77    // I'm done.
     78    if (!machineCodeBlock->hasCodeOrigins())
     79        return this;
     80   
     81    // At this point the PC must be due either to the DFG, or it must be unset.
     82    ASSERT(pc.hasJITReturnAddress() || !pc);
     83   
     84    // Try to determine the CodeOrigin. If we don't have a pc set then the only way
     85    // that this makes sense is if the CodeOrigin index was set in the call frame.
     86    // FIXME: Note that you will see "Not currently in inlined code" comments below.
     87    // Currently, we do not record code origins for code that is not inlined, because
     88    // the only thing that we use code origins for is determining the inline stack.
     89    // But in the future, we'll want to use this same functionality (having a code
     90    // origin mapping for any calls out of JIT code) to determine the PC at any point
     91    // in the stack even if not in inlined code. When that happens, the code below
     92    // will have to change the way it detects the presence of inlining: it will always
     93    // get a code origin, but sometimes, that code origin will not have an inline call
     94    // frame. In that case, this method should bail and return this.
     95    CodeOrigin codeOrigin;
     96    if (pc.isSet()) {
     97        ReturnAddressPtr currentReturnPC = pc.jitReturnAddress();
     98       
     99        if (!machineCodeBlock->codeOriginForReturn(currentReturnPC, codeOrigin))
     100            return this; // Not currently in inlined code.
     101    } else {
     102        unsigned index = codeOriginIndexForDFGWithInlining();
     103        if (index == UINT_MAX)
     104            return this; // Not currently in inlined code.
     105       
     106        codeOrigin = machineCodeBlock->codeOrigin(index);
     107    }
     108   
     109    for (InlineCallFrame* inlineCallFrame = codeOrigin.inlineCallFrame; inlineCallFrame;) {
     110        InlineCallFrame* nextInlineCallFrame = inlineCallFrame->caller.inlineCallFrame;
     111       
     112        CallFrame* inlinedCaller = this + inlineCallFrame->stackOffset;
     113       
     114        JSFunction* calleeAsFunction = inlineCallFrame->callee.get();
     115       
     116        // Fill in the inlinedCaller
     117        inlinedCaller->setCodeBlock(machineCodeBlock);
     118       
     119        inlinedCaller->setScopeChain(calleeAsFunction->scope());
     120        if (nextInlineCallFrame)
     121            inlinedCaller->setCallerFrame(this + nextInlineCallFrame->stackOffset);
     122        else
     123            inlinedCaller->setCallerFrame(this);
     124       
     125        inlinedCaller->setInlineCallFrame(inlineCallFrame);
     126        inlinedCaller->setArgumentCountIncludingThis(inlineCallFrame->arguments.size());
     127        inlinedCaller->setCallee(calleeAsFunction);
     128       
     129        inlineCallFrame = nextInlineCallFrame;
     130    }
     131   
     132    return this + codeOrigin.inlineCallFrame->stackOffset;
     133}
    64134       
    65135CallFrame* CallFrame::trueCallerFrame()
     
    71141    // machineCaller -> The caller according to the machine, which may be zero or
    72142    //    more frames above the true caller due to inlining.
    73     //
    74     // trueCaller -> The real caller.
    75143   
    76144    // Am I an inline call frame? If so, we're done.
     
    84152        return 0;
    85153    ASSERT(!machineCaller->isInlineCallFrame());
    86     if (!machineCaller->codeBlock())
    87         return machineCaller;
    88     if (!machineCaller->codeBlock()->hasCodeOrigins())
    89         return machineCaller; // No inlining, so machineCaller == trueCaller
    90154   
    91     // Figure out where the caller frame would have gone relative to the machine
    92     // caller, and rematerialize it. Do so for the entire inline stack.
     155    // Figure out how we want to get the current code location.
     156    if (hasHostCallFrameFlag() || returnAddressIsInCtiTrampoline(returnPC()))
     157        return machineCaller->trueCallFrameFromVMCode();
    93158   
    94     ReturnAddressPtr currentReturnPC = returnPC();
    95     CodeBlock* machineCodeBlock = machineCaller->codeBlock();
    96    
    97     CodeOrigin codeOrigin;
    98     if (!machineCodeBlock->codeOriginForReturn(currentReturnPC, codeOrigin))
    99         return machineCaller; // Not currently in inlined code, so machineCaller == trueCaller
    100    
    101     for (InlineCallFrame* inlineCallFrame = codeOrigin.inlineCallFrame; inlineCallFrame;) {
    102         InlineCallFrame* nextInlineCallFrame = inlineCallFrame->caller.inlineCallFrame;
    103        
    104         CallFrame* inlinedCaller = machineCaller + inlineCallFrame->stackOffset;
    105        
    106         JSFunction* calleeAsFunction = inlineCallFrame->callee.get();
    107        
    108         // Fill in the inlinedCaller
    109         inlinedCaller->setCodeBlock(machineCaller->codeBlock());
    110        
    111         inlinedCaller->setScopeChain(calleeAsFunction->scope());
    112         if (nextInlineCallFrame)
    113             inlinedCaller->setCallerFrame(machineCaller + nextInlineCallFrame->stackOffset);
    114         else
    115             inlinedCaller->setCallerFrame(machineCaller);
    116        
    117         inlinedCaller->setInlineCallFrame(inlineCallFrame);
    118         inlinedCaller->setArgumentCountIncludingThis(inlineCallFrame->arguments.size());
    119         inlinedCaller->setCallee(calleeAsFunction);
    120        
    121         inlineCallFrame = nextInlineCallFrame;
    122     }
    123    
    124     return machineCaller + codeOrigin.inlineCallFrame->stackOffset;
     159    return machineCaller->trueCallFrame(returnPC());
    125160}
    126161#endif
Note: See TracChangeset for help on using the changeset viewer.