aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@qt.io>2018-09-04 12:19:10 +0200
committerErik Verbruggen <erik.verbruggen@qt.io>2018-10-05 11:18:16 +0000
commit052d22e116957a170e290a49d077d3e9f290a237 (patch)
tree8d63f1b622a7c93d8b845c3d77f796f5ad18493b /src/qml/jsruntime
parent08342d761369c3755778f6d69fa6f5907ae1aead (diff)
ES7: Implement Tail Position Calls in the runtime
Change-Id: If1629109722496b3fd10b36b2376548440f2fee9 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src/qml/jsruntime')
-rw-r--r--src/qml/jsruntime/qv4functionobject.cpp10
-rw-r--r--src/qml/jsruntime/qv4functionobject_p.h4
-rw-r--r--src/qml/jsruntime/qv4runtime.cpp25
-rw-r--r--src/qml/jsruntime/qv4runtimeapi_p.h8
-rw-r--r--src/qml/jsruntime/qv4stackframe_p.h6
-rw-r--r--src/qml/jsruntime/qv4vme_moth.cpp6
6 files changed, 50 insertions, 9 deletions
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<const FunctionObject &>(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<Value *>(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<const FunctionObject &>(function).call(&thisObject, argv, argc);
+ const FunctionObject &fo = static_cast<const FunctionObject &>(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<void (*)(QV4::NoThrowEngine *, A, B, C)> {
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<Runtime>::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)