Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TCO with fine-tuned heuristic #1278

Merged
merged 3 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/interpreter/ByteCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,7 @@ class CallReturn : public ByteCode {
, m_argumentCount(argumentCount)
{
}

ByteCodeRegisterIndex m_calleeIndex;
ByteCodeRegisterIndex m_argumentsStartIndex;
uint16_t m_argumentCount;
Expand All @@ -2045,6 +2046,7 @@ class TailRecursion : public ByteCode {
, m_argumentCount(argumentCount)
{
}

ByteCodeRegisterIndex m_calleeIndex;
ByteCodeRegisterIndex m_argumentsStartIndex;
uint16_t m_argumentCount;
Expand Down
104 changes: 56 additions & 48 deletions src/interpreter/ByteCodeInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1540,32 +1540,36 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
TailRecursion* code = (TailRecursion*)programCounter;
const Value& callee = registerFile[code->m_calleeIndex];

if (UNLIKELY((callee != Value(state->resolveCallee())) || (state->m_argc != code->m_argumentCount))) {
if (UNLIKELY(callee != Value(state->lexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject()))) {
// goto slow path
return InterpreterSlowPath::tailRecursionSlowCase(*state, code, callee, registerFile);
}

// fast tail recursion
ASSERT(callee.isPointerValue() && callee.asPointerValue()->isScriptFunctionObject());
ASSERT(callee.asPointerValue()->asScriptFunctionObject()->codeBlock() == byteCodeBlock->codeBlock());
ASSERT(state->m_argc == code->m_argumentCount);

if (code->m_argumentCount) {
if (UNLIKELY(!state->initTCO())) {
// At the start of tail call, we need to allocate a buffer for arguments
// because recursive tail call reuses this buffer
if (UNLIKELY(!state->initTCO())) {
Value* newArgs = ALLOCA(sizeof(Value) * code->m_argumentCount, Value);
state->setTCOArguments(newArgs);
}
state->m_argc = code->m_argumentCount;
Value* newArgs = code->m_argumentCount ? ALLOCA(sizeof(Value) * code->m_argumentCount, Value) : nullptr;
state->setTCOArguments(newArgs);
}

// its safe to overwrite arguments because old arguments are no longer necessary
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}
// fast tail recursion
ASSERT(callee.isPointerValue() && callee.asPointerValue()->isScriptFunctionObject());
ASSERT(callee.asPointerValue()->asScriptFunctionObject()->codeBlock() == byteCodeBlock->codeBlock());
ASSERT(state->initTCO() && (state->m_argc == code->m_argumentCount));
#ifndef NDEBUG
// check this value
if (state->inStrictMode()) {
ASSERT(registerFile[byteCodeBlock->m_requiredOperandRegisterNumber].isUndefined());
} else {
ASSERT(registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] == Value(state->context()->globalObjectProxy()));
}
#endif

// set this value
registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] = state->inStrictMode() ? Value() : state->context()->globalObjectProxy();
// its safe to overwrite arguments because old arguments are no longer necessary
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}

// set programCounter
programCounter = reinterpret_cast<size_t>(byteCodeBlock->m_code.data());
Expand All @@ -1582,31 +1586,30 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
const Value& callee = registerFile[code->m_calleeIndex];
const Value& receiver = registerFile[code->m_receiverIndex];

if (UNLIKELY((callee != Value(state->resolveCallee())) || (state->m_argc != code->m_argumentCount))) {
if (UNLIKELY(callee != Value(state->lexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject()))) {
// goto slow path
return InterpreterSlowPath::tailRecursionWithReceiverSlowCase(*state, code, callee, receiver, registerFile);
}

if (UNLIKELY(!state->initTCO())) {
// At the start of tail call, we need to allocate a buffer for arguments
// because recursive tail call reuses this buffer
state->m_argc = code->m_argumentCount;
Value* newArgs = code->m_argumentCount ? ALLOCA(sizeof(Value) * code->m_argumentCount, Value) : nullptr;
state->setTCOArguments(newArgs);
}

// fast tail recursion with receiver
ASSERT(callee.isPointerValue() && callee.asPointerValue()->isScriptFunctionObject());
ASSERT(callee.asPointerValue()->asScriptFunctionObject()->codeBlock() == byteCodeBlock->codeBlock());
ASSERT(state->m_argc == code->m_argumentCount);

if (code->m_argumentCount) {
// At the start of tail call, we need to allocate a buffer for arguments
// because recursive tail call reuses this buffer
if (UNLIKELY(!state->initTCO())) {
Value* newArgs = ALLOCA(sizeof(Value) * code->m_argumentCount, Value);
state->setTCOArguments(newArgs);
}
ASSERT(state->initTCO() && (state->m_argc == code->m_argumentCount));

// its safe to overwrite arguments because old arguments are no longer necessary
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}
// its safe to overwrite arguments because old arguments are no longer necessary
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}

// set this value (receiver) // FIXME
// set this value (receiver)
if (state->inStrictMode()) {
registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] = receiver;
} else {
Expand All @@ -1631,7 +1634,8 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
TailRecursionInTry* code = (TailRecursionInTry*)programCounter;
const Value& callee = registerFile[code->m_calleeIndex];

if (UNLIKELY((callee != Value(state->resolveCallee())) || (state->m_argc != code->m_argumentCount))) {
if (UNLIKELY(callee != Value(state->resolveCallee()))) {
// should call resolveCallee because try-catch-finally block is executed in a sub-interpreter
// goto slow path
code->changeOpcode(Opcode::CallOpcode);
if (UNLIKELY(!callee.isPointerValue())) {
Expand All @@ -1647,7 +1651,6 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock

ASSERT(callee.isPointerValue() && callee.asPointerValue()->isScriptFunctionObject());
ASSERT(callee.asPointerValue()->asScriptFunctionObject()->codeBlock() == byteCodeBlock->codeBlock());
ASSERT(state->m_argc == code->m_argumentCount);
ASSERT(state->rareData()->controlFlowRecordVector() && state->rareData()->controlFlowRecordVector()->size());

// postpone recursion call
Expand Down Expand Up @@ -3222,26 +3225,31 @@ NEVER_INLINE Value InterpreterSlowPath::tryOperation(ExecutionState*& state, siz
ASSERT(!inPauserScope && !inPauserResumeProcess);
ASSERT(code->m_hasCatch || code->m_hasFinalizer);
ASSERT(record->m_value == state->resolveCallee());
#ifndef NDEBUG
// check this value
if (state->inStrictMode()) {
ASSERT(registerFile[byteCodeBlock->m_requiredOperandRegisterNumber].isUndefined());
} else {
ASSERT(registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] == Value(state->context()->globalObjectProxy()));
}
#endif

Value callee = record->m_value;
size_t argCount = record->m_count;
size_t argStartIndex = record->m_outerLimitCount;
if (argCount) {
// At the start of tail call, we need to allocate a buffer for arguments
// because recursive tail call reuses this buffer
if (UNLIKELY(!state->initTCO())) {
Value* newArgs = (Value*)GC_MALLOC(sizeof(Value) * argCount);
state->setTCOArguments(newArgs);
}

// its safe to overwrite arguments because old arguments are no longer necessary
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[argStartIndex + i];
}
// At the start of tail call, we need to allocate a buffer for arguments
// because recursive tail call reuses this buffer
if (UNLIKELY(!state->initTCO())) {
state->m_argc = argCount;
Value* newArgs = argCount ? (Value*)GC_MALLOC(sizeof(Value) * argCount) : nullptr;
state->setTCOArguments(newArgs);
}

// set this value
registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] = state->inStrictMode() ? Value() : state->context()->globalObjectProxy();
// its safe to overwrite arguments because old arguments are no longer necessary
ASSERT(state->m_argc == argCount);
for (size_t i = 0; i < state->m_argc; i++) {
state->m_argv[i] = registerFile[argStartIndex + i];
}

// set programCounter
programCounter = reinterpret_cast<size_t>(byteCodeBlock->m_code.data());
Expand Down
16 changes: 15 additions & 1 deletion src/parser/CodeBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -616,10 +616,24 @@ class InterpretedCodeBlock : public CodeBlock {
}

// for TCO
bool isTailRecursionTarget() const
bool isTailRecursionTarget(size_t argc, const AtomicString& calleeName) const
{
// global scope cannot create a return statement, neither tail recursion
ASSERT(!isGlobalScope());

// check argc
if (m_parameterCount != argc) {
return false;
}

#ifndef ESCARGOT_ENABLE_TEST
// check callee name
// this check is disabled in test build to pass test262 tco-related test cases
if (m_functionName.string()->length() && m_functionName != calleeName) {
return false;
}
#endif

return (!m_canAllocateVariablesOnStack || m_isArrowFunctionExpression || m_isClassConstructor || m_isDerivedClassConstructor || m_isClassMethod || m_isClassStaticMethod || m_isGenerator || m_isAsync || m_usesArgumentsObject) != true;
}

Expand Down
6 changes: 4 additions & 2 deletions src/parser/ast/CallExpressionNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ class CallExpressionNode : public ExpressionNode {
if (dstRegister == context->m_returnRegister) {
// Try tail recursion optimization (TCO)
isTailCall = true;
bool isTailRecursion = context->m_codeBlock->isTailRecursionTarget();
const AtomicString& calleeName = m_callee->asMemberExpression()->property()->isIdentifier() ? m_callee->asMemberExpression()->property()->asIdentifier()->name() : AtomicString();
bool isTailRecursion = context->m_codeBlock->isTailRecursionTarget(m_arguments.size(), calleeName);
if (UNLIKELY(context->tryCatchWithBlockStatementCount())) {
codeBlock->pushCode(CallWithReceiver(ByteCodeLOC(m_loc.index), receiverIndex, calleeIndex, argumentsStartIndex, dstRegister, m_arguments.size()), context, this->m_loc.index);
} else {
Expand All @@ -434,7 +435,8 @@ class CallExpressionNode : public ExpressionNode {
if (dstRegister == context->m_returnRegister) {
// Try tail recursion optimization (TCO)
isTailCall = true;
bool isTailRecursion = context->m_codeBlock->isTailRecursionTarget();
const AtomicString& calleeName = m_callee->isIdentifier() ? m_callee->asIdentifier()->name() : AtomicString();
bool isTailRecursion = context->m_codeBlock->isTailRecursionTarget(m_arguments.size(), calleeName);
if (UNLIKELY(context->tryCatchWithBlockStatementCount())) {
if (isTailRecursion) {
codeBlock->pushCode(TailRecursionInTry(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, dstRegister, m_arguments.size()), context, this->m_loc.index);
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/ExecutionState.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ class ExecutionState : public gc {
// allocate a new argument buffer
// because tail call reuses this buffer which can modify caller's register file
ASSERT(!m_initTCO && !!argv);
m_initTCO = true;
m_argv = argv;
m_initTCO = true; // initialize of TCO done
}
#endif

Expand Down