diff options
author | Erik Verbruggen <erik.verbruggen@qt.io> | 2018-09-04 12:19:10 +0200 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@qt.io> | 2018-10-05 11:18:16 +0000 |
commit | 052d22e116957a170e290a49d077d3e9f290a237 (patch) | |
tree | 8d63f1b622a7c93d8b845c3d77f796f5ad18493b /src | |
parent | 08342d761369c3755778f6d69fa6f5907ae1aead (diff) |
ES7: Implement Tail Position Calls in the runtime
Change-Id: If1629109722496b3fd10b36b2376548440f2fee9
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/qml/jit/qv4assemblercommon.cpp | 45 | ||||
-rw-r--r-- | src/qml/jit/qv4assemblercommon_p.h | 61 | ||||
-rw-r--r-- | src/qml/jit/qv4baselineassembler.cpp | 24 | ||||
-rw-r--r-- | src/qml/jit/qv4baselineassembler_p.h | 4 | ||||
-rw-r--r-- | src/qml/jit/qv4baselinejit.cpp | 12 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4functionobject.cpp | 10 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4functionobject_p.h | 4 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4runtime.cpp | 25 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4runtimeapi_p.h | 8 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4stackframe_p.h | 6 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4vme_moth.cpp | 6 |
11 files changed, 174 insertions, 31 deletions
diff --git a/src/qml/jit/qv4assemblercommon.cpp b/src/qml/jit/qv4assemblercommon.cpp index 0ae4da17fa..fadb9dce08 100644 --- a/src/qml/jit/qv4assemblercommon.cpp +++ b/src/qml/jit/qv4assemblercommon.cpp @@ -212,6 +212,13 @@ PlatformAssemblerCommon::Address PlatformAssemblerCommon::argStackAddress(int ar return Address(StackPointerRegister, offset * PointerSize); } +JSC::MacroAssemblerBase::Address PlatformAssemblerCommon::inArgStackAddress(int arg) +{ + int offset = arg - ArgInRegCount; + Q_ASSERT(offset >= 0); + return Address(FramePointerRegister, -(offset + 1) * PointerSize); +} + void PlatformAssemblerCommon::passAccumulatorAsArg(int arg) { #ifndef QT_NO_DEBUG @@ -329,12 +336,48 @@ void PlatformAssemblerCommon::callRuntime(const char *functionName, const void * } } -void JIT::PlatformAssemblerCommon::callRuntimeUnchecked(const char *functionName, const void *funcPtr) +void PlatformAssemblerCommon::callRuntimeUnchecked(const char *functionName, const void *funcPtr) { functions.insert(funcPtr, functionName); callAbsolute(funcPtr); } +void PlatformAssemblerCommon::tailCallRuntime(const char *functionName, const void *funcPtr) +{ + functions.insert(funcPtr, functionName); + setTailCallArg(EngineRegister, 1); + setTailCallArg(CppStackFrameRegister, 0); + freeStackSpace(); + generatePlatformFunctionExit(/*tailCall =*/ true); + jumpAbsolute(funcPtr); +} + +void PlatformAssemblerCommon::setTailCallArg(RegisterID src, int arg) +{ + if (arg < ArgInRegCount) + move(src, registerForArg(arg)); + else + storePtr(src, inArgStackAddress(arg)); +} + +JSC::MacroAssemblerBase::Address PlatformAssemblerCommon::jsAlloca(int slotCount) +{ + Address jsStackTopAddr(EngineRegister, offsetof(EngineBase, jsStackTop)); + RegisterID jsStackTop = AccumulatorRegisterValue; + loadPtr(jsStackTopAddr, jsStackTop); + addPtr(TrustedImm32(sizeof(Value) * slotCount), jsStackTop); + storePtr(jsStackTop, jsStackTopAddr); + return Address(jsStackTop, 0); +} + +void PlatformAssemblerCommon::storeInt32AsValue(int srcInt, Address destAddr) +{ + store32(TrustedImm32(srcInt), + Address(destAddr.base, destAddr.offset + QV4::Value::valueOffset())); + store32(TrustedImm32(int(QV4::Value::ValueTypeInternal::Integer)), + Address(destAddr.base, destAddr.offset + QV4::Value::tagOffset())); +} + } // JIT namespace } // QV4 namepsace diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index d64b9d0e5d..cbbd6464d9 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -116,14 +116,15 @@ public: move(Arg1Reg, EngineRegister); } - void generatePlatformFunctionExit() + void generatePlatformFunctionExit(bool tailCall = false) { pop(EngineRegister); pop(CppStackFrameRegister); pop(JSStackFrameRegister); pop(); // exceptionHandler pop(FramePointerRegister); - ret(); + if (!tailCall) + ret(); } void callAbsolute(const void *funcPtr) @@ -132,6 +133,12 @@ public: call(ScratchRegister); } + void jumpAbsolute(const void *funcPtr) + { + move(TrustedImmPtr(funcPtr), ScratchRegister); + jump(ScratchRegister); + } + void pushAligned(RegisterID reg) { subPtr(TrustedImm32(PointerSize), StackPointerRegister); @@ -195,14 +202,15 @@ public: move(Arg1Reg, EngineRegister); } - void generatePlatformFunctionExit() + void generatePlatformFunctionExit(bool tailCall = false) { pop(EngineRegister); pop(CppStackFrameRegister); pop(JSStackFrameRegister); pop(); // exceptionHandler pop(FramePointerRegister); - ret(); + if (!tailCall) + ret(); } void callAbsolute(const void *funcPtr) @@ -213,6 +221,12 @@ public: addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister); } + void jumpAbsolute(const void *funcPtr) + { + move(TrustedImmPtr(funcPtr), ScratchRegister); + jump(ScratchRegister); + } + void pushAligned(RegisterID reg) { subPtr(TrustedImm32(PointerSize), StackPointerRegister); @@ -280,7 +294,7 @@ public: loadPtr(Address(FramePointerRegister, 3 * PointerSize), EngineRegister); } - void generatePlatformFunctionExit() + void generatePlatformFunctionExit(bool tailCall = false) { addPtr(TrustedImm32(8), StackPointerRegister); pop(EngineRegister); @@ -288,7 +302,8 @@ public: pop(JSStackFrameRegister); pop(); // exceptionHandler pop(RegisterID::ebp); - ret(); + if (!tailCall) + ret(); } void callAbsolute(const void *funcPtr) @@ -297,6 +312,12 @@ public: call(ScratchRegister); } + void jumpAbsolute(const void *funcPtr) + { + move(TrustedImmPtr(funcPtr), ScratchRegister); + jump(ScratchRegister); + } + void pushAligned(RegisterID reg) { subPtr(TrustedImm32(PointerSize), StackPointerRegister); @@ -375,13 +396,14 @@ public: move(Arg1Reg, EngineRegister); } - void generatePlatformFunctionExit() + void generatePlatformFunctionExit(bool tailCall = false) { move(AccumulatorRegister, ReturnValueRegister); popPair(EngineRegister, CppStackFrameRegister); popPair(JSStackFrameRegister, AccumulatorRegister); popPair(JSC::ARM64Registers::fp, JSC::ARM64Registers::lr); - ret(); + if (!tailCall) + ret(); } void callAbsolute(const void *funcPtr) @@ -390,6 +412,12 @@ public: call(ScratchRegister); } + void jumpAbsolute(const void *funcPtr) + { + move(TrustedImmPtr(funcPtr), ScratchRegister); + jump(ScratchRegister); + } + void pushAligned(RegisterID reg) { pushToSave(reg); @@ -462,7 +490,7 @@ public: move(Arg1Reg, EngineRegister); } - void generatePlatformFunctionExit() + void generatePlatformFunctionExit(bool tailCall = false) { move(AccumulatorRegisterValue, ReturnValueRegisterValue); move(AccumulatorRegisterTag, ReturnValueRegisterTag); @@ -476,7 +504,8 @@ public: pop(); // exceptionHandler pop(FramePointerRegister); pop(JSC::ARMRegisters::lr); - ret(); + if (!tailCall) + ret(); } void callAbsolute(const void *funcPtr) @@ -485,6 +514,12 @@ public: call(dataTempRegister); } + void jumpAbsolute(const void *funcPtr) + { + move(TrustedImmPtr(funcPtr), dataTempRegister); + jump(dataTempRegister); + } + void pushAligned(RegisterID reg) { subPtr(TrustedImm32(PointerSize), StackPointerRegister); @@ -663,11 +698,15 @@ public: void passInt32AsArg(int value, int arg); void callRuntime(const char *functionName, const void *funcPtr); void callRuntimeUnchecked(const char *functionName, const void *funcPtr); - + void tailCallRuntime(const char *functionName, const void *funcPtr); + void setTailCallArg(RegisterID src, int arg); + Address jsAlloca(int slotCount); + void storeInt32AsValue(int srcInt, Address destAddr); private: void passAccumulatorAsArg_internal(int arg, bool doPush); static Address argStackAddress(int arg); + static Address inArgStackAddress(int arg); private: const Value* constantTable; diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index d53bb84827..f6b745632b 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -1446,7 +1446,29 @@ void BaselineAssembler::callRuntime(const char *functionName, const void *funcPt void BaselineAssembler::saveAccumulatorInFrame() { pasm()->storeAccumulator(PlatformAssembler::Address(PlatformAssembler::JSStackFrameRegister, - offsetof(CallData, accumulator))); + offsetof(CallData, accumulator))); +} + +static ReturnedValue TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing(CppStackFrame *frame, ExecutionEngine *engine) +{ + return Runtime::method_tailCall(frame, engine); +} + +void BaselineAssembler::jsTailCall(int func, int thisObject, int argc, int argv) +{ + Address tos = pasm()->jsAlloca(4); + + int32_t argcOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_argc; + int32_t argvOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_argv; + int32_t thisOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_thisObject; + int32_t funcOffset = tos.offset + int32_t(sizeof(Value)) * Runtime::StackOffsets::tailCall_function; + + pasm()->storeInt32AsValue(argc, Address(tos.base, argcOffset)); + pasm()->storeInt32AsValue(argv, Address(tos.base, argvOffset)); + pasm()->moveReg(regAddr(thisObject), Address(tos.base, thisOffset)); + pasm()->moveReg(regAddr(func), Address(tos.base, funcOffset)); + pasm()->tailCallRuntime("TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing", + reinterpret_cast<void *>(TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing)); } void BaselineAssembler::checkException() diff --git a/src/qml/jit/qv4baselineassembler_p.h b/src/qml/jit/qv4baselineassembler_p.h index a2140ce47b..0aa508ae71 100644 --- a/src/qml/jit/qv4baselineassembler_p.h +++ b/src/qml/jit/qv4baselineassembler_p.h @@ -67,6 +67,9 @@ namespace JIT { callRuntime(JIT_STRINGIFY(function), \ reinterpret_cast<void *>(&function), \ destination) +#define GENERATE_TAIL_CALL(function) \ + tailCallRuntime(JIT_STRINGIFY(function), \ + reinterpret_cast<void *>(&function)) class BaselineAssembler { public: @@ -149,6 +152,7 @@ public: void passInt32AsArg(int value, int arg); void callRuntime(const char *functionName, const void *funcPtr, CallResultDestination dest); void saveAccumulatorInFrame(); + void jsTailCall(int func, int thisObject, int argc, int argv); // exception/context stuff void checkException(); diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 70d1672689..bbbe0c69c5 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -77,6 +77,8 @@ void BaselineJIT::generate() #define STORE_ACC() as->saveAccumulatorInFrame() #define BASELINEJIT_GENERATE_RUNTIME_CALL(function, destination) \ as->GENERATE_RUNTIME_CALL(function, destination) +#define BASELINEJIT_GENERATE_TAIL_CALL(function) \ + as->GENERATE_TAIL_CALL(function) void BaselineJIT::generate_Ret() { @@ -555,17 +557,9 @@ void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, in void BaselineJIT::generate_TailCall(int func, int thisObject, int argc, int argv) { STORE_IP(); - as->prepareCallWithArgCount(5); - as->passInt32AsArg(argc, 4); - as->passJSSlotAsArg(argv, 3); - as->passJSSlotAsArg(thisObject, 2); - as->passJSSlotAsArg(func, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_tailCall, CallResultDestination::InAccumulator); - as->checkException(); + as->jsTailCall(func, thisObject, argc, argv); } - void BaselineJIT::generate_Construct(int func, int argc, int argv) { STORE_IP(); 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) |