Skip to content

Commit

Permalink
Revise computing and using stack limit
Browse files Browse the repository at this point in the history
* Compute stack limit correctly through pthread API or Windows internal API
* Store stack limit in TLS(or global) not a VMInstance or ExecutionState

Signed-off-by: Seonghyun Kim <[email protected]>
  • Loading branch information
ksh8281 committed Nov 16, 2023
1 parent 3218351 commit 9f802bf
Show file tree
Hide file tree
Showing 26 changed files with 143 additions and 223 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/es-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ jobs:
env:
GC_FREE_SPACE_DIVISOR: 1
run: $RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/release/x86/escargot" ${{ matrix.tc }}
- if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
timeout-minutes: 15


build-test-on-x64-release:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -330,6 +334,9 @@ jobs:
export LANG=en_US.UTF-8
locale
$RUNNER --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/release/x64/escargot" ${{ matrix.tc }}
- if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
timeout-minutes: 15

build-test-on-x86-x64-debug:
runs-on: ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion build/target.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ ELSEIF (${ESCARGOT_HOST} STREQUAL "darwin" AND ${ESCARGOT_ARCH} STREQUAL "x64")
SET (ENV{PKG_CONFIG_PATH} "/usr/local/opt/icu4c/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
ELSEIF (${ESCARGOT_HOST} STREQUAL "windows")
SET (ESCARGOT_LDFLAGS ${ESCARGOT_LDFLAGS} icu.lib)
SET (ESCARGOT_CXXFLAGS ${ESCARGOT_CXXFLAGS} -DSTACK_LIMIT_FROM_BASE=1048576) # in windows, default stack limit is 1MB
IF ((${ESCARGOT_ARCH} STREQUAL "x64") OR (${ESCARGOT_ARCH} STREQUAL "x86_64"))
SET (ESCARGOT_BUILD_64BIT ON)
SET (ESCARGOT_BUILD_64BIT_LARGE ON)
Expand Down
17 changes: 13 additions & 4 deletions src/Escargot.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,18 +523,26 @@ typedef uint16_t LexicalBlockIndex;
#define STACK_GROWS_DOWN
#endif

#ifndef STACK_LIMIT_FROM_BASE
#define STACK_LIMIT_FROM_BASE (1024 * 1024 * 3) // 3MB
#ifndef STACK_FREESPACE_FROM_LIMIT
#define STACK_FREESPACE_FROM_LIMIT (1024 * 256) // 256KB
#endif

#ifndef STACK_USAGE_LIMIT
#ifdef ESCARGOT_ENABLE_TEST
#define STACK_USAGE_LIMIT (1024 * 1024 * 2) // 2MB
#else
#define STACK_USAGE_LIMIT (1024 * 1024 * 4) // 4MB
#endif
#endif

#ifdef STACK_GROWS_DOWN
#define CHECK_STACK_OVERFLOW(state) \
if (UNLIKELY(state.stackLimit() > (size_t)currentStackPointer())) { \
if (UNLIKELY(ThreadLocal::stackLimit() > (size_t)currentStackPointer())) { \
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Maximum call stack size exceeded"); \
}
#else
#define CHECK_STACK_OVERFLOW(state) \
if (UNLIKELY(state.stackLimit() < (size_t)currentStackPointer())) { \
if (UNLIKELY(ThreadLocal::stackLimit() < (size_t)currentStackPointer())) { \
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Maximum call stack size exceeded"); \
}
#endif
Expand Down Expand Up @@ -581,5 +589,6 @@ using HashMap = tsl::robin_map<Key, T, Hash, KeyEqual, Allocator, StoreHash, Gro
#include "heap/Heap.h"
#include "util/Util.h"
#include "util/Optional.h"
#include "runtime/ThreadLocal.h"

#endif
9 changes: 3 additions & 6 deletions src/api/EscargotPublic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3065,17 +3065,14 @@ StackOverflowDisabler::StackOverflowDisabler(ExecutionStateRef* es)

// We assume that StackOverflowDisabler should not be nested-called
ASSERT(m_originStackLimit != newStackLimit);
ASSERT(m_originStackLimit == state->context()->vmInstance()->stackLimit());
state->m_stackLimit = newStackLimit;
state->context()->vmInstance()->m_stackLimit = newStackLimit;
ASSERT(m_originStackLimit == ThreadLocal::stackLimit());
ThreadLocal::g_stackLimit = newStackLimit;
}

StackOverflowDisabler::~StackOverflowDisabler()
{
ASSERT(!!m_executionState);
ExecutionState* state = toImpl(m_executionState);
state->m_stackLimit = m_originStackLimit;
state->context()->vmInstance()->m_stackLimit = m_originStackLimit;
ThreadLocal::g_stackLimit = m_originStackLimit;
}

OptionalRef<FunctionObjectRef> ExecutionStateRef::resolveCallee()
Expand Down
2 changes: 1 addition & 1 deletion src/api/EscargotPublic.h
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ class ESCARGOT_EXPORT Evaluator {
};

// temporally disable StackOverflow check
// StackOverflowDisabler only unlocks the predefined stack limit (STACK_LIMIT_FROM_BASE: 3MB)
// StackOverflowDisabler only unlocks the predefined stack limit (STACK_FREESPACE_FROM_LIMIT: 128KB)
// should be carefully used because it cannot prevent the system-stackoverflow exception
class ESCARGOT_EXPORT StackOverflowDisabler {
public:
Expand Down
5 changes: 2 additions & 3 deletions src/interpreter/ByteCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ ByteCodeGenerateContext::ByteCodeGenerateContext(InterpretedCodeBlock* codeBlock
, m_codeBlock(codeBlock)
, m_byteCodeBlock(byteCodeBlock)
, m_locData(nullptr)
, m_stackLimit(codeBlock->context()->vmInstance()->stackLimit())
, m_isGlobalScope(isGlobalScope)
, m_isEvalCode(isEvalCode)
, m_isOutermostContext(true)
Expand Down Expand Up @@ -313,9 +312,9 @@ void ByteCodeGenerator::collectByteCodeLOCData(Context* context, InterpretedCode
Node* ast = nullptr;
if (codeBlock->isGlobalCodeBlock() || codeBlock->isEvalCode()) {
ast = esprima::parseProgram(context, codeBlock->src(), esprima::generateClassInfoFrom(context, codeBlock->parent()),
codeBlock->script()->isModule(), codeBlock->isStrict(), codeBlock->inWith(), SIZE_MAX, false, false, false, true);
codeBlock->script()->isModule(), codeBlock->isStrict(), codeBlock->inWith(), false, false, false, true);
} else {
ast = esprima::parseSingleFunction(context, codeBlock, SIZE_MAX);
ast = esprima::parseSingleFunction(context, codeBlock);
}

// Generate ByteCode
Expand Down
24 changes: 13 additions & 11 deletions src/interpreter/ByteCodeGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ struct ByteCodeGenerateContext {
, m_codeBlock(contextBefore.m_codeBlock)
, m_byteCodeBlock(contextBefore.m_byteCodeBlock)
, m_locData(contextBefore.m_locData)
, m_stackLimit(contextBefore.m_stackLimit)
, m_isGlobalScope(contextBefore.m_isGlobalScope)
, m_isEvalCode(contextBefore.m_isEvalCode)
, m_isOutermostContext(false)
Expand Down Expand Up @@ -109,6 +108,7 @@ struct ByteCodeGenerateContext {
#endif /* ESCARGOT_DEBUGGER */
{
ASSERT(m_complexJumpBreakIgnoreCount == m_complexJumpContinueIgnoreCount);
checkStack();
}

~ByteCodeGenerateContext()
Expand Down Expand Up @@ -226,14 +226,7 @@ struct ByteCodeGenerateContext {
if (UNLIKELY(m_baseRegisterCount >= REGULAR_REGISTER_LIMIT)) {
throw "register limit exceed while generate byte code";
}
#ifdef STACK_GROWS_DOWN
if (UNLIKELY(m_stackLimit > (size_t)currentStackPointer())) {
#else
if (UNLIKELY(m_stackLimit < (size_t)currentStackPointer())) {
#endif
throw "native stack limit exceed while generate byte code";
}

checkStack();
m_registerStack->push_back(m_baseRegisterCount);
m_baseRegisterCount++;
return m_registerStack->back();
Expand Down Expand Up @@ -328,6 +321,17 @@ struct ByteCodeGenerateContext {
}
#endif

ALWAYS_INLINE void checkStack()
{
#ifdef STACK_GROWS_DOWN
if (UNLIKELY(ThreadLocal::stackLimit() > (size_t)currentStackPointer())) {
#else
if (UNLIKELY(ThreadLocal::stackLimit() < (size_t)currentStackPointer())) {
#endif
throw "native stack limit exceed while generate byte code";
}
}

#ifdef ESCARGOT_DEBUGGER
void calculateBreakpointLocation(size_t index, ExtendedNodeLOC sourceElementStart);
void insertBreakpoint(size_t index, Node* node);
Expand All @@ -341,8 +345,6 @@ struct ByteCodeGenerateContext {
ByteCodeBlock* m_byteCodeBlock;
std::vector<std::pair<size_t, size_t>, std::allocator<std::pair<size_t, size_t>>>* m_locData; // used only for calculating location info

size_t m_stackLimit;

bool m_isGlobalScope : 1;
bool m_isEvalCode : 1;
bool m_isOutermostContext : 1;
Expand Down
12 changes: 6 additions & 6 deletions src/parser/Script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,12 @@ Value Script::execute(ExecutionState& state, bool isExecuteOnEvalFunction, bool
ExecutionState* newState;
if (LIKELY(!m_topCodeBlock->isAsync())) {
if (byteCodeBlock->needsExtendedExecutionState()) {
newState = new (alloca(sizeof(ExtendedExecutionState))) ExtendedExecutionState(context(), state.stackLimit());
newState = new (alloca(sizeof(ExtendedExecutionState))) ExtendedExecutionState(context());
} else {
newState = new (alloca(sizeof(ExecutionState))) ExecutionState(context(), state.stackLimit());
newState = new (alloca(sizeof(ExecutionState))) ExecutionState(context());
}
} else {
newState = new ExtendedExecutionState(context(), nullptr, nullptr, 0, nullptr, false, ExtendedExecutionState::ForPauser);
newState = new ExtendedExecutionState(context(), nullptr, nullptr, 0, nullptr, false);
}
ExecutionState* codeExecutionState = newState;

Expand Down Expand Up @@ -1088,12 +1088,12 @@ Script::ModuleExecutionResult Script::moduleExecute(ExecutionState& state, Optio
ExecutionState* newState;
if (LIKELY(!m_topCodeBlock->isAsync())) {
if (byteCodeBlock->needsExtendedExecutionState()) {
newState = new (alloca(sizeof(ExtendedExecutionState))) ExtendedExecutionState(context(), state.stackLimit());
newState = new (alloca(sizeof(ExtendedExecutionState))) ExtendedExecutionState(context());
} else {
newState = new (alloca(sizeof(ExecutionState))) ExecutionState(context(), state.stackLimit());
newState = new (alloca(sizeof(ExecutionState))) ExecutionState(context());
}
} else {
newState = new ExtendedExecutionState(context(), nullptr, nullptr, 0, nullptr, false, ExtendedExecutionState::ForPauser);
newState = new ExtendedExecutionState(context(), nullptr, nullptr, 0, nullptr, false);
}
newState->setLexicalEnvironment(new LexicalEnvironment(moduleData()->m_moduleRecord, globalLexicalEnv), true);

Expand Down
12 changes: 6 additions & 6 deletions src/parser/ScriptParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ void ScriptParser::deleteCodeBlockCacheInfo()
}
#endif

ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* originSource, size_t originLineOffset, String* source, String* srcName, InterpretedCodeBlock* parentCodeBlock, bool isModule, bool isEvalMode, bool isEvalCodeInFunction, bool inWithOperation, bool strictFromOutside, bool allowSuperCall, bool allowSuperProperty, bool allowNewTarget, bool needByteCodeGeneration, size_t stackSizeRemain)
ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* originSource, size_t originLineOffset, String* source, String* srcName, InterpretedCodeBlock* parentCodeBlock, bool isModule, bool isEvalMode, bool isEvalCodeInFunction, bool inWithOperation, bool strictFromOutside, bool allowSuperCall, bool allowSuperProperty, bool allowNewTarget, bool needByteCodeGeneration)
{
ASSERT(m_context->astAllocator().isInitialized());

Expand Down Expand Up @@ -366,7 +366,7 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
ASTClassInfo* outerClassInfo = esprima::generateClassInfoFrom(m_context, parentCodeBlock);

programNode = esprima::parseProgram(m_context, sourceView, outerClassInfo,
isModule, strictFromOutside, inWith, stackSizeRemain, allowSC, allowSP, allowNewTarget, allowArguments);
isModule, strictFromOutside, inWith, allowSC, allowSP, allowNewTarget, allowArguments);

script = new Script(srcName, source, programNode->moduleData(), !parentCodeBlock, originLineOffset);
if (parentCodeBlock) {
Expand Down Expand Up @@ -460,7 +460,7 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
return result;
}

void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCodeBlock* codeBlock, size_t stackSizeRemain)
void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCodeBlock* codeBlock)
{
#ifdef ESCARGOT_DEBUGGER
// When the debugger is enabled, lazy compilation is disabled, so the functions are compiled
Expand All @@ -475,7 +475,7 @@ void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCo

// Parsing
try {
functionNode = esprima::parseSingleFunction(m_context, codeBlock, stackSizeRemain);
functionNode = esprima::parseSingleFunction(m_context, codeBlock);
} catch (esprima::Error* orgError) {
// reset ASTAllocator
m_context->astAllocator().reset();
Expand Down Expand Up @@ -538,7 +538,7 @@ void ScriptParser::recursivelyGenerateChildrenByteCode(InterpretedCodeBlock* par
InterpretedCodeBlock* codeBlock = childrenVector[i];

// Errors caught by the caller.
FunctionNode* functionNode = esprima::parseSingleFunction(m_context, codeBlock, SIZE_MAX);
FunctionNode* functionNode = esprima::parseSingleFunction(m_context, codeBlock);
codeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(m_context, codeBlock, functionNode);

m_context->astAllocator().reset();
Expand Down Expand Up @@ -573,7 +573,7 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScriptWithDebugger(
try {
ASTClassInfo* outerClassInfo = esprima::generateClassInfoFrom(m_context, parentCodeBlock);

programNode = esprima::parseProgram(m_context, sourceView, outerClassInfo, isModule, strictFromOutside, inWith, SIZE_MAX, allowSC, allowSP, allowNewTarget, allowArguments);
programNode = esprima::parseProgram(m_context, sourceView, outerClassInfo, isModule, strictFromOutside, inWith, allowSC, allowSP, allowNewTarget, allowArguments);

script = new Script(srcName, source, programNode->moduleData(), !parentCodeBlock, originLineOffset);
if (parentCodeBlock) {
Expand Down
4 changes: 2 additions & 2 deletions src/parser/ScriptParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ScriptParser : public gc {
Script* scriptThrowsExceptionIfParseError(ExecutionState& state);
};

InitializeScriptResult initializeScript(String* originSource, size_t originLineOffset, String* source, String* srcName, InterpretedCodeBlock* parentCodeBlock, bool isModule, bool isEvalMode = false, bool isEvalCodeInFunction = false, bool inWithOperation = false, bool strictFromOutside = false, bool allowSuperCall = false, bool allowSuperProperty = false, bool allowNewTarget = false, bool needByteCodeGeneration = true, size_t stackSizeRemain = SIZE_MAX);
InitializeScriptResult initializeScript(String* originSource, size_t originLineOffset, String* source, String* srcName, InterpretedCodeBlock* parentCodeBlock, bool isModule, bool isEvalMode = false, bool isEvalCodeInFunction = false, bool inWithOperation = false, bool strictFromOutside = false, bool allowSuperCall = false, bool allowSuperProperty = false, bool allowNewTarget = false, bool needByteCodeGeneration = true);
InitializeScriptResult initializeScript(String* source, String* srcName, bool isModule)
{
return initializeScript(nullptr, 0, source, srcName, nullptr, isModule);
Expand All @@ -61,7 +61,7 @@ class ScriptParser : public gc {

Context* context() const { return m_context; }

void generateFunctionByteCode(ExecutionState& state, InterpretedCodeBlock* codeBlock, size_t stackSizeRemain);
void generateFunctionByteCode(ExecutionState& state, InterpretedCodeBlock* codeBlock);

#if defined(ENABLE_CODE_CACHE)
void setCodeBlockCacheInfo(CodeBlockCacheInfo* info);
Expand Down
23 changes: 11 additions & 12 deletions src/parser/esprima_cpp/esprima.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,21 +201,20 @@ class Parser {
}
};

Parser(::Escargot::Context* escargotContext, StringView code, ASTClassInfo* outerClassInfo, bool isModule, size_t stackRemain, ExtendedNodeLOC startLoc = ExtendedNodeLOC(1, 0, 0))
Parser(::Escargot::Context* escargotContext, StringView code, ASTClassInfo* outerClassInfo, bool isModule, ExtendedNodeLOC startLoc = ExtendedNodeLOC(1, 0, 0))
: scannerInstance(escargotContext, &contextInstance, code, isModule, startLoc.line, startLoc.column)
, allocator(escargotContext->astAllocator())
, fakeContext(escargotContext->astAllocator())
{
ASSERT(escargotContext != nullptr);

if (stackRemain >= STACK_LIMIT_FROM_BASE) {
stackRemain = STACK_LIMIT_FROM_BASE;
}
size_t currentStackBase = reinterpret_cast<size_t>(currentStackPointer());
this->stackLimit = ThreadLocal::stackLimit();

// Use more stack for computing loc on stack-overflow situation
#ifdef STACK_GROWS_DOWN
this->stackLimit = currentStackBase - stackRemain;
this->stackLimit = this->stackLimit + STACK_FREESPACE_FROM_LIMIT / 2;
#else
this->stackLimit = currentStackBase + stackRemain;
this->stackLimit = this->stackLimit - STACK_FREESPACE_FROM_LIMIT / 2;
#endif
this->escargotContext = escargotContext;
this->stringArguments = escargotContext->staticStrings().arguments;
Expand Down Expand Up @@ -7142,12 +7141,12 @@ class Parser {
};

ProgramNode* parseProgram(::Escargot::Context* ctx, StringView source, ASTClassInfo* outerClassInfo, bool isModule, bool strictFromOutside,
bool inWith, size_t stackRemain, bool allowSuperCallFromOutside, bool allowSuperPropertyFromOutside, bool allowNewTargetFromOutside, bool allowArgumentsFromOutside)
bool inWith, bool allowSuperCallFromOutside, bool allowSuperPropertyFromOutside, bool allowNewTargetFromOutside, bool allowArgumentsFromOutside)
{
// GC should be disabled during the parsing process
ASSERT(GC_is_disabled());

Parser parser(ctx, source, outerClassInfo, isModule, stackRemain);
Parser parser(ctx, source, outerClassInfo, isModule);
NodeGenerator builder(ctx->astAllocator());

parser.context->strict |= strictFromOutside;
Expand All @@ -7161,13 +7160,13 @@ ProgramNode* parseProgram(::Escargot::Context* ctx, StringView source, ASTClassI
return nd;
}

FunctionNode* parseSingleFunction(::Escargot::Context* ctx, InterpretedCodeBlock* codeBlock, size_t stackRemain)
FunctionNode* parseSingleFunction(::Escargot::Context* ctx, InterpretedCodeBlock* codeBlock)
{
// GC should be disabled during the parsing process
ASSERT(GC_is_disabled());
ASSERT(ctx->astAllocator().isInitialized());

Parser parser(ctx, codeBlock->src(), nullptr, codeBlock->script()->isModule(), stackRemain, codeBlock->functionStart());
Parser parser(ctx, codeBlock->src(), nullptr, codeBlock->script()->isModule(), codeBlock->functionStart());
NodeGenerator builder(ctx->astAllocator());

parser.trackUsingNames = false;
Expand Down Expand Up @@ -7235,7 +7234,7 @@ void simpleSyntaxCheckFunctionElements(::Escargot::Context* ctx, String* paramet
ASSERT(GC_is_disabled());
ASSERT(ctx->astAllocator().isInitialized());

Parser parser(ctx, StringView(parameters), nullptr, false, SIZE_MAX);
Parser parser(ctx, StringView(parameters), nullptr, false);
#if defined(ESCARGOT_SMALL_CONFIG)
NodeGenerator checker(ctx->astAllocator(), false);
#else
Expand Down
4 changes: 2 additions & 2 deletions src/parser/esprima_cpp/esprima.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ struct Error : public gc {
#define ESPRIMA_RECURSIVE_LIMIT 1024

ProgramNode* parseProgram(::Escargot::Context* ctx, StringView source, ASTClassInfo* outerClassInfo,
bool isModule, bool strictFromOutside, bool inWith, size_t stackRemain, bool allowSuperCallFromOutside,
bool isModule, bool strictFromOutside, bool inWith, bool allowSuperCallFromOutside,
bool allowSuperPropertyFromOutside, bool allowNewTargetFromOutside, bool allowArgumentsFromOutside);
FunctionNode* parseSingleFunction(::Escargot::Context* ctx, InterpretedCodeBlock* codeBlock, size_t stackRemain);
FunctionNode* parseSingleFunction(::Escargot::Context* ctx, InterpretedCodeBlock* codeBlock);

ASTClassInfo* generateClassInfoFrom(::Escargot::Context* ctx, InterpretedCodeBlock* codeBlock);

Expand Down
Loading

0 comments on commit 9f802bf

Please sign in to comment.