From 052d22e116957a170e290a49d077d3e9f290a237 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 4 Sep 2018 12:19:10 +0200 Subject: ES7: Implement Tail Position Calls in the runtime Change-Id: If1629109722496b3fd10b36b2376548440f2fee9 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4functionobject.cpp | 10 ++++++++-- src/qml/jsruntime/qv4functionobject_p.h | 4 +++- src/qml/jsruntime/qv4runtime.cpp | 25 ++++++++++++++++++++++--- src/qml/jsruntime/qv4runtimeapi_p.h | 8 +++++++- src/qml/jsruntime/qv4stackframe_p.h | 6 +++++- src/qml/jsruntime/qv4vme_moth.cpp | 6 +++++- 6 files changed, 50 insertions(+), 9 deletions(-) (limited to 'src/qml/jsruntime') diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index f6b279ddaf..93cc55f8ad 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -498,7 +498,7 @@ ReturnedValue ArrowFunction::virtualCall(const FunctionObject *fo, const Value * { ExecutionEngine *engine = fo->engine(); CppStackFrame frame; - frame.init(engine, fo->function(), argv, argc); + frame.init(engine, fo->function(), argv, argc, true); frame.setupJSFrame(engine->jsStackTop, *fo, fo->scope(), thisObject ? *thisObject : Value::undefinedValue(), Value::undefinedValue()); @@ -506,7 +506,12 @@ ReturnedValue ArrowFunction::virtualCall(const FunctionObject *fo, const Value * frame.push(); engine->jsStackTop += frame.requiredJSStackFrameSize(); - ReturnedValue result = Moth::VME::exec(&frame, engine); + ReturnedValue result; + + do { + frame.pendingTailCall = false; + result = Moth::VME::exec(&frame, engine); + } while (frame.pendingTailCall); frame.pop(); @@ -530,6 +535,7 @@ void Heap::ArrowFunction::init(QV4::ExecutionContext *scope, Function *function, Q_ASSERT(internalClass && internalClass->verifyIndex(s.engine->id_length()->propertyKey(), Index_Length)); setProperty(s.engine, Index_Length, Value::fromInt32(int(function->compiledFunction->length))); + canBeTailCalled = true; } void Heap::ScriptFunction::init(QV4::ExecutionContext *scope, Function *function) diff --git a/src/qml/jsruntime/qv4functionobject_p.h b/src/qml/jsruntime/qv4functionobject_p.h index b08b333411..e03d49c74d 100644 --- a/src/qml/jsruntime/qv4functionobject_p.h +++ b/src/qml/jsruntime/qv4functionobject_p.h @@ -71,7 +71,8 @@ namespace Heap { Member(class, Pointer, ExecutionContext *, scope) \ Member(class, NoMark, Function *, function) \ Member(class, NoMark, VTable::Call, jsCall) \ - Member(class, NoMark, VTable::CallAsConstructor, jsConstruct) + Member(class, NoMark, VTable::CallAsConstructor, jsConstruct) \ + Member(class, NoMark, bool, canBeTailCalled) DECLARE_HEAP_OBJECT(FunctionObject, Object) { DECLARE_MARKOBJECTS(FunctionObject); @@ -175,6 +176,7 @@ struct Q_QML_EXPORT FunctionObject: Object { V4_NEEDS_DESTROY enum { NInlineProperties = 1 }; + bool canBeTailCalled() const { return d()->canBeTailCalled; } Heap::ExecutionContext *scope() const { return d()->scope; } Function *function() const { return d()->function; } diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 470629bd1f..66cd06ee1f 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1536,13 +1536,32 @@ ReturnedValue Runtime::method_constructWithSpread(ExecutionEngine *engine, const return static_cast(function).callAsConstructor(arguments.argv, arguments.argc, &newTarget); } -ReturnedValue Runtime::method_tailCall(ExecutionEngine *engine, const Value &function, const Value &thisObject, Value *argv, int argc) +ReturnedValue Runtime::method_tailCall(CppStackFrame *frame, ExecutionEngine *engine) { - //### unwinding the stack, etc, is done in a subsequent patch + // IMPORTANT! The JIT assumes that this method has the same amount (or less) arguments than + // the jitted function, so it can safely do a tail call. + + Value *tos = engine->jsStackTop; + const Value &function = tos[StackOffsets::tailCall_function]; + const Value &thisObject = tos[StackOffsets::tailCall_thisObject]; + Value *argv = reinterpret_cast(frame->jsFrame) + tos[StackOffsets::tailCall_argv].int_32(); + int argc = tos[StackOffsets::tailCall_argc].int_32(); + if (!function.isFunctionObject()) return engine->throwTypeError(); - return static_cast(function).call(&thisObject, argv, argc); + const FunctionObject &fo = static_cast(function); + if (!frame->callerCanHandleTailCall || !fo.canBeTailCalled() || engine->debugger()) { + // Cannot tailcall, do a normal call: + return fo.call(&thisObject, argv, argc); + } + + memcpy(frame->jsFrame->args, argv, argc * sizeof(Value)); + frame->init(engine, fo.function(), frame->jsFrame->args, argc, frame->callerCanHandleTailCall); + frame->setupJSFrame(frame->savedStackTop, fo, fo.scope(), thisObject, Primitive::undefinedValue()); + engine->jsStackTop = frame->savedStackTop + frame->requiredJSStackFrameSize(); + frame->pendingTailCall = true; + return Encode::undefined(); } void Runtime::method_throwException(ExecutionEngine *engine, const Value &value) diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index 826b371c1d..d64178a72f 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -101,7 +101,7 @@ struct ExceptionCheck { F(ReturnedValue, callWithReceiver, (ExecutionEngine *engine, const Value &func, const Value *thisObject, Value *argv, int argc)) \ F(ReturnedValue, callPossiblyDirectEval, (ExecutionEngine *engine, Value *argv, int argc)) \ F(ReturnedValue, callWithSpread, (ExecutionEngine *engine, const Value &func, const Value &thisObject, Value *argv, int argc)) \ - F(ReturnedValue, tailCall, (ExecutionEngine *engine, const Value &func, const Value &thisObject, Value *argv, int argc)) \ + F(ReturnedValue, tailCall, (CppStackFrame *frame, ExecutionEngine *engine)) \ \ /* construct */ \ F(ReturnedValue, construct, (ExecutionEngine *engine, const Value &func, const Value &newTarget, Value *argv, int argc)) \ @@ -234,6 +234,12 @@ struct Q_QML_PRIVATE_EXPORT Runtime { FOR_EACH_RUNTIME_METHOD(RUNTIME_METHOD) #undef RUNTIME_METHOD + struct StackOffsets { + static const int tailCall_function = -1; + static const int tailCall_thisObject = -2; + static const int tailCall_argv = -3; + static const int tailCall_argc = -4; + }; }; static_assert(std::is_standard_layout::value, "Runtime needs to be standard layout in order for us to be able to use offsetof"); diff --git a/src/qml/jsruntime/qv4stackframe_p.h b/src/qml/jsruntime/qv4stackframe_p.h index dd68c29a88..a97ae0e7c9 100644 --- a/src/qml/jsruntime/qv4stackframe_p.h +++ b/src/qml/jsruntime/qv4stackframe_p.h @@ -123,8 +123,10 @@ struct Q_QML_EXPORT CppStackFrame { const char *unwindLabel; int unwindLevel; bool yieldIsIterator; + bool callerCanHandleTailCall; + bool pendingTailCall; - void init(EngineBase *engine, Function *v4Function, const Value *argv, int argc) { + void init(EngineBase *engine, Function *v4Function, const Value *argv, int argc, bool callerCanHandleTailCall = false) { this->engine = engine; this->v4Function = v4Function; @@ -136,6 +138,8 @@ struct Q_QML_EXPORT CppStackFrame { unwindLabel = nullptr; unwindLevel = 0; yieldIsIterator = false; + this->callerCanHandleTailCall = callerCanHandleTailCall; + pendingTailCall = false; } void push() { diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 7fd7be8e38..5d95c8c2ab 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -778,7 +778,11 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(TailCall) STORE_IP(); - acc = Runtime::method_tailCall(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); + *engine->jsAlloca(1) = Primitive::fromInt32(argc); + *engine->jsAlloca(1) = Primitive::fromInt32(argv); + *engine->jsAlloca(1) = STACK_VALUE(thisObject); + *engine->jsAlloca(1) = STACK_VALUE(func); + return Runtime::method_tailCall(frame, engine); CHECK_EXCEPTION; MOTH_END_INSTR(TailCall) -- cgit v1.2.3