Title: [161180] branches/jsCStack/Source/_javascript_Core
Revision
161180
Author
[email protected]
Date
2013-12-31 02:26:49 -0800 (Tue, 31 Dec 2013)

Log Message

CStack: Need a separate stack limit for the JS stack and the C stack.
https://bugs.webkit.org/show_bug.cgi?id=126320.

Not yet reviewed.

With this patch, we now accurately track how much JS stack space the
VM has used, and cap that at the value specified by Options::maxStackSize().

The JS stack space is measured in chunks, one for each VMEntryScope. Each
VMEntryScope will also keep a running total of the sum of all the stack
usage of previous VMEntryScopes to simplify the calculation of the total
usage.

* interpreter/Interpreter.cpp:
(JSC::Interpreter::execute):
- In Interpreter::execute() for programs, we were installing the VMEntryScope
  even if we end up only processing a JSON object. This poses a problem
  because the code that processes the JSON object will call functions that
  will re-enter the VM thereby installing another VMEntryScope. Each
  VMEntryScope expects VM.topCallFrame and VM.stackPointerAtVMEntry to be
  set up properly for the previous VMEntryScope. However, in this case,
  we've installed the VMEntryScope for a potential ProgramExecutable but
  never executed a program to initialize its values for VM.topCallFrame and
  VM.stackPointerAtVMEntry. This in turn causes a crash when the second
  VMEntryScope makes use of VM.topCallFrame.
      The fix is simply to defer installation of the VMEntryScope for the
  program till we are actually sure that we will attempt to execute a
  program.

* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::LLINT_SLOW_PATH_DECL(stack_check)):
- In llint_stack_check(), we pop the frame that we failed to set up before
  throwing the StackOverflowError. Added an update of topCallFrame here to
  reflect this popping action.

* llint/LowLevelInterpreter64.asm:
- The VMEntryScope will have to calculate and set up a jsStackLimit value
  before we actually know what the stack pointer is at the point when we
  re-enter the VM. So, instead of using the real stackPointerAtVMEntry for
  the jsStackLimit calculation, we use an estimate.
      doCallToJavaScript() here will set VM.stackPointerAtVMEntry to its
  real value. But before it does that, it needs to adjust VM.jsStackLimit
  by the delta between the estimate and the real stackPointerAtVMEntry.

- If doCallToJavaScript() fails its stack check for incoming args, it also
  needs to clear VM.stackPointerAtVMEntry so that C code can assert that
  the various pointers into the stack remain consistent.

  By consistent, I mean that, given that the stack grows down, and that
  VM.stackPointerAtVMEntry and VM.topCallFrame are properly initialized,
  then the following should always be true:

      let topOfStack = VM.topCallFrame->topOfFrame();
      stackPointerAtVMEntry >= topOfStack >= stack pointer in a C helper function
  and
      JSStack.containsAddress(stackPointerAtVMEntry)
  and
      JSStack.containsAddress(topOfStack)

  Clearing VM.stackPointerAtVMEntry, if the stack check in doCallToJavaScript()
  fails, indicates we failed to enter the current VMEntryScope and that we
  should be using the previous VMEntryScope's stackPointerAtVMEntry and
  topCallFrame for the above assertions.

  NOTE: These assertions are done in VMEntryScope::updateStackLimits().

* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:
* runtime/VMEntryScope.cpp:
(JSC::VMEntryScope::VMEntryScope):
(JSC::VMEntryScope::~VMEntryScope):
(JSC::VMEntryScope::stackUsageFor):
(JSC::VMEntryScope::updateStackLimits):
(JSC::VMEntryScope::currentStackPointer):
(JSC::VMEntryScope::requiredCapacity):
- requiredCapacity() now computes the excess amount C stack capacity that
  we have available, and discount it in the computation of the JS stack
  limit. By excess, I mean the amount of unused C stack space that
  exceeds the amount of unused JS stack space capacity.

* runtime/VMEntryScope.h:

Modified Paths

Diff

Modified: branches/jsCStack/Source/_javascript_Core/ChangeLog (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/ChangeLog	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/ChangeLog	2013-12-31 10:26:49 UTC (rev 161180)
@@ -1,3 +1,88 @@
+2013-12-31  Mark Lam  <[email protected]>
+
+        CStack: Need a separate stack limit for the JS stack and the C stack.
+        https://bugs.webkit.org/show_bug.cgi?id=126320.
+
+        Not yet reviewed.
+
+        With this patch, we now accurately track how much JS stack space the
+        VM has used, and cap that at the value specified by Options::maxStackSize().
+
+        The JS stack space is measured in chunks, one for each VMEntryScope. Each
+        VMEntryScope will also keep a running total of the sum of all the stack
+        usage of previous VMEntryScopes to simplify the calculation of the total
+        usage.
+
+        * interpreter/Interpreter.cpp:
+        (JSC::Interpreter::execute):
+        - In Interpreter::execute() for programs, we were installing the VMEntryScope
+          even if we end up only processing a JSON object. This poses a problem
+          because the code that processes the JSON object will call functions that
+          will re-enter the VM thereby installing another VMEntryScope. Each
+          VMEntryScope expects VM.topCallFrame and VM.stackPointerAtVMEntry to be
+          set up properly for the previous VMEntryScope. However, in this case,
+          we've installed the VMEntryScope for a potential ProgramExecutable but
+          never executed a program to initialize its values for VM.topCallFrame and
+          VM.stackPointerAtVMEntry. This in turn causes a crash when the second
+          VMEntryScope makes use of VM.topCallFrame.
+              The fix is simply to defer installation of the VMEntryScope for the
+          program till we are actually sure that we will attempt to execute a
+          program.
+
+        * llint/LLIntSlowPaths.cpp:
+        (JSC::LLInt::LLINT_SLOW_PATH_DECL(stack_check)):
+        - In llint_stack_check(), we pop the frame that we failed to set up before
+          throwing the StackOverflowError. Added an update of topCallFrame here to
+          reflect this popping action.
+
+        * llint/LowLevelInterpreter64.asm:
+        - The VMEntryScope will have to calculate and set up a jsStackLimit value
+          before we actually know what the stack pointer is at the point when we
+          re-enter the VM. So, instead of using the real stackPointerAtVMEntry for
+          the jsStackLimit calculation, we use an estimate.
+              doCallToJavaScript() here will set VM.stackPointerAtVMEntry to its
+          real value. But before it does that, it needs to adjust VM.jsStackLimit
+          by the delta between the estimate and the real stackPointerAtVMEntry.
+
+        - If doCallToJavaScript() fails its stack check for incoming args, it also
+          needs to clear VM.stackPointerAtVMEntry so that C code can assert that
+          the various pointers into the stack remain consistent.
+
+          By consistent, I mean that, given that the stack grows down, and that
+          VM.stackPointerAtVMEntry and VM.topCallFrame are properly initialized,
+          then the following should always be true:
+
+              let topOfStack = VM.topCallFrame->topOfFrame();
+              stackPointerAtVMEntry >= topOfStack >= stack pointer in a C helper function
+          and
+              JSStack.containsAddress(stackPointerAtVMEntry)
+          and
+              JSStack.containsAddress(topOfStack)
+
+          Clearing VM.stackPointerAtVMEntry, if the stack check in doCallToJavaScript()
+          fails, indicates we failed to enter the current VMEntryScope and that we
+          should be using the previous VMEntryScope's stackPointerAtVMEntry and
+          topCallFrame for the above assertions.
+
+          NOTE: These assertions are done in VMEntryScope::updateStackLimits().
+
+        * runtime/VM.cpp:
+        (JSC::VM::VM):
+        * runtime/VM.h:
+        * runtime/VMEntryScope.cpp:
+        (JSC::VMEntryScope::VMEntryScope):
+        (JSC::VMEntryScope::~VMEntryScope):
+        (JSC::VMEntryScope::stackUsageFor):
+        (JSC::VMEntryScope::updateStackLimits):
+        (JSC::VMEntryScope::currentStackPointer):
+        (JSC::VMEntryScope::requiredCapacity):
+        - requiredCapacity() now computes the excess amount C stack capacity that
+          we have available, and discount it in the computation of the JS stack
+          limit. By excess, I mean the amount of unused C stack space that
+          exceeds the amount of unused JS stack space capacity.
+
+        * runtime/VMEntryScope.h:
+
 2013-12-30  Mark Lam  <[email protected]>
 
         CStack: the GC should not be assuming any limit on the stack size.

Modified: branches/jsCStack/Source/_javascript_Core/interpreter/Interpreter.cpp (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/interpreter/Interpreter.cpp	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/interpreter/Interpreter.cpp	2013-12-31 10:26:49 UTC (rev 161180)
@@ -770,7 +770,6 @@
     if (vm.isCollectorBusy())
         return jsNull();
 
-    VMEntryScope entryScope(vm, scope->globalObject());
     if (!vm.isSafeToRecurse())
         return checkedReturn(throwStackOverflowError(callFrame));
 
@@ -877,6 +876,8 @@
     // If we get here, then we have already proven that the script is not a JSON
     // object.
 
+    VMEntryScope entryScope(vm, scope->globalObject());
+
     // Compile source to bytecode if necessary:
     if (JSObject* error = program->initializeGlobalProperties(vm, callFrame, scope))
         return checkedReturn(callFrame->vm().throwException(callFrame, error));

Modified: branches/jsCStack/Source/_javascript_Core/llint/LLIntSlowPaths.cpp (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/llint/LLIntSlowPaths.cpp	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/llint/LLIntSlowPaths.cpp	2013-12-31 10:26:49 UTC (rev 161180)
@@ -458,12 +458,13 @@
     // Hence, if we get here, then we know a stack overflow is imminent. So, just
     // throw the StackOverflowError unconditionally.
 #if ENABLE(LLINT_C_LOOP)
-    ASSERT(!exec->vm().interpreter->stack().containsAddress(exec->topOfFrame()));
+    ASSERT(!vm.interpreter->stack().containsAddress(exec->topOfFrame()));
     if (LIKELY(vm.interpreter->stack().ensureCapacityFor(exec->topOfFrame())))
         LLINT_RETURN_TWO(pc, 0);
 #endif
 
     exec = exec->callerFrame();
+    vm.topCallFrame = exec;
     Interpreter::ErrorHandlingMode mode(exec);
     CommonSlowPaths::interpreterThrowInCaller(exec, createStackOverflowError(exec));
     pc = returnToThrowForThrownException(exec);

Modified: branches/jsCStack/Source/_javascript_Core/llint/LowLevelInterpreter64.asm (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/llint/LowLevelInterpreter64.asm	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/llint/LowLevelInterpreter64.asm	2013-12-31 10:26:49 UTC (rev 161180)
@@ -179,6 +179,15 @@
 
     checkStackPointerAlignment(temp2, 0xbad0dc01)
 
+    # The jsStackLimit was previously computed in VMEntryScope using an
+    # estimated stackPointerAtVMEntry value. Adjust the jsStackLimit by
+    # the delta between the actual stackPointerAtVMEntry and the estimate
+    # that we used previously.
+    subp VM::stackPointerAtVMEntry[vm], sp, temp2
+    subp VM::m_jsStackLimit[vm], temp2, temp2
+    storep temp2, VM::m_jsStackLimit[vm]
+    storep sp, VM::stackPointerAtVMEntry[vm]
+
     # The stack host zone ensures that we have adequate space for the
     # VMEntrySentinelFrame. Proceed with allocating and initializing the
     # sentinel frame.
@@ -210,6 +219,7 @@
     # up throwing a StackOverflowError.
     end
 
+    storep 0, VM::stackPointerAtVMEntry[vm]
     cCall2(_llint_throw_stack_overflow_error, vm, protoCallFrame)
     callToJavaScriptEpilogue()
     ret

Modified: branches/jsCStack/Source/_javascript_Core/runtime/VM.cpp (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/runtime/VM.cpp	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/runtime/VM.cpp	2013-12-31 10:26:49 UTC (rev 161180)
@@ -167,6 +167,7 @@
     , vmType(vmType)
     , clientData(0)
     , topCallFrame(CallFrame::noCaller())
+    , stackPointerAtVMEntry(0)
     , arrayConstructorTable(adoptPtr(new HashTable(JSC::arrayConstructorTable)))
     , arrayPrototypeTable(adoptPtr(new HashTable(JSC::arrayPrototypeTable)))
     , booleanPrototypeTable(adoptPtr(new HashTable(JSC::booleanPrototypeTable)))

Modified: branches/jsCStack/Source/_javascript_Core/runtime/VM.h (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/runtime/VM.h	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/runtime/VM.h	2013-12-31 10:26:49 UTC (rev 161180)
@@ -227,6 +227,7 @@
         VMType vmType;
         ClientData* clientData;
         ExecState* topCallFrame;
+        void* stackPointerAtVMEntry;
         Watchdog watchdog;
 
         const OwnPtr<const HashTable> arrayConstructorTable;

Modified: branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.cpp (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.cpp	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.cpp	2013-12-31 10:26:49 UTC (rev 161180)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "VMEntryScope.h"
 
+#include "Options.h"
 #include "VM.h"
 #include <wtf/StackBounds.h>
 
@@ -35,6 +36,7 @@
     : m_vm(vm)
     , m_stack(wtfThreadData().stack())
     , m_globalObject(globalObject)
+    , m_prevStackUsage(0)
     , m_prevFirstEntryScope(vm.firstEntryScope)
     , m_prevTopEntryScope(vm.topEntryScope)
     , m_prevStackLimit(vm.stackLimit())
@@ -42,7 +44,20 @@
     , m_prevJSStackLimit(vm.jsStackLimit())
 #endif
     , m_prevLastStackTop(vm.lastStackTop())
+    , m_prevStackPointerAtVMEntry(vm.stackPointerAtVMEntry)
+    , m_prevTopCallFrame(vm.topCallFrame)
 {
+    ASSERT(wtfThreadData().stack().isGrowingDownward());
+    ASSERT(!vm.topCallFrame || currentStackPointer() <= reinterpret_cast<char*>(vm.topCallFrame->topOfFrame()));
+
+    // Step 1: Compute the stack usage of the last VM entry before we install
+    // the current entry scope below.
+    if (vm.topEntryScope) {
+        char* topOfStack = reinterpret_cast<char*>(vm.topCallFrame->topOfFrame());
+        m_prevStackUsage = vm.topEntryScope->stackUsageFor(topOfStack);
+    }
+
+    // Step 2: Install the current entry scope.
     if (!vm.firstEntryScope) {
 #if ENABLE(ASSEMBLER)
         if (ExecutableAllocator::underMemoryPressure())
@@ -54,13 +69,16 @@
         // observe time xone changes.
         vm.resetDateCache();
     }
+    vm.stackPointerAtVMEntry = 0;
     vm.topEntryScope = this;
 
     // Clear the captured exception stack between entries
     vm.clearExceptionStack();
 
+    vm.setLastStackTop(m_stack.origin());
+
+    // Step 3: Compute the stack limit using the installed entry scope.
     updateStackLimits();
-    vm.setLastStackTop(m_stack.origin());
 }
 
 VMEntryScope::~VMEntryScope()
@@ -72,21 +90,88 @@
     m_vm.setJSStackLimit(m_prevJSStackLimit);
 #endif
     m_vm.setLastStackTop(m_prevLastStackTop);
+    m_vm.stackPointerAtVMEntry = m_prevStackPointerAtVMEntry;
+    m_vm.topCallFrame = m_prevTopCallFrame;
 }
 
+size_t VMEntryScope::stackUsageFor(char* topOfStack) const
+{
+    size_t currentStackUsage = 0;
+    ASSERT(m_vm.stackPointerAtVMEntry);
+    char* startOfStack = reinterpret_cast<char*>(m_vm.stackPointerAtVMEntry);
+    ASSERT(topOfStack <= startOfStack);
+    currentStackUsage = startOfStack - topOfStack;
+
+    ASSERT(Options::maxStackSize() >= m_prevStackUsage + currentStackUsage);
+    return m_prevStackUsage + currentStackUsage;
+}
+
 void VMEntryScope::updateStackLimits()
 {
+    ASSERT(wtfThreadData().stack().isGrowingDownward());
+    char* topOfStack = currentStackPointer();
+
 #if !ENABLE(LLINT_C_LOOP)
-    void* jsStackLimit = m_stack.recursionLimit(requiredCapacity(JSStackCapacity));
+    char* topOfJSStack = m_vm.topCallFrame ? reinterpret_cast<char*>(m_vm.topCallFrame->topOfFrame()) : topOfStack;
+
+    // If we have not re-entered the VM yet via callToJavaScript / callToNativeFunction,
+    // then stackPointerAtVMEntry will not have been set up yet. Instead, we'll
+    // compute the stack limit relative to the current topOfJSStack (as an estimate
+    // of stackPointerAtVMEntry). When we enter callToJavaScript later, we'll adjust
+    // the stack limit with the delta between the actual stackPointerAtVMEntry and
+    // the estimate value that we use here.
+    if (!m_vm.stackPointerAtVMEntry)
+        m_vm.stackPointerAtVMEntry = topOfJSStack;
+
+    void* jsStackLimit = m_stack.recursionLimit(requiredCapacity(topOfJSStack, JSStackCapacity));
+#ifndef NDEBUG
+    char* startOfStack = reinterpret_cast<char*>(m_vm.stackPointerAtVMEntry);
+    char* stackLimit = reinterpret_cast<char*>(jsStackLimit);
+    ASSERT(m_prevStackUsage + (startOfStack - stackLimit) <= Options::maxStackSize());
+#endif
     m_vm.setJSStackLimit(jsStackLimit);
-#endif
-    void* nativeStackLimit = m_stack.recursionLimit(requiredCapacity(NativeStackCapacity));
+
+    // Some sanity checks for our pointers into the stack:
+    ASSERT(m_vm.interpreter->stack().containsAddress(reinterpret_cast<Register*>(m_vm.stackPointerAtVMEntry)));
+    ASSERT(m_vm.interpreter->stack().containsAddress(reinterpret_cast<Register*>(topOfJSStack)));
+    ASSERT(currentStackPointer() <= topOfJSStack);
+    ASSERT(topOfJSStack <= m_vm.stackPointerAtVMEntry);
+#endif // !ENABLE(LLINT_C_LOOP)
+
+    void* nativeStackLimit = m_stack.recursionLimit(requiredCapacity(topOfStack, NativeStackCapacity));
     m_vm.setStackLimit(nativeStackLimit);
 }
 
-size_t VMEntryScope::requiredCapacity(CapacityType type) const
+char* VMEntryScope::currentStackPointer() const
 {
+    char* p;
+#if ENABLE(LLINT_C_LOOP)
+    p = reinterpret_cast<char*>(m_vm.topCallFrame->topOfFrame());
+#else
+    p = reinterpret_cast<char*>(&p);
+#endif
+    return p;
+}
+
+size_t VMEntryScope::requiredCapacity(char* topOfStack, CapacityType type) const
+{
+    ASSERT(m_stack.isGrowingDownward());
+
+    size_t excessCStackSize = 0;
+#if !ENABLE(LLINT_C_LOOP)
+    if (type == JSStackCapacity) {
+        ASSERT(Options::maxStackSize() >= stackUsageFor(topOfStack));
+        size_t availableJSStack = Options::maxStackSize() - stackUsageFor(topOfStack);
+
+        char* bottomOfStack = reinterpret_cast<char*>(m_stack.origin());
+        size_t availableCStack = m_stack.size() - (bottomOfStack - topOfStack);
+        if (availableCStack > availableJSStack)
+            excessCStackSize = availableCStack - availableJSStack;
+    }
+#else
     UNUSED_PARAM(type);
+    ASSERT(type == NativeStackCapacity);
+#endif
 
     // We require a smaller stack budget for the error stack. This is to allow
     // some minimal JS execution to proceed and do the work of throwing a stack
@@ -100,6 +185,8 @@
 
     Interpreter* interpreter = m_vm.interpreter;
     size_t requiredCapacity = interpreter->isInErrorHandlingMode() ? errorModeRequiredStack : requiredStack;
+    requiredCapacity += excessCStackSize;
+
     RELEASE_ASSERT(m_stack.size() >= requiredCapacity);
     return requiredCapacity; 
 }

Modified: branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.h (161179 => 161180)


--- branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.h	2013-12-31 08:34:32 UTC (rev 161179)
+++ branches/jsCStack/Source/_javascript_Core/runtime/VMEntryScope.h	2013-12-31 10:26:49 UTC (rev 161180)
@@ -48,12 +48,15 @@
         JSStackCapacity,
         NativeStackCapacity,
     };
-    size_t requiredCapacity(CapacityType) const;
+    size_t requiredCapacity(char* topOfStack, CapacityType) const;
+    char* currentStackPointer() const;
+    size_t stackUsageFor(char* topOfStack) const;
 
     VM& m_vm;
     StackStats::CheckPoint m_stackCheckPoint;
     StackBounds m_stack;
     JSGlobalObject* m_globalObject;
+    size_t m_prevStackUsage;
 
     // The following pointers may point to a different thread's stack.
     VMEntryScope* m_prevFirstEntryScope;
@@ -63,6 +66,8 @@
     void* m_prevJSStackLimit;
 #endif
     void* m_prevLastStackTop;
+    void* m_prevStackPointerAtVMEntry;
+    ExecState* m_prevTopCallFrame;
 };
 
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to