Implement general tail call optimization

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
This commit is contained in:
HyukWoo Park 2024-01-26 19:12:55 +09:00 committed by Patrick Kim
commit 023b7ea014
27 changed files with 368 additions and 89 deletions

View file

@ -568,6 +568,11 @@ typedef uint16_t LexicalBlockIndex;
#define REGEXP_CACHE_SIZE_MAX 64
#endif
// maximum number of tail call arguments allowed
#ifndef TCO_ARGUMENT_COUNT_LIMIT
#define TCO_ARGUMENT_COUNT_LIMIT 8
#endif
#include <tsl/robin_set.h>
template <class Key, class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>,

View file

@ -297,7 +297,7 @@ void CodeCacheWriter::storeByteCodeBlock(ByteCodeBlock* block)
m_buffer.ensureSize(2 * sizeof(bool) + 2 * sizeof(uint16_t));
m_buffer.put(block->m_shouldClearStack);
m_buffer.put(block->m_isOwnerMayFreed);
m_buffer.put(block->m_needsExtendedExectuionState);
m_buffer.put(block->m_needsExtendedExecutionState);
m_buffer.put((uint16_t)block->m_requiredOperandRegisterNumber);
m_buffer.put((uint16_t)block->m_requiredTotalRegisterNumber);
@ -859,7 +859,7 @@ ByteCodeBlock* CodeCacheReader::loadByteCodeBlock(Context* context, InterpretedC
block->m_shouldClearStack = m_buffer.get<bool>();
block->m_isOwnerMayFreed = m_buffer.get<bool>();
block->m_needsExtendedExectuionState = m_buffer.get<bool>();
block->m_needsExtendedExecutionState = m_buffer.get<bool>();
block->m_requiredOperandRegisterNumber = m_buffer.get<uint16_t>();
block->m_requiredTotalRegisterNumber = m_buffer.get<uint16_t>();

View file

@ -99,7 +99,7 @@ void SetGlobalVariable::dump()
ByteCodeBlock::ByteCodeBlock()
: m_shouldClearStack(false)
, m_isOwnerMayFreed(false)
, m_needsExtendedExectuionState(false)
, m_needsExtendedExecutionState(false)
, m_requiredOperandRegisterNumber(2)
, m_requiredTotalRegisterNumber(0)
, m_inlineCacheDataSize(0)
@ -129,7 +129,7 @@ static void clearByteCodeBlock(ByteCodeBlock* self)
ByteCodeBlock::ByteCodeBlock(InterpretedCodeBlock* codeBlock)
: m_shouldClearStack(false)
, m_isOwnerMayFreed(false)
, m_needsExtendedExectuionState(false)
, m_needsExtendedExecutionState(false)
, m_requiredOperandRegisterNumber(2)
, m_requiredTotalRegisterNumber(0)
, m_inlineCacheDataSize(0)

View file

@ -141,6 +141,7 @@ struct GlobalVariableAccessCacheItem;
#if defined(ENABLE_TCO)
#define FOR_EACH_BYTECODE_TCO_OP(F) \
F(CallReturn) \
F(TailCall) \
F(TailRecursion) \
F(TailRecursionInTry)
#else
@ -2158,6 +2159,53 @@ public:
#endif
};
class TailCall : public ByteCode {
public:
TailCall(const ByteCodeLOC& loc, const size_t calleeIndex, const size_t argumentsStartIndex, const size_t argumentCount)
: ByteCode(Opcode::TailCallOpcode, loc)
, m_receiverIndex(REGISTER_LIMIT)
, m_calleeIndex(calleeIndex)
, m_argumentsStartIndex(argumentsStartIndex)
, m_argumentCount(argumentCount)
{
// tail call without receiver
}
TailCall(const ByteCodeLOC& loc, const size_t receiverIndex, const size_t calleeIndex, const size_t argumentsStartIndex, const size_t argumentCount)
: ByteCode(Opcode::TailCallOpcode, loc)
, m_receiverIndex(receiverIndex)
, m_calleeIndex(calleeIndex)
, m_argumentsStartIndex(argumentsStartIndex)
, m_argumentCount(argumentCount)
{
// tail call with receiver
}
ByteCodeRegisterIndex m_receiverIndex;
ByteCodeRegisterIndex m_calleeIndex;
ByteCodeRegisterIndex m_argumentsStartIndex;
uint16_t m_argumentCount;
#ifndef NDEBUG
void dump()
{
if (m_receiverIndex != REGISTER_LIMIT) {
if (m_argumentCount) {
printf("tail call r%u.r%u(r%u-r%u)", m_receiverIndex, m_calleeIndex, m_argumentsStartIndex, m_argumentsStartIndex + m_argumentCount);
} else {
printf("tail call r%u.r%u()", m_receiverIndex, m_calleeIndex);
}
} else {
if (m_argumentCount) {
printf("tail call r%u(r%u-r%u)", m_calleeIndex, m_argumentsStartIndex, m_argumentsStartIndex + m_argumentCount);
} else {
printf("tail call r%u()", m_calleeIndex);
}
}
}
#endif
};
class TailRecursion : public ByteCode {
public:
TailRecursion(const ByteCodeLOC& loc, const size_t calleeIndex, const size_t argumentsStartIndex, const size_t argumentCount)
@ -2167,7 +2215,7 @@ public:
, m_argumentsStartIndex(argumentsStartIndex)
, m_argumentCount(argumentCount)
{
// tail recursion call without receiver
// tail recursion without receiver
}
TailRecursion(const ByteCodeLOC& loc, const size_t receiverIndex, const size_t calleeIndex, const size_t argumentsStartIndex, const size_t argumentCount)
@ -2177,7 +2225,7 @@ public:
, m_argumentsStartIndex(argumentsStartIndex)
, m_argumentCount(argumentCount)
{
// tail recursion call with receiver
// tail recursion with receiver
}
ByteCodeRegisterIndex m_receiverIndex;
@ -2232,6 +2280,7 @@ public:
#endif
};
COMPILE_ASSERT(sizeof(CallReturn) == sizeof(TailCall), "");
COMPILE_ASSERT(sizeof(CallReturn) == sizeof(TailRecursion), "");
COMPILE_ASSERT(sizeof(Call) == sizeof(TailRecursionInTry), "");
#endif
@ -3242,7 +3291,7 @@ public:
bool needsExtendedExecutionState() const
{
return m_needsExtendedExectuionState;
return m_needsExtendedExecutionState;
}
ExtendedNodeLOC computeNodeLOCFromByteCode(Context* c, size_t codePosition, InterpretedCodeBlock* cb, ByteCodeLOCData* locData);
@ -3251,7 +3300,7 @@ public:
bool m_shouldClearStack : 1;
bool m_isOwnerMayFreed : 1;
bool m_needsExtendedExectuionState : 1;
bool m_needsExtendedExecutionState : 1;
// number of bytecode registers used for bytecode operation like adding...moving...
ByteCodeRegisterIndex m_requiredOperandRegisterNumber : REGISTER_INDEX_IN_BIT;
// precomputed value of total register number which is "m_requiredTotalRegisterNumber + stack allocated variables size"

View file

@ -278,7 +278,7 @@ ByteCodeBlock* ByteCodeGenerator::generateByteCode(Context* context, Interpreted
block->m_code.shrinkToFit();
block->m_requiredTotalRegisterNumber = block->m_requiredOperandRegisterNumber + codeBlock->totalStackAllocatedVariableSize() + block->m_numeralLiteralData.size();
block->m_needsExtendedExectuionState = ctx.m_needsExtendedExecutionState;
block->m_needsExtendedExecutionState = ctx.m_needsExtendedExecutionState;
#if defined(ENABLE_CODE_CACHE)
// cache bytecode right before relocation
@ -612,6 +612,13 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block)
ASSIGN_STACKINDEX_IF_NEEDED(cd->m_argumentsStartIndex, stackBase, stackBaseWillBe, stackVariableSize);
break;
}
case TailCallOpcode: {
TailCall* cd = (TailCall*)currentCode;
ASSIGN_STACKINDEX_IF_NEEDED(cd->m_receiverIndex, stackBase, stackBaseWillBe, stackVariableSize);
ASSIGN_STACKINDEX_IF_NEEDED(cd->m_calleeIndex, stackBase, stackBaseWillBe, stackVariableSize);
ASSIGN_STACKINDEX_IF_NEEDED(cd->m_argumentsStartIndex, stackBase, stackBaseWillBe, stackVariableSize);
break;
}
case TailRecursionOpcode: {
TailRecursion* cd = (TailRecursion*)currentCode;
ASSIGN_STACKINDEX_IF_NEEDED(cd->m_receiverIndex, stackBase, stackBaseWillBe, stackVariableSize);

View file

@ -51,6 +51,20 @@
#include "parser/ScriptParser.h"
#include "CheckedArithmetic.h"
#if defined(ENABLE_TCO)
#include "runtime/FunctionObjectInlines.h"
namespace Escargot {
MAY_THREAD_LOCAL Value* Interpreter::tcoBuffer;
void Interpreter::initTCOBuffer()
{
ASSERT(!Interpreter::tcoBuffer);
Interpreter::tcoBuffer = (Value*)GC_MALLOC_UNCOLLECTABLE(sizeof(Value) * TCO_ARGUMENT_COUNT_LIMIT);
}
} // namespace Escargot
#endif
#if defined(ESCARGOT_COMPUTED_GOTO_INTERPRETER) && !defined(ESCARGOT_COMPUTED_GOTO_INTERPRETER_INIT_WITH_NULL)
extern char FillOpcodeTableAsmLbl[];
const void* FillOpcodeTableAddress[] = { &FillOpcodeTableAsmLbl[0] };
@ -191,7 +205,9 @@ public:
static int evaluateImportAssertionOperation(ExecutionState& state, const Value& options);
#if defined(ENABLE_TCO)
static Value tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, const Value& callee, const Value& receiver, Value* registerFile);
static Value tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, ByteCodeBlock* byteCodeBlock, const Value& callee, Value* registerFile);
static Value prepareTailCallOptimization(ExecutionState* state, TailCall* code, ScriptFunctionObject* callee, ByteCodeBlock*& callerBlock, size_t& programCounter, const Value* registerFile);
static Value tailCallSlowCase(ExecutionState& state, TailCall* code, const Value& callee, Value* registerFile);
#endif
private:
@ -1525,33 +1541,31 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
}
#if defined(ENABLE_TCO)
// TCO : tail recursion case
// Tail recursion
DEFINE_OPCODE(TailRecursion)
:
{
TailRecursion* code = (TailRecursion*)programCounter;
const Value& callee = registerFile[code->m_calleeIndex];
const Value& receiver = (code->m_receiverIndex == REGISTER_LIMIT) ? Value() : registerFile[code->m_receiverIndex];
if (UNLIKELY(callee != Value(state->lexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject()))) {
// goto slow path
return InterpreterSlowPath::tailRecursionSlowCase(*state, code, callee, receiver, registerFile);
return InterpreterSlowPath::tailRecursionSlowCase(*state, code, byteCodeBlock, callee, registerFile);
}
if (UNLIKELY(!state->initTCO())) {
if (UNLIKELY(!state->inTCO())) {
// 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);
state->initTCOWithBuffer(Interpreter::tcoBuffer);
}
state->m_argc = code->m_argumentCount;
// 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));
ASSERT(state->inTCO() && (state->m_argc <= TCO_ARGUMENT_COUNT_LIMIT));
#ifndef NDEBUG
// check this value for call without receiver
// check this value
if (code->m_receiverIndex == REGISTER_LIMIT) {
if (state->inStrictMode()) {
ASSERT(registerFile[byteCodeBlock->m_requiredOperandRegisterNumber].isUndefined());
@ -1566,8 +1580,8 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}
// set this value (receiver)
if (code->m_receiverIndex != REGISTER_LIMIT) {
const Value& receiver = registerFile[code->m_receiverIndex];
if (state->inStrictMode()) {
registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] = receiver;
} else {
@ -1586,6 +1600,49 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
NEXT_INSTRUCTION();
}
// TCO : general tail call optimization
DEFINE_OPCODE(TailCall)
:
{
TailCall* code = (TailCall*)programCounter;
ASSERT(state->lexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord());
ASSERT(byteCodeBlock->m_codeBlock->isTailCallTarget(code->m_argumentCount));
ASSERT(code->m_argumentCount <= TCO_ARGUMENT_COUNT_LIMIT);
const Value& calleeValue = registerFile[code->m_calleeIndex];
if (calleeValue.isPointerValue() && calleeValue.asPointerValue()->canBeTailCallTargetRuntime(code->m_argumentCount)) {
Value thisValue = InterpreterSlowPath::prepareTailCallOptimization(state, code, calleeValue.asPointerValue()->asScriptFunctionObject(), byteCodeBlock, programCounter, registerFile);
if (!thisValue.isEmpty()) {
ASSERT(byteCodeBlock == calleeValue.asPointerValue()->asScriptFunctionObject()->interpretedCodeBlock()->byteCodeBlock());
ASSERT(programCounter == (size_t)byteCodeBlock->m_code.data());
ASSERT(state->m_programCounter == &programCounter);
if (UNLIKELY(!state->lexicalEnvironment())) {
// should allocate environment stuctures on the stack
ScriptFunctionObject* callee = calleeValue.asPointerValue()->asScriptFunctionObject();
FunctionEnvironmentRecord* record = new (alloca(sizeof(FunctionEnvironmentRecordOnStack<false, false>))) FunctionEnvironmentRecordOnStack<false, false>(callee);
LexicalEnvironment* lexEnv = new (alloca(sizeof(LexicalEnvironment))) LexicalEnvironment(record, callee->outerEnvironment()
#ifndef NDEBUG
,
false
#endif
);
state->m_lexicalEnvironment = lexEnv;
}
// set this value
registerFile[byteCodeBlock->m_requiredOperandRegisterNumber] = thisValue;
// directly jump to the first bytecode of callee
NEXT_INSTRUCTION();
}
}
// goto slow path
return InterpreterSlowPath::tailCallSlowCase(*state, code, calleeValue, registerFile);
}
// TCO : tail recursion case in catch or finally block
DEFINE_OPCODE(TailRecursionInTry)
:
@ -3378,11 +3435,11 @@ NEVER_INLINE Value InterpreterSlowPath::tryOperation(ExecutionState*& state, siz
size_t argStartIndex = record->m_outerLimitCount;
// 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);
if (UNLIKELY(!state->inTCO())) {
ASSERT(Interpreter::tcoBuffer);
state->initTCOWithBuffer(Interpreter::tcoBuffer);
}
state->m_argc = argCount;
// its safe to overwrite arguments because old arguments are no longer necessary
ASSERT(state->m_argc == argCount);
@ -4918,9 +4975,114 @@ NEVER_INLINE int InterpreterSlowPath::evaluateImportAssertionOperation(Execution
}
#if defined(ENABLE_TCO)
NEVER_INLINE Value InterpreterSlowPath::tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, const Value& callee, const Value& receiver, Value* registerFile)
NEVER_INLINE Value InterpreterSlowPath::tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, ByteCodeBlock* byteCodeBlock, const Value& callee, Value* registerFile)
{
// fail to tail recursion
// fix the caller's call site to TailCall
code->changeOpcode(Opcode::TailCallOpcode);
byteCodeBlock->codeBlock()->disableTailRecursion();
// if PointerValue is not callable, PointerValue::call function throws builtin error
// https://www.ecma-international.org/ecma-262/6.0/#sec-call
// If IsCallable(F) is false, throw a TypeError exception.
if (UNLIKELY(!callee.isPointerValue())) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::NOT_Callable);
}
const Value& receiver = (code->m_receiverIndex == REGISTER_LIMIT) ? Value() : registerFile[code->m_receiverIndex];
return callee.asPointerValue()->call(state, receiver, code->m_argumentCount, &registerFile[code->m_argumentsStartIndex]);
}
NEVER_INLINE Value InterpreterSlowPath::prepareTailCallOptimization(ExecutionState* state, TailCall* code, ScriptFunctionObject* callee, ByteCodeBlock*& callerByteBlock, size_t& programCounter, const Value* registerFile)
{
ASSERT(!callee->isScriptArrowFunctionObject() && !!callerByteBlock);
ASSERT(state->m_programCounter == &programCounter);
ASSERT(Interpreter::tcoBuffer);
ASSERT(code->m_argumentCount <= TCO_ARGUMENT_COUNT_LIMIT);
InterpretedCodeBlock* calleeBlock = callee->interpretedCodeBlock();
if (!calleeBlock->byteCodeBlock()) {
// if callee doesn't have ByteCode yet, generate it
callee->generateByteCodeBlock(*state);
}
ByteCodeBlock* calleeByteBlock = calleeBlock->byteCodeBlock();
if (!calleeByteBlock->needsExtendedExecutionState() && (callerByteBlock->m_requiredTotalRegisterNumber >= calleeByteBlock->m_requiredTotalRegisterNumber)) {
// Note) any element of registerFile should not be modified in this function
const Value& receiver = (code->m_receiverIndex == REGISTER_LIMIT) ? Value() : registerFile[code->m_receiverIndex];
Context* context = calleeBlock->context();
bool isStrict = calleeBlock->isStrict();
bool isTailRecursion = (calleeByteBlock == callerByteBlock) && (!callerByteBlock->codeBlock()->isTailRecursionDisabled());
// tail recursion reuses environment structures
if (isTailRecursion) {
// convert to fast tail recursion
code->changeOpcode(Opcode::TailRecursionOpcode);
} else {
FunctionEnvironmentRecord* record = nullptr;
LexicalEnvironment* lexEnv = nullptr;
if (!calleeBlock->canAllocateEnvironmentOnStack()) {
// cannot reuse environment structures
// should create new environments
ASSERT(!callee->isScriptSimpleFunctionObject());
record = FunctionObjectProcessCallGenerator::createFunctionEnvironmentRecord<ScriptFunctionObject, false, false>(*state, callee, calleeBlock);
lexEnv = new LexicalEnvironment(record, callee->outerEnvironment());
} else if (callerByteBlock->codeBlock()->canAllocateEnvironmentOnStack()) {
// reuse caller's environment structures
ASSERT(state->lexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->isFunctionEnvironmentRecordOnStack());
record = new (state->lexicalEnvironment()->record()) FunctionEnvironmentRecordOnStack<false, false>(callee);
lexEnv = new (state->lexicalEnvironment()) LexicalEnvironment(record, callee->outerEnvironment()
#ifndef NDEBUG
,
false
#endif
);
}
// other case, environment structures need to be newly allocated on the stack using alloca method
// this will be handled right after this slow path
ExecutionState* newState = new (state) ExecutionState(context, state->parent(), lexEnv, 0, nullptr, isStrict);
newState->m_programCounter = &programCounter;
ASSERT(state == newState);
}
if (!state->inTCO()) {
// At the start of tail call, we need to set a buffer for arguments
// because tail call reuses this buffer
state->initTCOWithBuffer(Interpreter::tcoBuffer);
}
state->m_argc = code->m_argumentCount;
// rewrite arguments info on ExecutionState
for (size_t i = 0; i < code->m_argumentCount; i++) {
state->m_argv[i] = registerFile[code->m_argumentsStartIndex + i];
}
// get this value
Value thisValue;
if (code->m_receiverIndex == REGISTER_LIMIT) {
thisValue = isStrict ? Value() : context->globalObjectProxy();
} else {
thisValue = isStrict ? receiver : (receiver.isUndefinedOrNull() ? context->globalObjectProxy() : receiver.toObject(*state));
}
// rewrite call environment
ASSERT(state->m_programCounter == &programCounter);
callerByteBlock = calleeByteBlock;
programCounter = reinterpret_cast<size_t>(calleeByteBlock->m_code.data());
return thisValue;
}
// empty value represents invalid tail call
return Value(Value::EmptyValue);
}
NEVER_INLINE Value InterpreterSlowPath::tailCallSlowCase(ExecutionState& state, TailCall* code, const Value& callee, Value* registerFile)
{
// fail to tail Call
// convert to CallReturn
code->changeOpcode(Opcode::CallReturnOpcode);
@ -4931,6 +5093,7 @@ NEVER_INLINE Value InterpreterSlowPath::tailRecursionSlowCase(ExecutionState& st
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::NOT_Callable);
}
const Value& receiver = (code->m_receiverIndex == REGISTER_LIMIT) ? Value() : registerFile[code->m_receiverIndex];
return callee.asPointerValue()->call(state, receiver, code->m_argumentCount, &registerFile[code->m_argumentsStartIndex]);
}
#endif

View file

@ -40,6 +40,12 @@ public:
};
static Value interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock, size_t programCounter, Value* registerFile);
#if defined(ENABLE_TCO)
static void initTCOBuffer();
static MAY_THREAD_LOCAL Value* tcoBuffer;
#endif
};
} // namespace Escargot

View file

@ -289,6 +289,9 @@ InterpretedCodeBlock::InterpretedCodeBlock(Context* ctx, Script* script)
, m_allowSuperProperty(false)
, m_allowArguments(false)
, m_hasDynamicSourceCode(false)
#if defined(ENABLE_TCO)
, m_isTailRecursionDisabled(false)
#endif
#ifdef ESCARGOT_DEBUGGER
, m_markDebugging(ctx->inDebuggingCodeMode())
#endif

View file

@ -742,25 +742,17 @@ public:
}
// for TCO
bool isTailRecursionTarget(size_t argc, const AtomicString& calleeName) const
bool isTailCallTarget(size_t argc) const
{
// global scope cannot create a return statement, neither tail recursion
// global scope cannot create a return statement, neither tail call
ASSERT(!isGlobalScope());
// check argc
if (m_parameterCount != argc) {
if (argc > TCO_ARGUMENT_COUNT_LIMIT) {
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;
// skip arrow functions because arrow functions are rarely invoked in tail call
return m_canAllocateVariablesOnStack && !m_isArrowFunctionExpression && !m_isClassConstructor && !m_isDerivedClassConstructor && !m_isClassMethod && !m_isClassStaticMethod && !m_isGenerator && !m_isAsync && !m_usesArgumentsObject;
}
bool usesArgumentsObject() const
@ -803,6 +795,18 @@ public:
m_hasDynamicSourceCode = true;
}
#if defined(ENABLE_TCO)
bool isTailRecursionDisabled() const
{
return m_isTailRecursionDisabled;
}
void disableTailRecursion()
{
m_isTailRecursionDisabled = true;
}
#endif
#ifdef ESCARGOT_DEBUGGER
bool markDebugging() const
{
@ -1008,6 +1012,9 @@ protected:
bool m_allowArguments : 1;
// represent if its source code is created dynamically by createDynamicFunctionScript
bool m_hasDynamicSourceCode : 1;
#if defined(ENABLE_TCO)
bool m_isTailRecursionDisabled : 1;
#endif
#ifdef ESCARGOT_DEBUGGER
// mark that this InterpretedCodeBlock should generate debugging bytecode (breakpoint)
bool m_markDebugging : 1;

View file

@ -62,7 +62,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
bool isSlow = !canUseDirectRegister(context, m_left, m_right);
bool directBefore = context->m_canSkipCopyToRegister;
@ -73,9 +73,9 @@ public:
if (UNLIKELY(m_left->isLiteral())) {
bool boolVal = m_left->asLiteral()->value().toBoolean();
if (boolVal) {
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
} else {
m_left->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_left->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
}
} else {
m_left->generateExpressionByteCode(codeBlock, context, dstRegister);

View file

@ -62,7 +62,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
bool isSlow = !canUseDirectRegister(context, m_left, m_right);
bool directBefore = context->m_canSkipCopyToRegister;
@ -73,9 +73,9 @@ public:
if (UNLIKELY(m_left->isLiteral())) {
bool boolVal = m_left->asLiteral()->value().toBoolean();
if (boolVal) {
m_left->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_left->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
} else {
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
}
} else {
m_left->generateExpressionByteCode(codeBlock, context, dstRegister);

View file

@ -54,7 +54,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
bool isSlow = !canUseDirectRegister(context, m_left, m_right);
bool directBefore = context->m_canSkipCopyToRegister;
@ -68,7 +68,7 @@ public:
size_t pos = codeBlock->lastCodePosition<JumpIfUndefinedOrNull>();
// try TCO only for right hand-side because we need to check the result of left hand-side expression
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_right->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
codeBlock->peekCode<JumpIfUndefinedOrNull>(pos)->m_jumpPosition = codeBlock->currentCodeSize();
context->m_canSkipCopyToRegister = directBefore;

View file

@ -288,7 +288,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
if (m_callee->isIdentifier() && m_callee->asIdentifier()->name().string()->equals("eval")) {
ByteCodeRegisterIndex evalIndex = context->getRegister();
@ -415,15 +415,15 @@ public:
context, this->m_loc.index);
} else if (isCalleeHasReceiver) {
if (dstRegister == context->m_returnRegister) {
// Try tail recursion optimization (TCO)
isTailCall = true;
// try tail call optimization (TCO)
isTailCallForm = true;
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 {
if (isTailRecursion) {
codeBlock->pushCode(TailRecursion(ByteCodeLOC(m_loc.index), receiverIndex, calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
if (context->m_codeBlock->isTailCallTarget(m_arguments.size())) {
// tail call
codeBlock->pushCode(TailCall(ByteCodeLOC(m_loc.index), receiverIndex, calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
} else {
codeBlock->pushCode(CallReturn(ByteCodeLOC(m_loc.index), receiverIndex, calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
}
@ -433,19 +433,21 @@ public:
}
} else {
if (dstRegister == context->m_returnRegister) {
// Try tail recursion optimization (TCO)
isTailCall = true;
// try tail call optimization (TCO)
isTailCallForm = true;
const AtomicString& calleeName = m_callee->isIdentifier() ? m_callee->asIdentifier()->name() : AtomicString();
bool isTailRecursion = context->m_codeBlock->isTailRecursionTarget(m_arguments.size(), calleeName);
bool isTailCallTarget = context->m_codeBlock->isTailCallTarget(m_arguments.size());
if (UNLIKELY(context->tryCatchWithBlockStatementCount())) {
if (isTailRecursion) {
if (isTailCallTarget) {
// try tail recursion
codeBlock->pushCode(TailRecursionInTry(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, dstRegister, m_arguments.size()), context, this->m_loc.index);
} else {
codeBlock->pushCode(Call(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, dstRegister, m_arguments.size()), context, this->m_loc.index);
}
} else {
if (isTailRecursion) {
codeBlock->pushCode(TailRecursion(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
if (isTailCallTarget) {
// tail call
codeBlock->pushCode(TailCall(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
} else {
codeBlock->pushCode(CallReturn(ByteCodeLOC(m_loc.index), calleeIndex, argumentsStartIndex, m_arguments.size()), context, this->m_loc.index);
}

View file

@ -58,7 +58,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
size_t testReg = m_test->getRegister(codeBlock, context);
m_test->generateExpressionByteCode(codeBlock, context, testReg);
@ -68,13 +68,13 @@ public:
context->giveUpRegister();
size_t jumpPosForTestIsFalse = codeBlock->lastCodePosition<JumpIfFalse>();
m_consequente->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_consequente->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
codeBlock->pushCode(Jump(ByteCodeLOC(m_loc.index), SIZE_MAX), context, this->m_loc.index);
JumpIfFalse* jumpForTestIsFalse = codeBlock->peekCode<JumpIfFalse>(jumpPosForTestIsFalse);
size_t jumpPosForEndOfConsequence = codeBlock->lastCodePosition<Jump>();
jumpForTestIsFalse->m_jumpPosition = codeBlock->currentCodeSize();
m_alternate->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_alternate->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
Jump* jumpForEndOfConsequence = codeBlock->peekCode<Jump>(jumpPosForEndOfConsequence);
jumpForEndOfConsequence->m_jumpPosition = codeBlock->currentCodeSize();

View file

@ -316,9 +316,9 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm)
{
UNUSED_PARAMETER(isTailCall);
UNUSED_PARAMETER(isTailCallForm);
generateExpressionByteCode(codeBlock, context, dstRegister);
}
#endif

View file

@ -54,9 +54,9 @@ public:
#if defined(ENABLE_TCO)
if (!context->m_tcoDisabled && (context->tryCatchWithBlockStatementCount() == 1) && ((context->m_recursiveStatementStack.back().first == ByteCodeGenerateContext::Catch) || (context->m_recursiveStatementStack.back().first == ByteCodeGenerateContext::Finally))) {
// consider tail recursion (TCO) for catch, finally block within depth 1
bool isTailCall = false;
bool isTailCallForm = false;
context->setReturnRegister(index);
m_argument->generateTCOExpressionByteCode(codeBlock, context, index, isTailCall);
m_argument->generateTCOExpressionByteCode(codeBlock, context, index, isTailCallForm);
context->setReturnRegister(SIZE_MAX);
} else {
m_argument->generateExpressionByteCode(codeBlock, context, index);
@ -74,7 +74,7 @@ public:
}
context->giveUpRegister();
} else {
bool isTailCall = false;
bool isTailCallForm = false;
size_t r;
if (m_argument) {
r = m_argument->getRegister(codeBlock, context);
@ -83,7 +83,7 @@ public:
// consider tail recursion (TCO)
ASSERT(!context->m_tcoDisabled);
context->setReturnRegister(r);
m_argument->generateTCOExpressionByteCode(codeBlock, context, r, isTailCall);
m_argument->generateTCOExpressionByteCode(codeBlock, context, r, isTailCallForm);
context->setReturnRegister(SIZE_MAX);
#else
m_argument->generateExpressionByteCode(codeBlock, context, r);
@ -94,7 +94,7 @@ public:
}
#if defined(ENABLE_TCO)
if (!isTailCall || (m_argument->type() != CallExpression))
if (!isTailCallForm || (m_argument->type() != CallExpression))
// skip End bytecode only if it directly returns the result of tail call
#endif
codeBlock->pushCode(End(ByteCodeLOC(m_loc.index), r), context, this->m_loc.index);

View file

@ -48,7 +48,7 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
ASSERT(m_expressions.size());
ByteCodeRegisterIndex r = 0;
@ -59,7 +59,7 @@ public:
}
// directly store the result of the last expression on to dstRegister
m_expressions.back()->astNode()->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
m_expressions.back()->astNode()->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
}
#endif

View file

@ -53,9 +53,9 @@ public:
}
#if defined(ENABLE_TCO)
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCall) override
virtual void generateTCOExpressionByteCode(ByteCodeBlock* codeBlock, ByteCodeGenerateContext* context, ByteCodeRegisterIndex dstRegister, bool& isTailCallForm) override
{
return m_convertedExpression->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCall);
return m_convertedExpression->generateTCOExpressionByteCode(codeBlock, context, dstRegister, isTailCallForm);
}
#endif

View file

@ -4839,7 +4839,6 @@ public:
template <class ASTBuilder>
ASTNode parseDebuggerStatement(ASTBuilder& builder)
{
ESCARGOT_LOG_ERROR("debugger keyword is not supported yet");
MetaNode node = this->createNode();
this->expectKeyword(KeywordKind::DebuggerKeyword);
this->consumeSemicolon();

View file

@ -43,7 +43,7 @@ ExecutionState::ExecutionState()
, m_onFinally(false)
#endif
#if defined(ENABLE_TCO)
, m_initTCO(false)
, m_inTCO(false)
#endif
, m_argc(0)
, m_argv(nullptr)

View file

@ -97,7 +97,7 @@ public:
, m_onFinally(false)
#endif
#if defined(ENABLE_TCO)
, m_initTCO(false)
, m_inTCO(false)
#endif
, m_argc(parent->argc())
, m_argv(parent->argv())
@ -119,7 +119,7 @@ public:
, m_onFinally(false)
#endif
#if defined(ENABLE_TCO)
, m_initTCO(false)
, m_inTCO(false)
#endif
, m_argc(0)
, m_argv(nullptr)
@ -141,7 +141,7 @@ public:
, m_onFinally(false)
#endif
#if defined(ENABLE_TCO)
, m_initTCO(false)
, m_inTCO(false)
#endif
, m_argc(argc)
, m_argv(argv)
@ -163,7 +163,7 @@ public:
, m_onFinally(false)
#endif
#if defined(ENABLE_TCO)
, m_initTCO(false)
, m_inTCO(false)
#endif
, m_argc(argc)
, m_argv(argv)
@ -248,18 +248,17 @@ public:
#endif
#if defined(ENABLE_TCO)
bool initTCO() const
bool inTCO() const
{
return m_initTCO;
return m_inTCO;
}
void setTCOArguments(Value* argv)
void initTCOWithBuffer(Value* argv)
{
// allocate a new argument buffer
// because tail call reuses this buffer which can modify caller's register file
ASSERT(!m_initTCO);
// initialize arguments buffer for tail call
ASSERT(!m_inTCO);
m_argv = argv;
m_initTCO = true; // initialize of TCO done
m_inTCO = true;
}
#endif
@ -339,7 +338,7 @@ protected:
bool m_onFinally : 1;
#endif
#if defined(ENABLE_TCO)
bool m_initTCO : 1;
bool m_inTCO : 1;
#endif
#ifdef ESCARGOT_32

View file

@ -224,11 +224,18 @@ public:
if (UNLIKELY(blk->m_shouldClearStack)) {
clearStack<512>();
}
#if defined(ENABLE_TCO)
if (!isConstructCall) {
if (UNLIKELY(newState->inTCO())) {
// callee has been called in tail call, so reset the argument buffer
memset(Interpreter::tcoBuffer, 0, sizeof(Value) * TCO_ARGUMENT_COUNT_LIMIT);
}
}
#endif
return returnValue;
}
private:
template <typename FunctionObjectType, bool hasNewTargetOnEnvironment, bool canBindThisValueOnEnvironment>
static NEVER_INLINE FunctionEnvironmentRecord* createFunctionEnvironmentRecord(ExecutionState& state, FunctionObjectType* self, InterpretedCodeBlock* codeBlock)
{

View file

@ -21,6 +21,7 @@
#include "PointerValue.h"
#include "FunctionObject.h"
#include "ErrorObject.h"
#include "ScriptFunctionObject.h"
namespace Escargot {
@ -37,6 +38,13 @@ size_t PointerValue::g_objectRareDataTag;
DECLARE_SCRIPTSIMPLEFUNCTION_LIST(DEFINE_SCRIPTSIMPLEFUNCTION_TAGS);
#undef DEFINE_SCRIPTSIMPLEFUNCTION_TAGS
#if defined(ENABLE_TCO)
bool PointerValue::canBeTailCallTargetRuntime(size_t argc)
{
return isScriptFunctionObject() && asScriptFunctionObject()->interpretedCodeBlock()->isTailCallTarget(argc);
}
#endif
Value PointerValue::call(ExecutionState& state, const Value& thisValue, const size_t argc, Value* argv)
{
ASSERT(!isCallable());

View file

@ -630,6 +630,10 @@ public:
RELEASE_ASSERT_NOT_REACHED();
}
#if defined(ENABLE_TCO)
bool canBeTailCallTargetRuntime(size_t argc);
#endif
String* asString()
{
ASSERT(isString());

View file

@ -26,6 +26,7 @@ namespace Escargot {
class ScriptFunctionObject : public FunctionObject {
friend class Script;
friend class Interpreter;
friend class InterpreterSlowPath;
friend class FunctionObjectProcessCallGenerator;
friend class Global;

View file

@ -99,6 +99,17 @@ protected:
}
}
#if defined(ENABLE_TCO)
const Value returnValue = Interpreter::interpret(&newState, blk, programStart, registerFile);
if (shouldClearStack) {
clearStack<512>();
}
if (UNLIKELY(newState.inTCO())) {
// callee has been called in tail call, so reset the argument buffer
memset(Interpreter::tcoBuffer, 0, sizeof(Value) * TCO_ARGUMENT_COUNT_LIMIT);
}
return returnValue;
#else
if (shouldClearStack) {
const Value returnValue = Interpreter::interpret(&newState, blk, programStart, registerFile);
clearStack<512>();
@ -106,6 +117,7 @@ protected:
} else {
return Interpreter::interpret(&newState, blk, programStart, registerFile);
}
#endif
}
virtual Value construct(ExecutionState& state, const size_t argc, Value* argv, Object* newTarget) override
@ -163,7 +175,6 @@ protected:
ExecutionState newState(ctx, &state, &lexEnv, argc, argv, isStrict);
stackStorage[0] = thisArgument;
record.setNewTarget(newTarget);
const Value returnValue = Interpreter::interpret(&newState, blk, reinterpret_cast<const size_t>(blk->m_code.data()), registerFile);

View file

@ -31,6 +31,9 @@
#include "runtime/ReloadableString.h"
#include "intl/Intl.h"
#include "interpreter/ByteCode.h"
#if defined(ENABLE_TCO)
#include "interpreter/ByteCodeInterpreter.h"
#endif
#if defined(ENABLE_CODE_CACHE)
#include "codecache/CodeCache.h"
#endif
@ -398,7 +401,6 @@ VMInstance::VMInstance(const char* locale, const char* timezone, const char* bas
}
#endif
// add gc event callback
GCEventListenerSet& list = ThreadLocal::gcEventListenerSet();
list.ensureMarkStartListeners()->push_back(std::make_pair(vmMarkStartCallback, this));
@ -480,6 +482,12 @@ VMInstance::VMInstance(const char* locale, const char* timezone, const char* bas
m_jobQueue = new JobQueue();
#if defined(ENABLE_TCO)
if (!Interpreter::tcoBuffer) {
Interpreter::initTCOBuffer();
}
#endif
#if defined(ENABLE_CODE_CACHE)
if (UNLIKELY(!baseCacheDir || strlen(baseCacheDir) == 0)) {
const char* homeDir = getenv("HOME");