diff options
Diffstat (limited to 'src/qml/jit')
31 files changed, 9299 insertions, 373 deletions
diff --git a/src/qml/jit/jit.pri b/src/qml/jit/jit.pri index 2c664af188..bc0d20dba7 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -2,13 +2,42 @@ INCLUDEPATH += $$PWD INCLUDEPATH += $$OUT_PWD SOURCES += \ - $$PWD/qv4jithelpers.cpp \ $$PWD/qv4baselinejit.cpp \ $$PWD/qv4baselineassembler.cpp \ $$PWD/qv4assemblercommon.cpp HEADERS += \ - $$PWD/qv4jithelpers_p.h \ $$PWD/qv4baselinejit_p.h \ $$PWD/qv4baselineassembler_p.h \ $$PWD/qv4assemblercommon_p.h + +qtConfig(qml-tracing) { +SOURCES += \ + $$PWD/qv4ir.cpp \ + $$PWD/qv4operation.cpp \ + $$PWD/qv4node.cpp \ + $$PWD/qv4graph.cpp \ + $$PWD/qv4graphbuilder.cpp \ + $$PWD/qv4lowering.cpp \ + $$PWD/qv4tracingjit.cpp \ + $$PWD/qv4mi.cpp \ + $$PWD/qv4domtree.cpp \ + $$PWD/qv4schedulers.cpp \ + $$PWD/qv4blockscheduler.cpp \ + $$PWD/qv4loopinfo.cpp + +HEADERS += \ + $$PWD/qv4ir_p.h \ + $$PWD/qv4operation_p.h \ + $$PWD/qv4runtimesupport_p.h \ + $$PWD/qv4node_p.h \ + $$PWD/qv4graph_p.h \ + $$PWD/qv4graphbuilder_p.h \ + $$PWD/qv4lowering_p.h \ + $$PWD/qv4mi_p.h \ + $$PWD/qv4miblockset_p.h \ + $$PWD/qv4domtree_p.h \ + $$PWD/qv4schedulers_p.h \ + $$PWD/qv4blockscheduler_p.h \ + $$PWD/qv4loopinfo_p.h +} diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index 25c74e74e8..238c11f478 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -943,7 +943,7 @@ void BaselineAssembler::uminus() saveAccumulatorInFrame(); pasm()->prepareCallWithArgCount(1); pasm()->passAccumulatorAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_uMinus, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(UMinus, CallResultDestination::InAccumulator); checkException(); } @@ -1044,7 +1044,7 @@ void BaselineAssembler::add(int lhs) pasm()->passAccumulatorAsArg(2); pasm()->passJSSlotAsArg(lhs, 1); pasm()->passEngineAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_add, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Add, CallResultDestination::InAccumulator); checkException(); // done. @@ -1196,7 +1196,7 @@ void BaselineAssembler::mul(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_mul, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Mul, CallResultDestination::InAccumulator); checkException(); // done. @@ -1209,7 +1209,7 @@ void BaselineAssembler::div(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_div, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Div, CallResultDestination::InAccumulator); checkException(); } @@ -1219,7 +1219,7 @@ void BaselineAssembler::mod(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_mod, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Mod, CallResultDestination::InAccumulator); checkException(); } @@ -1239,7 +1239,7 @@ void BaselineAssembler::sub(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_sub, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Sub, CallResultDestination::InAccumulator); checkException(); // done. @@ -1269,7 +1269,7 @@ void BaselineAssembler::cmpeqInt(int lhs) else pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1)); pasm()->pushAccumulatorAsArg(0); - pasm()->callRuntimeUnchecked("Runtime::method_equal", (void*)Runtime::method_equal); + pasm()->callRuntimeUnchecked("Equal", (void*)Runtime::Equal::call); pasm()->saveReturnValueInAccumulator(); if (PlatformAssembler::ArgInRegCount < 2) pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister); @@ -1293,7 +1293,7 @@ void BaselineAssembler::cmpneInt(int lhs) else pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1)); pasm()->pushAccumulatorAsArg(0); - pasm()->callRuntimeUnchecked("Runtime::method_notEqual", (void*)Runtime::method_notEqual); + pasm()->callRuntimeUnchecked("NotEqual", (void*)Runtime::NotEqual::call); pasm()->saveReturnValueInAccumulator(); if (PlatformAssembler::ArgInRegCount < 2) pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister); @@ -1314,7 +1314,6 @@ void BaselineAssembler::cmp(int cond, CmpFunc function, const char *functionName pasm()->compare32(c, PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue, PlatformAssembler::AccumulatorRegisterValue); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); return PlatformAssembler::Jump(); }); @@ -1326,60 +1325,58 @@ void BaselineAssembler::cmp(int cond, CmpFunc function, const char *functionName callRuntime(functionName, reinterpret_cast<void*>(function), CallResultDestination::InAccumulator); checkException(); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); // done. done.link(pasm()); + pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); } void BaselineAssembler::cmpeq(int lhs) { - cmp(PlatformAssembler::Equal, &Runtime::method_compareEqual, - "Runtime::method_compareEqual", lhs); + cmp(PlatformAssembler::Equal, &Runtime::CompareEqual::call, + "CompareEqual", lhs); } void BaselineAssembler::cmpne(int lhs) { - cmp(PlatformAssembler::NotEqual, &Runtime::method_compareNotEqual, - "Runtime::method_compareNotEqual", lhs); + cmp(PlatformAssembler::NotEqual, &Runtime::CompareNotEqual::call, + "CompareNotEqual", lhs); } void BaselineAssembler::cmpgt(int lhs) { - cmp(PlatformAssembler::GreaterThan, &Runtime::method_compareGreaterThan, - "Runtime::method_compareGreaterThan", lhs); + cmp(PlatformAssembler::GreaterThan, &Runtime::CompareGreaterThan::call, + "CompareGreaterThan", lhs); } void BaselineAssembler::cmpge(int lhs) { - cmp(PlatformAssembler::GreaterThanOrEqual, &Runtime::method_compareGreaterEqual, - "Runtime::method_compareGreaterEqual", lhs); + cmp(PlatformAssembler::GreaterThanOrEqual, &Runtime::CompareGreaterEqual::call, + "CompareGreaterEqual", lhs); } void BaselineAssembler::cmplt(int lhs) { - cmp(PlatformAssembler::LessThan, &Runtime::method_compareLessThan, - "Runtime::method_compareLessThan", lhs); + cmp(PlatformAssembler::LessThan, &Runtime::CompareLessThan::call, + "CompareLessThan", lhs); } void BaselineAssembler::cmple(int lhs) { - cmp(PlatformAssembler::LessThanOrEqual, &Runtime::method_compareLessEqual, - "Runtime::method_compareLessEqual", lhs); + cmp(PlatformAssembler::LessThanOrEqual, &Runtime::CompareLessEqual::call, + "CompareLessEqual", lhs); } void BaselineAssembler::cmpStrictEqual(int lhs) { - cmp(PlatformAssembler::Equal, &RuntimeHelpers::strictEqual, + cmp(PlatformAssembler::Equal, &Runtime::CompareStrictEqual::call, "RuntimeHelpers::strictEqual", lhs); } void BaselineAssembler::cmpStrictNotEqual(int lhs) { - cmp(PlatformAssembler::Equal, &RuntimeHelpers::strictEqual, - "RuntimeHelpers::strictEqual", lhs); - pasm()->xor32(TrustedImm32(1), PlatformAssembler::AccumulatorRegisterValue); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); + cmp(PlatformAssembler::NotEqual, &Runtime::CompareStrictNotEqual::call, + "RuntimeHelpers::strictNotEqual", lhs); } int BaselineAssembler::jump(int offset) @@ -1481,7 +1478,7 @@ void BaselineAssembler::saveAccumulatorInFrame() static ReturnedValue TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing(CppStackFrame *frame, ExecutionEngine *engine) { - return Runtime::method_tailCall(frame, engine); + return Runtime::TailCall::call(frame, engine); } void BaselineAssembler::jsTailCall(int func, int thisObject, int argc, int argv) @@ -1588,9 +1585,8 @@ void BaselineAssembler::pushCatchContext(int index, int name) pasm()->prepareCallWithArgCount(3); pasm()->passInt32AsArg(name, 2); pasm()->passInt32AsArg(index, 1); - pasm()->passJSSlotAsArg(CallData::Context, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_createCatchContext, CallResultDestination::InAccumulator); - pasm()->storeAccumulator(pasm()->contextAddress()); + pasm()->passEngineAsArg(0); + ASM_GENERATE_RUNTIME_CALL(PushCatchContext, CallResultDestination::Ignore); } void BaselineAssembler::popContext() @@ -1610,7 +1606,7 @@ void BaselineAssembler::deadTemporalZoneCheck(int offsetForSavedIP, int variable prepareCallWithArgCount(2); passInt32AsArg(variableName, 1); passEngineAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_throwReferenceError, CallResultDestination::Ignore); + ASM_GENERATE_RUNTIME_CALL(ThrowReferenceError, CallResultDestination::Ignore); gotoCatchException(); valueIsAliveJump.link(pasm()); } diff --git a/src/qml/jit/qv4baselineassembler_p.h b/src/qml/jit/qv4baselineassembler_p.h index c39d002bf9..3bbaefd000 100644 --- a/src/qml/jit/qv4baselineassembler_p.h +++ b/src/qml/jit/qv4baselineassembler_p.h @@ -65,7 +65,7 @@ namespace JIT { #define GENERATE_RUNTIME_CALL(function, destination) \ callRuntime(JIT_STRINGIFY(function), \ - reinterpret_cast<void *>(&function), \ + reinterpret_cast<void *>(&Runtime::function::call), \ destination) #define GENERATE_TAIL_CALL(function) \ tailCallRuntime(JIT_STRINGIFY(function), \ diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index e518fc5a0e..80155d7b20 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qv4baselinejit_p.h" -#include "qv4jithelpers_p.h" #include "qv4baselineassembler_p.h" #include <private/qv4lookup_p.h> #include <private/qv4generatorobject_p.h> @@ -77,10 +76,11 @@ void BaselineJIT::generate() #define STORE_IP() as->storeInstructionPointer(nextInstructionOffset()) #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) +#define BASELINEJIT_GENERATE_RUNTIME_CALL(function, destination) { \ + as->GENERATE_RUNTIME_CALL(function, destination); \ + if (Runtime::function::throws) \ + as->checkException(); \ + else {} } // this else prevents else statements after the macro from attaching to the if above void BaselineJIT::generate_Ret() { @@ -183,7 +183,7 @@ void BaselineJIT::generate_MoveRegExp(int regExpId, int destReg) as->prepareCallWithArgCount(2); as->passInt32AsArg(regExpId, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_regexpLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(RegexpLiteral, CallResultDestination::InAccumulator); as->storeReg(destReg); } @@ -192,7 +192,7 @@ void BaselineJIT::generate_LoadClosure(int value) as->prepareCallWithArgCount(2); as->passInt32AsArg(value, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_closure, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(Closure, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) @@ -201,28 +201,24 @@ void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) as->prepareCallWithArgCount(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadName, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadGlobalLookup(int index, int /*traceSlot*/) { as->prepareCallWithArgCount(3); as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::loadGlobalLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadGlobalLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadQmlContextPropertyLookup(int index, int /*traceSlot*/) { - as->prepareCallWithArgCount(3); - as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::loadQmlContextPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(2); + as->passInt32AsArg(index, 1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadQmlContextPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreNameSloppy(int name) @@ -233,8 +229,7 @@ void BaselineJIT::generate_StoreNameSloppy(int name) as->passAccumulatorAsArg(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeNameSloppy, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreNameSloppy, CallResultDestination::Ignore); } void BaselineJIT::generate_StoreNameStrict(int name) @@ -245,8 +240,7 @@ void BaselineJIT::generate_StoreNameStrict(int name) as->passAccumulatorAsArg(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeNameStrict, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreNameStrict, CallResultDestination::Ignore); } void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) @@ -257,8 +251,7 @@ void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) @@ -270,8 +263,7 @@ void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) as->passJSSlotAsArg(index, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeElement, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreElement, CallResultDestination::Ignore); } void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) @@ -282,8 +274,7 @@ void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) as->passInt32AsArg(name, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_GetLookup(int index, int /*traceSlot*/) @@ -293,10 +284,9 @@ void BaselineJIT::generate_GetLookup(int index, int /*traceSlot*/) as->prepareCallWithArgCount(4); as->passInt32AsArg(index, 3); as->passAccumulatorAsArg(2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::getLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreProperty(int name, int base) @@ -308,8 +298,7 @@ void BaselineJIT::generate_StoreProperty(int name, int base) as->passInt32AsArg(name, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeProperty, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreProperty, CallResultDestination::Ignore); } void BaselineJIT::generate_SetLookup(int index, int base) @@ -318,12 +307,13 @@ void BaselineJIT::generate_SetLookup(int index, int base) STORE_ACC(); as->prepareCallWithArgCount(4); as->passAccumulatorAsArg(3); - as->passJSSlotAsArg(base, 2); - as->passInt32AsArg(index, 1); + as->passInt32AsArg(index, 2); + as->passJSSlotAsArg(base, 1); as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL((function->isStrict() ? Helpers::setLookupStrict : Helpers::setLookupSloppy), - CallResultDestination::InAccumulator); - as->checkException(); + if (function->isStrict()) + BASELINEJIT_GENERATE_RUNTIME_CALL(SetLookupStrict, CallResultDestination::InAccumulator) + else + BASELINEJIT_GENERATE_RUNTIME_CALL(SetLookupSloppy, CallResultDestination::InAccumulator) } void BaselineJIT::generate_LoadSuperProperty(int property) @@ -333,8 +323,7 @@ void BaselineJIT::generate_LoadSuperProperty(int property) as->prepareCallWithArgCount(2); as->passJSSlotAsArg(property, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadSuperProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadSuperProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreSuperProperty(int property) @@ -345,8 +334,7 @@ void BaselineJIT::generate_StoreSuperProperty(int property) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(property, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeSuperProperty, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreSuperProperty, CallResultDestination::Ignore); } void BaselineJIT::generate_Yield() @@ -375,8 +363,7 @@ void BaselineJIT::generate_CallValue(int name, int argc, int argv, int /*traceSl as->passJSSlotAsArg(argv, 2); as->passJSSlotAsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callValue, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallValue, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, int /*traceSlot*/) @@ -388,8 +375,7 @@ void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, as->passJSSlotAsArg(thisObject, 2); as->passJSSlotAsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callWithReceiver, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallWithReceiver, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) @@ -401,8 +387,7 @@ void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, as->passInt32AsArg(name, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, int /*traceSlot*/) @@ -414,8 +399,7 @@ void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int arg as->passInt32AsArg(lookupIndex, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) @@ -427,8 +411,7 @@ void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, as->passJSSlotAsArg(index, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) @@ -439,8 +422,7 @@ void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlo as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callName, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) @@ -450,8 +432,7 @@ void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*trac as->passInt32AsArg(argc, 2); as->passJSSlotAsArg(argv, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callPossiblyDirectEval, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallPossiblyDirectEval, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) @@ -462,8 +443,7 @@ void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int / as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(index, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callGlobalLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallGlobalLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, @@ -475,8 +455,7 @@ void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(index, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callQmlContextPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallQmlContextPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, int argv, int /*traceSlot*/) @@ -488,8 +467,7 @@ void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, in as->passJSSlotAsArg(thisObject, 2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callWithSpread, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallWithSpread, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TailCall(int func, int thisObject, int argc, int argv) @@ -508,8 +486,7 @@ void BaselineJIT::generate_Construct(int func, int argc, int argv) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_construct, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Construct, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ConstructWithSpread(int func, int argc, int argv) @@ -522,8 +499,7 @@ void BaselineJIT::generate_ConstructWithSpread(int func, int argc, int argv) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_constructWithSpread, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ConstructWithSpread, CallResultDestination::InAccumulator); } void BaselineJIT::generate_SetUnwindHandler(int offset) @@ -556,7 +532,7 @@ void BaselineJIT::generate_ThrowException() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_throwException, CallResultDestination::Ignore); + BASELINEJIT_GENERATE_RUNTIME_CALL(ThrowException, CallResultDestination::Ignore); as->gotoCatchException(); } @@ -567,8 +543,7 @@ void BaselineJIT::generate_CreateCallContext() { as->prepareCallWithArgCount(1); as->passCppFrameAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(ExecutionContext::newCallContext, CallResultDestination::Ignore); // keeps result in return value register - as->storeHeapObject(CallData::Context); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushCallContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PushCatchContext(int index, int name) { as->pushCatchContext(index, name); } @@ -578,11 +553,9 @@ void BaselineJIT::generate_PushWithContext() STORE_IP(); as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(2); - as->passJSSlotAsArg(0, 1); + as->passJSSlotAsArg(CallData::Accumulator, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createWithContext, CallResultDestination::Ignore); // keeps result in return value register - as->checkException(); - as->storeHeapObject(CallData::Context); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushWithContext, CallResultDestination::InAccumulator); } void BaselineJIT::generate_PushBlockContext(int index) @@ -590,35 +563,33 @@ void BaselineJIT::generate_PushBlockContext(int index) as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(2); as->passInt32AsArg(index, 1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::pushBlockContext, CallResultDestination::Ignore); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushBlockContext, CallResultDestination::Ignore); } void BaselineJIT::generate_CloneBlockContext() { as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(1); - as->passJSSlotAsArg(CallData::Context, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::cloneBlockContext, CallResultDestination::Ignore); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(CloneBlockContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PushScriptContext(int index) { as->saveAccumulatorInFrame(); - as->prepareCallWithArgCount(3); - as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::pushScriptContext, CallResultDestination::Ignore); + as->prepareCallWithArgCount(2); + as->passInt32AsArg(index, 1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushScriptContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PopScriptContext() { as->saveAccumulatorInFrame(); - as->prepareCallWithArgCount(2); - as->passEngineAsArg(1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::popScriptContext, CallResultDestination::Ignore); + as->prepareCallWithArgCount(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PopScriptContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PopContext() { as->popContext(); } @@ -630,8 +601,7 @@ void BaselineJIT::generate_GetIterator(int iterator) as->passInt32AsArg(iterator, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_getIterator, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetIterator, CallResultDestination::InAccumulator); } void BaselineJIT::generate_IteratorNext(int value, int done) @@ -641,9 +611,8 @@ void BaselineJIT::generate_IteratorNext(int value, int done) as->passJSSlotAsArg(value, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorNext, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorNext, CallResultDestination::InAccumulator); as->storeReg(done); - as->checkException(); } void BaselineJIT::generate_IteratorNextForYieldStar(int iterator, int object) @@ -654,8 +623,7 @@ void BaselineJIT::generate_IteratorNextForYieldStar(int iterator, int object) as->passJSSlotAsArg(iterator, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorNextForYieldStar, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorNextForYieldStar, CallResultDestination::InAccumulator); } void BaselineJIT::generate_IteratorClose(int done) @@ -665,8 +633,7 @@ void BaselineJIT::generate_IteratorClose(int done) as->passJSSlotAsArg(done, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorClose, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorClose, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DestructureRestElement() @@ -675,29 +642,28 @@ void BaselineJIT::generate_DestructureRestElement() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_destructureRestElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(DestructureRestElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeleteProperty(int base, int index) { STORE_IP(); - as->prepareCallWithArgCount(3); - as->passJSSlotAsArg(index, 2); - as->passJSSlotAsArg(base, 1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::deleteProperty, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(4); + as->passJSSlotAsArg(index, 3); + as->passJSSlotAsArg(base, 2); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeleteProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeleteName(int name) { STORE_IP(); - as->prepareCallWithArgCount(2); - as->passInt32AsArg(name, 1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::deleteName, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(3); + as->passInt32AsArg(name, 2); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeleteName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TypeofName(int name) @@ -705,7 +671,7 @@ void BaselineJIT::generate_TypeofName(int name) as->prepareCallWithArgCount(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_typeofName, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(TypeofName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TypeofValue() @@ -714,7 +680,7 @@ void BaselineJIT::generate_TypeofValue() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_typeofValue, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(TypeofValue, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeclareVar(int varName, int isDeletable) @@ -723,7 +689,7 @@ void BaselineJIT::generate_DeclareVar(int varName, int isDeletable) as->passInt32AsArg(varName, 2); as->passInt32AsArg(isDeletable, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_declareVar, CallResultDestination::Ignore); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeclareVar, CallResultDestination::Ignore); } void BaselineJIT::generate_DefineArray(int argc, int args) @@ -732,7 +698,7 @@ void BaselineJIT::generate_DefineArray(int argc, int args) as->passInt32AsArg(argc, 2); as->passJSSlotAsArg(args, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_arrayLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(ArrayLiteral, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DefineObjectLiteral(int internalClassId, int argc, int args) @@ -742,7 +708,7 @@ void BaselineJIT::generate_DefineObjectLiteral(int internalClassId, int argc, in as->passJSSlotAsArg(args, 2); as->passInt32AsArg(internalClassId, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_objectLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(ObjectLiteral, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CreateClass(int classIndex, int heritage, int computedNames) @@ -752,14 +718,14 @@ void BaselineJIT::generate_CreateClass(int classIndex, int heritage, int compute as->passJSSlotAsArg(heritage, 2); as->passInt32AsArg(classIndex, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createClass, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateClass, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CreateMappedArgumentsObject() { as->prepareCallWithArgCount(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createMappedArgumentsObject, + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateMappedArgumentsObject, CallResultDestination::InAccumulator); } @@ -767,7 +733,7 @@ void BaselineJIT::generate_CreateUnmappedArgumentsObject() { as->prepareCallWithArgCount(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createUnmappedArgumentsObject, + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateUnmappedArgumentsObject, CallResultDestination::InAccumulator); } @@ -776,7 +742,7 @@ void BaselineJIT::generate_CreateRestParameter(int argIndex) as->prepareCallWithArgCount(2); as->passInt32AsArg(argIndex, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createRestParameter, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateRestParameter, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ConvertThisToObject() @@ -784,8 +750,8 @@ void BaselineJIT::generate_ConvertThisToObject() as->prepareCallWithArgCount(2); as->passJSSlotAsArg(CallData::This, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::convertThisToObject, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ConvertThisToObject, CallResultDestination::InAccumulator); + as->storeReg(CallData::This); } void BaselineJIT::generate_LoadSuperConstructor() @@ -793,8 +759,7 @@ void BaselineJIT::generate_LoadSuperConstructor() as->prepareCallWithArgCount(2); as->passJSSlotAsArg(CallData::Function, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadSuperConstructor, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadSuperConstructor, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ToObject() @@ -803,8 +768,7 @@ void BaselineJIT::generate_ToObject() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::toObject, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ToObject, CallResultDestination::InAccumulator); } @@ -853,8 +817,7 @@ void BaselineJIT::generate_CmpIn(int lhs) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(lhs, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_in, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(In, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CmpInstanceOf(int lhs) @@ -864,12 +827,11 @@ void BaselineJIT::generate_CmpInstanceOf(int lhs) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(lhs, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_instanceof, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Instanceof, CallResultDestination::InAccumulator); } void BaselineJIT::generate_UNot() { as->unot(); } -void BaselineJIT::generate_UPlus() { as->toNumber(); } +void BaselineJIT::generate_UPlus(int /*traceSlot*/) { as->toNumber(); } void BaselineJIT::generate_UMinus(int /*traceSlot*/) { as->uminus(); } void BaselineJIT::generate_UCompl() { as->ucompl(); } void BaselineJIT::generate_Increment(int /*traceSlot*/) { as->inc(); } @@ -896,8 +858,7 @@ void BaselineJIT::generate_Exp(int lhs) { as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passJSSlotAsArg(lhs, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::exp, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Exp, CallResultDestination::InAccumulator); } void BaselineJIT::generate_Mul(int lhs, int /*traceSlot*/) { as->mul(lhs); } void BaselineJIT::generate_Div(int lhs) { as->div(lhs); } @@ -929,8 +890,7 @@ void BaselineJIT::generate_ThrowOnNullOrUndefined() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::throwOnNullOrUndefined, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ThrowOnNullOrUndefined, CallResultDestination::Ignore); } void BaselineJIT::generate_GetTemplateObject(int index) @@ -939,14 +899,14 @@ void BaselineJIT::generate_GetTemplateObject(int index) as->prepareCallWithArgCount(2); as->passInt32AsArg(index, 1); as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(RuntimeHelpers::getTemplateObject, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetTemplateObject, CallResultDestination::InAccumulator); } -void BaselineJIT::startInstruction(Instr::Type /*instr*/) +ByteCodeHandler::Verdict BaselineJIT::startInstruction(Instr::Type /*instr*/) { if (labels.contains(currentInstructionOffset())) as->addLabel(currentInstructionOffset()); + return ProcessInstruction; } void BaselineJIT::endInstruction(Instr::Type instr) diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 10c89bc74b..37ab37eac2 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -179,7 +179,7 @@ public: void generate_CmpIn(int lhs) override; void generate_CmpInstanceOf(int lhs) override; void generate_UNot() override; - void generate_UPlus() override; + void generate_UPlus(int) override; void generate_UMinus(int traceSlot) override; void generate_UCompl() override; void generate_Increment(int traceSlot) override; @@ -206,7 +206,7 @@ public: void generate_ThrowOnNullOrUndefined() override; void generate_GetTemplateObject(int index) override; - void startInstruction(Moth::Instr::Type instr) override; + Verdict startInstruction(Moth::Instr::Type instr) override; void endInstruction(Moth::Instr::Type instr) override; private: diff --git a/src/qml/jit/qv4blockscheduler.cpp b/src/qml/jit/qv4blockscheduler.cpp new file mode 100644 index 0000000000..3e2bfe15c5 --- /dev/null +++ b/src/qml/jit/qv4blockscheduler.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4blockscheduler_p.h" +#include "qv4domtree_p.h" +#include "qv4loopinfo_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcBlockScheduler, "qt.v4.ir.blockscheduler") + +bool BlockScheduler::checkCandidate(MIBlock *candidate) +{ + Q_ASSERT(loopInfo.loopHeaderFor(candidate) == currentGroup.group); + + for (MIBlock *pred : candidate->inEdges()) { + if (pred->isDeoptBlock()) + continue; + + if (emitted.alreadyProcessed(pred)) + continue; + + if (dominatorTree.dominates(candidate->index(), pred->index())) { + // this is a loop, where there in + // -> candidate edge is the jump back to the top of the loop. + continue; + } + + if (pred == candidate) + // this is a very tight loop, e.g.: + // L1: ... + // goto L1 + // This can happen when, for example, the basic-block merging gets rid of the empty + // body block. In this case, we can safely schedule this block (if all other + // incoming edges are either loop-back edges, or have been scheduled already). + continue; + + return false; // an incoming edge that is not yet emitted, and is not a back-edge + } + + if (loopInfo.isLoopHeader(candidate)) { + // postpone everything, and schedule the loop first. + postponedGroups.push(currentGroup); + currentGroup = WorkForGroup(candidate); + } + + return true; +} + +MIBlock *BlockScheduler::pickNext() +{ + while (true) { + while (currentGroup.postponed.isEmpty()) { + if (postponedGroups.isEmpty()) + return nullptr; + if (currentGroup.group) // record the first and the last node of a group + loopsStartEnd[currentGroup.group] = sequence.back(); + currentGroup = postponedGroups.pop(); + } + + MIBlock *next = currentGroup.postponed.pop(); + if (checkCandidate(next)) + return next; + } + + Q_UNREACHABLE(); + return nullptr; +} + +void BlockScheduler::emitBlock(MIBlock *bb) +{ + if (emitted.alreadyProcessed(bb)) + return; + + sequence.push_back(bb); + emitted.markAsProcessed(bb); +} + +void BlockScheduler::schedule(MIBlock *functionEntryPoint) +{ + MIBlock *next = functionEntryPoint; + + while (next) { + emitBlock(next); + // postpone all outgoing edges, if they were not already processed + QVarLengthArray<MIBlock *, 32> nonExceptionEdges; + // first postpone all exception edges, so they will be processed last + for (int i = next->outEdges().size(); i != 0; ) { + --i; + MIBlock *out = next->outEdges().at(i); + if (emitted.alreadyProcessed(out)) + continue; + if (out == nullptr) + continue; + if (out->instructions().front().opcode() == Meta::OnException) + postpone(out); + else + nonExceptionEdges.append(out); + } + for (MIBlock *edge : nonExceptionEdges) + postpone(edge); + next = pickNext(); + } + + // finally schedule all de-optimization blocks at the end + for (auto bb : dominatorTree.function()->blocks()) { + if (bb->isDeoptBlock()) + emitBlock(bb); + } +} + +void BlockScheduler::postpone(MIBlock *bb) +{ + if (currentGroup.group == loopInfo.loopHeaderFor(bb)) { + currentGroup.postponed.append(bb); + return; + } + + for (int i = postponedGroups.size(); i != 0; ) { + --i; + WorkForGroup &g = postponedGroups[i]; + if (g.group == loopInfo.loopHeaderFor(bb)) { + g.postponed.append(bb); + return; + } + } + + Q_UNREACHABLE(); +} + +void BlockScheduler::dump() const +{ + if (!lcBlockScheduler().isDebugEnabled()) + return; + + QString s = QStringLiteral("Scheduled blocks:\n"); + for (auto *bb : sequence) { + s += QLatin1String(" L") + QString::number(bb->index()); + MIBlock *loopEnd = loopsStartEnd[bb]; + if (loopEnd) + s += QLatin1String(", loop start, ends at L") + QString::number(loopEnd->index()); + s += QLatin1Char('\n'); + } + qCDebug(lcBlockScheduler).noquote().nospace() << s; +} + +BlockScheduler::BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo) + : dominatorTree(dominatorTree) + , loopInfo(loopInfo) + , sequence(0) + , emitted(dominatorTree.function()) +{ + schedule(dominatorTree.function()->blocks().front()); + + dump(); + + if (dominatorTree.function()->blockCount() != sequence.size()) { + qFatal("The block scheduler did not schedule all blocks. This is most likely due to" + "a non-natural loop."); + // Usually caused by having an execution path that manages to skip over unwind handler + // reset: any exception happening after will jump back to the unwind handler, and thereby + // creating a loop that can be entered in 2 different ways. + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4blockscheduler_p.h b/src/qml/jit/qv4blockscheduler_p.h new file mode 100644 index 0000000000..1289fda9f0 --- /dev/null +++ b/src/qml/jit/qv4blockscheduler_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4BLOCKSCHEDULER_P_H +#define QV4BLOCKSCHEDULER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qstack.h> + +#include "qv4mi_p.h" +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree; +class LoopInfo; + +// High-level algorithm: +// 0. start with the first node (the start node) of a function +// 1. emit the node +// 2. add all outgoing edges that are not yet emitted to the postponed stack +// 3. When the postponed stack is empty, pop a stack from the loop stack. If that is empty too, +// we're done. +// 4. pop a node from the postponed stack, and check if it can be scheduled: +// a. if all incoming edges are scheduled, go to 4. +// b. if an incoming edge is unscheduled, but it's a back-edge (an edge in a loop that jumps +// back to the start of the loop), ignore it +// c. if there is any unscheduled edge that is not a back-edge, ignore this node, and go to 4. +// 5. if this node is the start of a loop, push the postponed stack on the loop stack. +// 6. go back to 1. +// +// The postponing action in step 2 will put the node into its containing group. The case where this +// is important is when a (labeled) continue or a (labeled) break statement occur in a loop: the +// outgoing edge points to a node that is not part of the current loop (and possibly not of the +// parent loop). +// +// Linear scan register allocation benefits greatly from short life-time intervals with few holes +// (see for example section 4 (Lifetime Analysis) of [Wimmer1]). This algorithm makes sure that the +// blocks of a group are scheduled together, with no non-loop blocks in between. This applies +// recursively for nested loops. It also schedules groups of if-then-else-endif blocks together for +// the same reason. +class BlockScheduler +{ + const DominatorTree &dominatorTree; + const LoopInfo &loopInfo; + + struct WorkForGroup + { + MIBlock *group; + QStack<MIBlock *> postponed; + + WorkForGroup(MIBlock *group = nullptr) : group(group) {} + }; + WorkForGroup currentGroup; + QStack<WorkForGroup> postponedGroups; + std::vector<MIBlock *> sequence; + + class ProcessedBlocks + { + BitVector processed; + + public: + ProcessedBlocks(MIFunction *function) + : processed(int(function->blockCount()), false) + {} + + bool alreadyProcessed(MIBlock *bb) const + { + Q_ASSERT(bb); + + return processed.at(bb->index()); + } + + void markAsProcessed(MIBlock *bb) + { + processed.setBit(bb->index()); + } + } emitted; + QHash<MIBlock *, MIBlock *> loopsStartEnd; + + bool checkCandidate(MIBlock *candidate); + MIBlock *pickNext(); + void emitBlock(MIBlock *bb); + void schedule(MIBlock *functionEntryPoint); + void postpone(MIBlock *bb); + void dump() const; + +public: + BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo); + + const std::vector<MIBlock *> &scheduledBlockSequence() const + { return sequence; } + + QHash<MIBlock *, MIBlock *> loopEndsByStartBlock() const + { return loopsStartEnd; } +}; + + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4BLOCKSCHEDULER_P_H diff --git a/src/qml/jit/qv4domtree.cpp b/src/qml/jit/qv4domtree.cpp new file mode 100644 index 0000000000..9484f4e2dc --- /dev/null +++ b/src/qml/jit/qv4domtree.cpp @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qbuffer.h> + +#include "qv4domtree_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcDomTree, "qt.v4.ir.domTree") +Q_LOGGING_CATEGORY(lcDomFrontier, "qt.v4.ir.domFrontier") + +DominatorTree::DominatorTree(MIFunction *f) + : m_function(f) + , m_data(new Data) +{ + calculateIDoms(); + m_data.reset(); +} + +void DominatorTree::dumpImmediateDominators() const +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QTextStream qout(&buf); + qout << "Immediate dominators for " << m_function->irFunction()->name() << ":" << endl; + for (MIBlock *to : m_function->blocks()) { + MIBlock::Index from = m_idom.at(to->index()); + if (from != MIBlock::InvalidIndex) + qout << " " << from; + else + qout << " (none)"; + qout << " dominates " << to->index() << endl; + } + qCDebug(lcDomTree, "%s", buf.data().constData()); +} + +void DominatorTree::setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator) +{ + if (m_idom.size() <= size_t(dominated)) + m_idom.resize(dominated + 1); + m_idom[dominated] = dominator; +} + +bool DominatorTree::dominates(MIBlock::Index dominator, MIBlock::Index dominated) const +{ + // dominator can be Invalid when the dominated block has no dominator (i.e. the start node) + Q_ASSERT(dominated != MIBlock::InvalidIndex); + + if (dominator == dominated) + return false; + + for (MIBlock::Index it = m_idom[dominated]; it != MIBlock::InvalidIndex; it = m_idom[it]) { + if (it == dominator) + return true; + } + + return false; +} + +// Calculate a depth-first iteration order on the nodes of the dominator tree. +// +// The order of the nodes in the vector is not the same as one where a recursive depth-first +// iteration is done on a tree. Rather, the nodes are (reverse) sorted on tree depth. +// So for the: +// 1 dominates 2 +// 2 dominates 3 +// 3 dominates 4 +// 2 dominates 5 +// the order will be: +// 4, 3, 5, 2, 1 +// or: +// 4, 5, 3, 2, 1 +// So the order of nodes on the same depth is undefined, but it will be after the nodes +// they dominate, and before the nodes that dominate them. +// +// The reason for this order is that a proper DFS pre-/post-order would require inverting +// the idom vector by either building a real tree datastructure or by searching the idoms +// for siblings and children. Both have a higher time complexity than sorting by depth. +std::vector<MIBlock *> DominatorTree::calculateDFNodeIterOrder() const +{ + std::vector<int> depths = calculateNodeDepths(); + std::vector<MIBlock *> order = m_function->blocks(); + std::sort(order.begin(), order.end(), [&depths](MIBlock *one, MIBlock *two) -> bool { + return depths.at(one->index()) > depths.at(two->index()); + }); + return order; +} + +// Algorithm: +// - for each node: +// - get the depth of a node. If it's unknown (-1): +// - get the depth of the immediate dominator. +// - if that's unknown too, calculate it by calling calculateNodeDepth +// - set the current node's depth to that of immediate dominator + 1 +std::vector<int> DominatorTree::calculateNodeDepths() const +{ + std::vector<int> nodeDepths(size_t(m_function->blockCount()), -1); + for (MIBlock *bb : m_function->blocks()) { + int &bbDepth = nodeDepths[bb->index()]; + if (bbDepth == -1) { + const int immDom = m_idom[bb->index()]; + if (immDom == -1) { + // no immediate dominator, so it's either the start block, or an unreachable block + bbDepth = 0; + } else { + int immDomDepth = nodeDepths[immDom]; + if (immDomDepth == -1) + immDomDepth = calculateNodeDepth(immDom, nodeDepths); + bbDepth = immDomDepth + 1; + } + } + } + return nodeDepths; +} + +// Algorithm: +// - search for the first dominator of a node that has a known depth. As all nodes are +// reachable from the start node, and that node's depth is 0, this is finite. +// - while doing that search, put all unknown nodes in the worklist +// - pop all nodes from the worklist, and set their depth to the previous' (== dominating) +// node's depth + 1 +// This way every node's depth is calculated once, and the complexity is O(n). +int DominatorTree::calculateNodeDepth(MIBlock::Index nodeIdx, std::vector<int> &nodeDepths) const +{ + std::vector<int> worklist; + worklist.reserve(8); + int depth = -1; + + do { + worklist.push_back(nodeIdx); + nodeIdx = m_idom[nodeIdx]; + depth = nodeDepths[nodeIdx]; + } while (depth == -1); + + for (auto it = worklist.rbegin(), eit = worklist.rend(); it != eit; ++it) + nodeDepths[*it] = ++depth; + + return depth; +} + +namespace { +struct DFSTodo { + MIBlock::Index node = MIBlock::InvalidIndex; + MIBlock::Index parent = MIBlock::InvalidIndex; + + DFSTodo() = default; + DFSTodo(MIBlock::Index node, MIBlock::Index parent) + : node(node) + , parent(parent) + {} +}; +} // anonymous namespace + +void DominatorTree::dfs(MIBlock::Index node) +{ + std::vector<DFSTodo> worklist; + worklist.reserve(m_data->vertex.capacity() / 2); + DFSTodo todo(node, MIBlock::InvalidIndex); + + while (true) { + MIBlock::Index n = todo.node; + + if (m_data->dfnum[n] == 0) { + m_data->dfnum[n] = m_data->size; + m_data->vertex[m_data->size] = n; + m_data->parent[n] = todo.parent; + ++m_data->size; + + MIBlock::OutEdges out = m_function->block(n)->outEdges(); + for (int i = out.size() - 1; i > 0; --i) + worklist.emplace_back(out[i]->index(), n); + + if (!out.isEmpty()) { + todo.node = out.first()->index(); + todo.parent = n; + continue; + } + } + + if (worklist.empty()) + break; + + todo = worklist.back(); + worklist.pop_back(); + } +} + +void DominatorTree::link(MIBlock::Index p, MIBlock::Index n) +{ + m_data->ancestor[n] = p; + m_data->best[n] = n; +} + +void DominatorTree::calculateIDoms() +{ + Q_ASSERT(m_function->block(0)->inEdges().count() == 0); + + const size_t bbCount = m_function->blockCount(); + m_data->vertex = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->parent = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->dfnum = std::vector<unsigned>(bbCount, 0); + m_data->semi = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->ancestor = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_idom = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->samedom = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->best = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + + QHash<MIBlock::Index, std::vector<MIBlock::Index>> bucket; + bucket.reserve(int(bbCount)); + + dfs(m_function->block(0)->index()); + + std::vector<MIBlock::Index> worklist; + worklist.reserve(m_data->vertex.capacity() / 2); + + for (int i = m_data->size - 1; i > 0; --i) { + MIBlock::Index n = m_data->vertex[i]; + MIBlock::Index p = m_data->parent[n]; + MIBlock::Index s = p; + + for (auto inEdge : m_function->block(n)->inEdges()) { + if (inEdge->isDeoptBlock()) + continue; + MIBlock::Index v = inEdge->index(); + MIBlock::Index ss = MIBlock::InvalidIndex; + if (m_data->dfnum[v] <= m_data->dfnum[n]) + ss = v; + else + ss = m_data->semi[ancestorWithLowestSemi(v, worklist)]; + if (m_data->dfnum[ss] < m_data->dfnum[s]) + s = ss; + } + m_data->semi[n] = s; + bucket[s].push_back(n); + link(p, n); + if (bucket.contains(p)) { + for (MIBlock::Index v : bucket[p]) { + MIBlock::Index y = ancestorWithLowestSemi(v, worklist); + MIBlock::Index semi_v = m_data->semi[v]; + if (m_data->semi[y] == semi_v) + m_idom[v] = semi_v; + else + m_data->samedom[v] = y; + } + bucket.remove(p); + } + } + + for (unsigned i = 1; i < m_data->size; ++i) { + MIBlock::Index n = m_data->vertex[i]; + Q_ASSERT(n != MIBlock::InvalidIndex); + Q_ASSERT(!bucket.contains(n)); + Q_ASSERT(m_data->ancestor[n] != MIBlock::InvalidIndex); + Q_ASSERT((m_data->semi[n] != MIBlock::InvalidIndex + && m_data->dfnum[m_data->ancestor[n]] <= m_data->dfnum[m_data->semi[n]]) + || m_data->semi[n] == n); + MIBlock::Index sdn = m_data->samedom[n]; + if (sdn != MIBlock::InvalidIndex) + m_idom[n] = m_idom[sdn]; + } + + if (lcDomTree().isDebugEnabled()) + dumpImmediateDominators(); + + m_data.reset(nullptr); +} + +MIBlock::Index DominatorTree::ancestorWithLowestSemi(MIBlock::Index v, + std::vector<MIBlock::Index> &worklist) +{ + worklist.clear(); + for (MIBlock::Index it = v; it != MIBlock::InvalidIndex; it = m_data->ancestor[it]) + worklist.push_back(it); + + if (worklist.size() < 2) + return m_data->best[v]; + + MIBlock::Index b = MIBlock::InvalidIndex; + MIBlock::Index last = worklist.back(); + Q_ASSERT(worklist.size() <= INT_MAX); + for (int it = static_cast<int>(worklist.size()) - 2; it >= 0; --it) { + MIBlock::Index bbIt = worklist[it]; + m_data->ancestor[bbIt] = last; + MIBlock::Index &best_it = m_data->best[bbIt]; + if (b != MIBlock::InvalidIndex + && m_data->dfnum[m_data->semi[b]] < m_data->dfnum[m_data->semi[best_it]]) { + best_it = b; + } else { + b = best_it; + } + } + return b; +} + +void DominatorFrontier::compute(const DominatorTree &domTree) +{ + struct NodeProgress { + std::vector<MIBlock::Index> children; + std::vector<MIBlock::Index> todo; + }; + + MIFunction *function = domTree.function(); + m_df.resize(function->blockCount()); + + // compute children of each node in the dominator tree + std::vector<std::vector<MIBlock::Index> > children; // BasicBlock index -> children + children.resize(function->blockCount()); + for (MIBlock *n : function->blocks()) { + const MIBlock::Index nodeIndex = n->index(); + Q_ASSERT(function->block(nodeIndex) == n); + const MIBlock::Index nodeDominator = domTree.immediateDominator(nodeIndex); + if (nodeDominator == MIBlock::InvalidIndex) + continue; // there is no dominator to add this node to as a child (e.g. the start node) + children[nodeDominator].push_back(nodeIndex); + } + + // Fill the worklist and initialize the node status for each basic-block + std::vector<NodeProgress> nodeStatus; + nodeStatus.resize(function->blockCount()); + std::vector<MIBlock::Index> worklist; + worklist.reserve(function->blockCount()); + for (MIBlock *bb : function->blocks()) { + MIBlock::Index nodeIndex = bb->index(); + worklist.push_back(nodeIndex); + NodeProgress &np = nodeStatus[nodeIndex]; + np.children = children[nodeIndex]; + np.todo = children[nodeIndex]; + } + + BitVector DF_done(int(function->blockCount()), false); + + while (!worklist.empty()) { + MIBlock::Index node = worklist.back(); + + if (DF_done.at(node)) { + worklist.pop_back(); + continue; + } + + NodeProgress &np = nodeStatus[node]; + auto it = np.todo.begin(); + while (it != np.todo.end()) { + if (DF_done.at(*it)) { + it = np.todo.erase(it); + } else { + worklist.push_back(*it); + break; + } + } + + if (np.todo.empty()) { + MIBlockSet &miBlockSet = m_df[node]; + miBlockSet.init(function); + for (MIBlock *y : function->block(node)->outEdges()) { + if (domTree.immediateDominator(y->index()) != node) + miBlockSet.insert(y); + } + for (MIBlock::Index child : np.children) { + const MIBlockSet &ws = m_df[child]; + for (auto w : ws) { + const MIBlock::Index wIndex = w->index(); + if (node == wIndex || !domTree.dominates(node, w->index())) + miBlockSet.insert(w); + } + } + DF_done.setBit(node); + worklist.pop_back(); + } + } + + if (lcDomFrontier().isDebugEnabled()) + dump(domTree.function()); +} + +void DominatorFrontier::dump(MIFunction *function) +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QTextStream qout(&buf); + qout << "Dominator Frontiers:" << endl; + for (MIBlock *n : function->blocks()) { + qout << "\tDF[" << n->index() << "]: {"; + const MIBlockSet &SList = m_df[n->index()]; + for (MIBlockSet::const_iterator i = SList.begin(), ei = SList.end(); i != ei; ++i) { + if (i != SList.begin()) + qout << ", "; + qout << (*i)->index(); + } + qout << "}" << endl; + } + qCDebug(lcDomFrontier, "%s", buf.data().constData()); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4domtree_p.h b/src/qml/jit/qv4domtree_p.h new file mode 100644 index 0000000000..703e17ab61 --- /dev/null +++ b/src/qml/jit/qv4domtree_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DOMTREE_P_H +#define QV4DOMTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" +#include "qv4miblockset_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree +{ + Q_DISABLE_COPY_MOVE(DominatorTree) + +public: + DominatorTree(MIFunction *f); + ~DominatorTree() = default; + + void dumpImmediateDominators() const; + MIFunction *function() const + { return m_function; } + + void setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator); + + MIBlock::Index immediateDominator(MIBlock::Index blockIndex) const + { return m_idom[blockIndex]; } + + bool dominates(MIBlock::Index dominator, MIBlock::Index dominated) const; + + bool insideSameDominatorChain(MIBlock::Index one, MIBlock::Index other) const + { return one == other || dominates(one, other) || dominates(other, one); } + + std::vector<MIBlock *> calculateDFNodeIterOrder() const; + + std::vector<int> calculateNodeDepths() const; + +private: // functions + int calculateNodeDepth(MIBlock::Index nodeIdx, std::vector<int> &nodeDepths) const; + void link(MIBlock::Index p, MIBlock::Index n); + void calculateIDoms(); + void dfs(MIBlock::Index node); + MIBlock::Index ancestorWithLowestSemi(MIBlock::Index v, std::vector<MIBlock::Index> &worklist); + +private: // data + struct Data { + std::vector<unsigned> dfnum; // MIBlock index -> dfnum + std::vector<MIBlock::Index> vertex; + std::vector<MIBlock::Index> parent; // MIBlock index -> parent MIBlock index + std::vector<MIBlock::Index> ancestor; // MIBlock index -> ancestor MIBlock index + std::vector<MIBlock::Index> best; // MIBlock index -> best MIBlock index + std::vector<MIBlock::Index> semi; // MIBlock index -> semi dominator MIBlock index + std::vector<MIBlock::Index> samedom; // MIBlock index -> same dominator MIBlock index + unsigned size = 0; + }; + + MIFunction *m_function; + QScopedPointer<Data> m_data; + std::vector<MIBlock::Index> m_idom; // MIBlock index -> immediate dominator MIBlock index +}; + +class DominatorFrontier +{ +public: + DominatorFrontier(const DominatorTree &domTree) + { compute(domTree); } + + const MIBlockSet &operator[](MIBlock *n) const + { return m_df[n->index()]; } + +private: // functions + void compute(const DominatorTree &domTree); + void dump(MIFunction *function); + +private: // data + std::vector<MIBlockSet> m_df; // MIBlock index -> dominator frontier +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4DOMTREE_P_H diff --git a/src/qml/jit/qv4graph.cpp b/src/qml/jit/qv4graph.cpp new file mode 100644 index 0000000000..4025ceb993 --- /dev/null +++ b/src/qml/jit/qv4graph.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4graph_p.h" +#include "qv4operation_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Graph *Graph::create(Function *function) +{ + auto storage = function->pool()->allocate(sizeof(Graph)); + auto g = new (storage) Graph(function); + g->m_undefinedNode = g->createNode(g->opBuilder()->get<Meta::Undefined>()); + g->m_emptyNode = g->createNode(g->opBuilder()->get<Meta::Empty>()); + g->m_nullNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::nullValue())); + g->m_trueNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(true))); + g->m_falseNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(false))); + return g; +} + +Graph::MemoryPool *Graph::pool() const +{ + return m_function->pool(); +} + +Node *Graph::createNode(const Operation *op, Node *const operands[], size_t opCount, + bool incomplete) +{ + return Node::create(pool(), m_nextNodeId++, op, opCount, operands, incomplete); +} + +Node *Graph::createConstantBoolNode(bool value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromBoolean(value))); +} + +Node *Graph::createConstantIntNode(int value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromInt32(value))); +} + +Graph::Graph(Function *function) + : m_function(function) + , m_opBuilder(OperationBuilder::create(pool())) +{} + +Node *Graph::createConstantHeapNode(Heap::Base *heap) +{ + return createNode(opBuilder()->getConstant(Primitive::fromHeapObject(heap))); +} + +void Graph::addEndInput(Node *n) +{ + if (m_endNode) { + auto newEnd = m_opBuilder->getEnd(m_endNode->operation()->controlInputCount() + 1); + m_endNode->setOperation(newEnd); + m_endNode->addInput(m_function->pool(), n); + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graph_p.h b/src/qml/jit/qv4graph_p.h new file mode 100644 index 0000000000..4706399c94 --- /dev/null +++ b/src/qml/jit/qv4graph_p.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GRAPH_P_H +#define QV4GRAPH_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4node_p.h> + +#include <array> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Function; +class Operation; +class OperationBuilder; + +class Graph final +{ + Q_DISABLE_COPY_MOVE(Graph) + +public: + using MemoryPool = QQmlJS::MemoryPool; + +public: + static Graph *create(Function *function); + ~Graph() = delete; + + MemoryPool *pool() const; + OperationBuilder *opBuilder() const + { return m_opBuilder; } + + Node *createNode(const Operation *op, Node * const operands[] = nullptr, size_t opCount = 0, + bool incomplete = false); + template <typename... Nodes> + Node *createNode(Operation *op, Nodes*... nodes) { + std::array<Node *, sizeof...(nodes)> nodesArray {{ nodes... }}; + return createNode(op, nodesArray.data(), nodesArray.size()); + } + Node *createConstantBoolNode(bool value); + Node *createConstantIntNode(int value); + Node *createConstantHeapNode(Heap::Base *heap); + + Node *undefinedNode() const { return m_undefinedNode; } + Node *emptyNode() const { return m_emptyNode; } + Node *nullNode() const { return m_nullNode; } + Node *trueConstant() const { return m_trueNode; } + Node *falseConstant() const { return m_falseNode; } + + Node *startNode() const { return m_startNode; } + Node *engineNode() const { return m_engineNode; } + Node *functionNode() const { return m_functionNode; } + Node *cppFrameNode() const { return m_cppFrameNode; } + Node *endNode() const { return m_endNode; } + Node *initialFrameState() const { return m_initialFrameState; } + void setStartNode(Node *n) { m_startNode = n; } + void setEngineNode(Node *n) { m_engineNode = n; } + void setFunctionNode(Node *n) { m_functionNode = n; } + void setCppFrameNode(Node *n) { m_cppFrameNode = n; } + void setEndNode(Node *n) { m_endNode = n; } + void setInitialFrameState(Node *n) { m_initialFrameState = n; } + + unsigned nodeCount() const + { return unsigned(m_nextNodeId); } + + void addEndInput(Node *n); + +private: // types and methods + Graph(Function *function); + +private: // fields + Function *m_function; + OperationBuilder *m_opBuilder; + Node::Id m_nextNodeId = 0; + Node *m_undefinedNode = nullptr; + Node *m_emptyNode = nullptr; + Node *m_nullNode = nullptr; + Node *m_trueNode = nullptr; + Node *m_falseNode = nullptr; + Node *m_startNode = nullptr; + Node *m_engineNode = nullptr; + Node *m_functionNode = nullptr; + Node *m_cppFrameNode = nullptr; + Node *m_endNode = nullptr; + Node *m_initialFrameState = nullptr; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPH_P_H diff --git a/src/qml/jit/qv4graphbuilder.cpp b/src/qml/jit/qv4graphbuilder.cpp new file mode 100644 index 0000000000..2c073701ee --- /dev/null +++ b/src/qml/jit/qv4graphbuilder.cpp @@ -0,0 +1,1683 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4graphbuilder_p.h" +#include "qv4function_p.h" +#include "qv4lookup_p.h" +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcIRGraphBuilder, "qt.v4.ir.graphbuilder") + +using MemoryPool = QQmlJS::MemoryPool; + +namespace { +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; + +template <typename Array> +inline size_t arraySize(const Array &array) +{ + Q_UNUSED(array); // for MSVC + return sizeof(ArraySizeHelper(array)); +} +} // anonymous namespace + +class GraphBuilder::InterpreterEnvironment +{ +public: + struct FrameState: public QQmlJS::FixedPoolArray<Node *> + { + FrameState(MemoryPool *pool, int totalSlotCount) + : FixedPoolArray(pool, totalSlotCount) + {} + + Node *&unwindHandlerOffset() + { return at(size() - 1); } + + static FrameState *create(MemoryPool *pool, int jsSlotCount) + { + auto totalSlotCount = jsSlotCount; + auto fs = pool->New<FrameState>(pool, totalSlotCount); + return fs; + } + + static FrameState *clone(MemoryPool *pool, FrameState *other) + { + FrameState *fs = create(pool, other->size()); + + for (int i = 0, ei = other->size(); i != ei; ++i) + fs->at(i) = other->at(i); + + return fs; + } + }; + +public: + InterpreterEnvironment(GraphBuilder *graphBuilder, Node *controlDependency) + : m_graphBuilder(graphBuilder) + , m_effectDependency(controlDependency) + , m_controlDependency(controlDependency) + , m_currentFrame(nullptr) + {} + + void createEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + // 1 extra slot for the unwindHandlerOffset + m_currentFrame = FrameState::create(graph()->pool(), int(nRegisters + 1)); + } + + void setupStartEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nFormals = v4Function->compiledFunction->nFormals; + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + createEnvironment(); + + Node *startNode = graph()->startNode(); + auto opB = opBuilder(); + auto create = [&](int index, const char *name) { + m_currentFrame->at(index) = graph()->createNode( + opB->getParam(index, f->addString(QLatin1String(name))), startNode); + }; + create(0, "%function"); + create(1, "%context"); + create(2, "%acc"); + create(3, "%this"); + create(4, "%newTarget"); + create(5, "%argc"); + const quint32_le *formalNameIdx = v4Function->compiledFunction->formalsTable(); + for (size_t i = 0; i < nFormals; ++i, ++formalNameIdx) { + const int slot = int(CallData::HeaderSize() + i); + Q_ASSERT(*formalNameIdx <= quint32(std::numeric_limits<int>::max())); + auto op = opB->getParam( + slot, + f->addString(v4Function->compilationUnit->stringAt(int(*formalNameIdx)))); + Node *argNode = graph()->createNode(op, startNode); + m_currentFrame->at(slot) = argNode; + } + Node *undefinedNode = graph()->undefinedNode(); + Node *emptyNode = graph()->emptyNode(); + const auto firstDeadZoneRegister + = v4Function->compiledFunction->firstTemporalDeadZoneRegister; + const auto registerDeadZoneSize + = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone; + for (size_t i = CallData::HeaderSize() + nFormals; i < nRegisters; ++i) { + const bool isDead = i >= firstDeadZoneRegister + && i < size_t(firstDeadZoneRegister + registerDeadZoneSize); + m_currentFrame->at(int(i)) = isDead ? emptyNode : undefinedNode; + } + setUnwindHandlerOffset(0); + } + + Function *function() const { return m_graphBuilder->function(); } + Graph *graph() const { return function()->graph(); } + OperationBuilder *opBuilder() const { return graph()->opBuilder(); } + GraphBuilder *graphBuilder() const { return m_graphBuilder; } + + Node *bindAcc(Node *node) + { + bindNodeToSlot(node, CallData::Accumulator); + return node; + } + + Node *accumulator() const + { return slot(CallData::Accumulator); } + + Node *bindNodeToSlot(Node *node, int slot) + { + m_currentFrame->at(size_t(slot)) = node; + return node; + } + + Node *slot(int slot) const + { return m_currentFrame->at(slot); } + + int slotCount() const + { return m_currentFrame->size(); } + + Node *effectDependency() const + { return m_effectDependency; } + + void setEffectDependency(Node *newNode) + { m_effectDependency = newNode; } + + Node *controlDependency() const + { return m_controlDependency; } + + void setControlDependency(Node *newNode) + { m_controlDependency = newNode; } + + Node *createFrameState() + { + return graph()->createNode(graphBuilder()->opBuilder()->getFrameState(slotCount()), + m_currentFrame->begin(), slotCount()); + } + + Node *merge(InterpreterEnvironment *other); + + InterpreterEnvironment *copy() const + { + auto *newEnv = graph()->pool()->New<InterpreterEnvironment>(graphBuilder(), + controlDependency()); + newEnv->setEffectDependency(effectDependency()); + newEnv->m_currentFrame = FrameState::clone(graph()->pool(), m_currentFrame); + return newEnv; + } + + int unwindHandlerOffset() const + { + auto uhOp = m_currentFrame->unwindHandlerOffset()->operation(); + Q_ASSERT(uhOp->kind() == Meta::Constant); + return ConstantPayload::get(*uhOp)->value().int_32(); + } + + void setUnwindHandlerOffset(int newOffset) + { m_currentFrame->unwindHandlerOffset() = graphBuilder()->createConstant(newOffset); } + + FrameState *frameState() const + { return m_currentFrame; } + +private: + GraphBuilder *m_graphBuilder; + Node *m_effectDependency; + Node *m_controlDependency; + FrameState *m_currentFrame; +}; + +namespace { +class InterpreterSubEnvironment final +{ + Q_DISABLE_COPY_MOVE(InterpreterSubEnvironment) + +public: + explicit InterpreterSubEnvironment(GraphBuilder *builder) + : m_builder(builder) + , m_parent(builder->env()->copy()) + {} + + ~InterpreterSubEnvironment() + { m_builder->setEnv(m_parent); } + +private: + GraphBuilder *m_builder; + GraphBuilder::InterpreterEnvironment *m_parent; +}; +} // anonymous namespace + +Node *GraphBuilder::InterpreterEnvironment::merge(InterpreterEnvironment *other) +{ + Q_ASSERT(m_currentFrame->size() == other->m_currentFrame->size()); + + auto gb = graphBuilder(); + Node *mergedControl = gb->mergeControl(controlDependency(), other->controlDependency()); + setControlDependency(mergedControl); + setEffectDependency(gb->mergeEffect(effectDependency(), other->effectDependency(), mergedControl)); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = m_currentFrame->size() - 1; i != ei; ++i) { + //### use lifeness info to trim this! + m_currentFrame->at(i) = gb->mergeValue(m_currentFrame->at(i), + other->m_currentFrame->at(i), + mergedControl); + } + Q_ASSERT(unwindHandlerOffset() >= 0); // specifically: don't crash + return mergedControl; +} + +void GraphBuilder::buildGraph(IR::Function *function) +{ + const char *code = function->v4Function()->codeData; + uint len = function->v4Function()->compiledFunction->codeSize; + + GraphBuilder builder(function); + builder.startGraph(); + + InterpreterEnvironment initial(&builder, function->graph()->startNode()); + initial.setupStartEnvironment(); + builder.setEnv(&initial); + builder.graph()->setInitialFrameState(initial.createFrameState()); + builder.decode(code, len); + builder.endGraph(); +}; + +GraphBuilder::GraphBuilder(IR::Function *function) + : m_func(function) + , m_graph(function->graph()) + , m_currentEnv(nullptr) +{ + for (unsigned i = 0, ei = m_func->v4Function()->compiledFunction->nLabelInfos; i != ei; ++i) { + unsigned label = m_func->v4Function()->compiledFunction->labelInfoTable()[i]; + m_labelInfos.emplace_back(label); + if (lcIRGraphBuilder().isDebugEnabled()) { + const LabelInfo &li = m_labelInfos.back(); + qCDebug(lcIRGraphBuilder) << "Loop start at" << li.labelOffset; + } + } +} + +void GraphBuilder::startGraph() +{ + size_t nValuesOut = 1 + CallData::HeaderSize() + + m_func->v4Function()->compiledFunction->nFormals; + Node *start = m_graph->createNode(opBuilder()->getStart(uint16_t(nValuesOut)), nullptr, 0); + m_func->nodeInfo(start)->setBytecodeOffsets(0, 0); + m_graph->setStartNode(start); + m_graph->setEngineNode(m_graph->createNode(opBuilder()->get<Meta::Engine>(), &start, 1)); + auto frame = m_graph->createNode(opBuilder()->get<Meta::CppFrame>(), &start, 1); + m_graph->setCppFrameNode(frame); + m_graph->setFunctionNode(m_graph->createNode(opBuilder()->get<Meta::Function>(), + &frame, 1)); +} + +void GraphBuilder::endGraph() +{ + const auto inputCount = uint16_t(m_exitControls.size()); + Node **inputs = &m_exitControls.front(); + Q_ASSERT(m_graph->endNode() == nullptr); + m_graph->setEndNode(m_graph->createNode(opBuilder()->getEnd(inputCount), inputs, inputCount)); +} + +Node *GraphBuilder::bindAcc(Node *n) +{ + return env()->bindAcc(n); +} + +/* IMPORTANT!!! + * + * This might change the success environment, so don't call: + * env()->bindAcc(createNode(...)) + * because the binding should only happen on success, but the call to env() will get the + * environment from *before* the new success environment was created. Instead, do: + * bindAcc(createNode(....)) + */ +Node *GraphBuilder::createAndLinkNode(Operation *op, Node *operands[], size_t opCount, + bool incomplete) +{ + Q_ASSERT(op->effectInputCount() < 2); + Q_ASSERT(op->controlInputCount() < 2); + + QVarLengthArray<Node *, 32> inputs(static_cast<int>(opCount)); + std::copy_n(operands, opCount, inputs.data()); + + if (op->effectInputCount() == 1) + inputs.append(env()->effectDependency()); + if (op->controlInputCount() == 1) + inputs.append(env()->controlDependency()); + if (op->hasFrameStateInput()) + inputs.append(env()->createFrameState()); + + Node *node = m_graph->createNode(op, inputs.data(), inputs.size(), incomplete); + + if (op->needsBytecodeOffsets()) { + m_func->nodeInfo(node)->setBytecodeOffsets(currentInstructionOffset(), + nextInstructionOffset()); + } + + if (op->effectOutputCount() > 0) + env()->setEffectDependency(node); + if (op->controlOutputCount() > 0) + env()->setControlDependency(node); + + if (op->canThrow() && env()->unwindHandlerOffset()) { + InterpreterSubEnvironment successEnv(this); + Node *control = env()->controlDependency(); + control = m_graph->createNode(opBuilder()->get<Meta::OnException>(), &control, 1); + env()->setControlDependency(control); + auto unwindHandlerOffset = env()->unwindHandlerOffset(); + mergeIntoSuccessor(unwindHandlerOffset); + } + + return node; +} + +Node *GraphBuilder::createNode(Operation *op, bool incomplete) +{ + return createAndLinkNode(op, nullptr, 0, incomplete); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1) +{ + Node *buf[] = { n1 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2) +{ + Node *buf[] = { n1, n2 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3) +{ + Node *buf[] = { n1, n2, n3 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4) +{ + Node *buf[] = { n1, n2, n3, n4 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createRegion(unsigned nControlInputs) +{ + return createNode(opBuilder()->getRegion(nControlInputs), true); +} + +Node *GraphBuilder::createIfTrue() +{ + return createNode(opBuilder()->get<Meta::IfTrue>()); +} + +Node *GraphBuilder::createIfFalse() +{ + return createNode(opBuilder()->get<Meta::IfFalse>()); +} + +Node *GraphBuilder::createConstant(int v) +{ + return m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(v))); +} + +Node *GraphBuilder::createPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createEffectPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getEffectPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createHandleUnwind(int offset) +{ + return createNode(opBuilder()->getHandleUnwind(offset)); +} + +Node *GraphBuilder::mergeControl(Node *c1, Node *c2) +{ + if (c1->operation()->kind() == Meta::Region) { + const unsigned nInputs = c1->operation()->controlInputCount() + 1; + c1->addInput(m_graph->pool(), c2); + c1->setOperation(opBuilder()->getRegion(nInputs)); + return c1; + } + auto op = opBuilder()->getRegion(2); + Node *inputs[] = { c1, c2 }; + return m_graph->createNode(op, inputs, 2); +} + +Node *GraphBuilder::mergeEffect(Node *e1, Node *e2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (e1->operation()->kind() == Meta::EffectPhi && e1->controlInput() == control) { + e1->insertInput(m_graph->pool(), nInputs - 1, e2); + e1->setOperation(opBuilder()->getEffectPhi(nInputs)); + return e1; + } + + if (e1 != e2) { + Node *phi = createEffectPhi(nInputs, e1, control); + phi->replaceInput(nInputs - 1, e2); + return phi; + } + + return e1; +} + +Node *GraphBuilder::mergeValue(Node *v1, Node *v2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (v1->operation()->kind() == Meta::Phi && v1->controlInput() == control) { + v1->insertInput(m_graph->pool(), nInputs - 1, v2); + v1->setOperation(opBuilder()->getPhi(nInputs)); + return v1; + } + + if (v1 != v2) { + Node *phi = createPhi(nInputs, v1, control); + phi->replaceInput(nInputs - 1, v2); + return phi; + } + + return v1; +} + +Node *GraphBuilder::createToBoolean(Node *input) +{ + return createNode(opBuilder()->get<Meta::ToBoolean>(), input); +} + +void GraphBuilder::populate(VarArgNodes &args, int argc, int argv) +{ + for (int i = 0; i < argc; ++i) + args.append(env()->slot(argv + i)); + Q_ASSERT(argc >= 0 && argc <= std::numeric_limits<uint16_t>::max()); +} + +void GraphBuilder::queueFunctionExit(Node *exitNode) +{ + m_exitControls.push_back(exitNode); + setEnv(nullptr); +} + +Node *GraphBuilder::mergeIntoSuccessor(int offset) +{ + InterpreterEnvironment *&successorEnvironment = m_envForOffset[offset]; + + Node *region = nullptr; + if (successorEnvironment == nullptr) { + region = createRegion(1); + successorEnvironment = env(); + } else { + // Merge any values which are live coming into the successor. + region = successorEnvironment->merge(env()); + } + setEnv(nullptr); + return region; +} + +const GraphBuilder::LabelInfo *GraphBuilder::labelInfoAt(unsigned offset) const +{ + for (const LabelInfo &li : m_labelInfos) { + if (li.labelOffset == offset) + return &li; + } + return nullptr; +} + +const GraphBuilder::LabelInfo *GraphBuilder::isLoopStart(unsigned offset) const +{ + if (auto li = labelInfoAt(offset)) { + //### in the future, check if this is a loop start, or some other label + return li; + } + + return nullptr; +} + +void GraphBuilder::handleLoopStart(const LabelInfo &labelInfo) +{ + Q_ASSERT(env() != nullptr); + + // We unconditionally insert a region node with phi nodes here. Now there might already be + // such a node, (e.g. the region after an if-then-else), but for simplicity we ignore that. + // A subsequent pass will fold/remove chains of Region nodes. + //### FIXME: add a DCE pass + + const auto offset = int(labelInfo.labelOffset); + Node *control = createRegion(1); + env()->setControlDependency(control); + Node *effect = createEffectPhi(1, env()->effectDependency(), control); + env()->setEffectDependency(effect); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = env()->slotCount() - 1; i != ei; ++i) { + //### use lifeness info to trim this further! + if (i == CallData::Accumulator) + continue; // should never be alive on loop entry + env()->bindNodeToSlot(createPhi(1, env()->slot(i), control), i); + } + + m_envForOffset.insert(offset, env()->copy()); +} + +void GraphBuilder::startUnwinding() +{ + if (int target = env()->unwindHandlerOffset()) { + mergeIntoSuccessor(target); + } else { + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } +} + +void GraphBuilder::generate_Ret() +{ + Node* control = createNode(opBuilder()->get<Meta::Return>(), env()->accumulator()); + queueFunctionExit(control); +} + +void GraphBuilder::generate_Debug() { Q_UNREACHABLE(); } + +void GraphBuilder::generate_LoadConst(int index) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadZero() +{ + bindAcc(createConstant(0)); +} + +void GraphBuilder::generate_LoadTrue() +{ + bindAcc(m_graph->trueConstant()); +} + +void GraphBuilder::generate_LoadFalse() +{ + bindAcc(m_graph->falseConstant()); +} + +void GraphBuilder::generate_LoadNull() +{ + bindAcc(m_graph->nullNode()); +} + +void GraphBuilder::generate_LoadUndefined() +{ + bindAcc(m_graph->undefinedNode()); +} + +void GraphBuilder::generate_LoadInt(int value) +{ + bindAcc(m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(value)))); +} + +void GraphBuilder::generate_MoveConst(int constIndex, int destTemp) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[constIndex]; + env()->bindNodeToSlot(createNode(opBuilder()->getConstant(v)), destTemp); +} + +void GraphBuilder::generate_LoadReg(int reg) +{ + bindAcc(env()->slot(reg)); +} + +void GraphBuilder::generate_StoreReg(int reg) +{ + Node *n = env()->accumulator(); + if (reg == CallData::This) + n = createNode(opBuilder()->get<Meta::StoreThis>(), n); + env()->bindNodeToSlot(n, reg); +} + +void GraphBuilder::generate_MoveReg(int srcReg, int destReg) +{ + env()->bindNodeToSlot(env()->slot(srcReg), destReg); +} + +void GraphBuilder::generate_LoadImport(int index) +{ + auto func = function()->v4Function(); + Value v = *func->compilationUnit->imports[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadLocal(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(0), + createConstant(index))); +} + +void GraphBuilder::generate_StoreLocal(int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(0), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadScopedLocal(int scope, int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(scope), + createConstant(index))); +} + +void GraphBuilder::generate_StoreScopedLocal(int scope, int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(scope), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadRuntimeString(int stringId) +{ + auto func = function()->v4Function(); + Value v = Value::fromHeapObject(func->compilationUnit->runtimeStrings[stringId]); + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_MoveRegExp(int regExpId, int destReg) +{ + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::LoadRegExp>(), + createConstant(regExpId)), destReg); +} + +void GraphBuilder::generate_LoadClosure(int value) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadClosure>(), + createConstant(value))); +} + +void GraphBuilder::generate_LoadName(int name, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadName>(), + createConstant(name))); +} + +void GraphBuilder::generate_LoadGlobalLookup(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadGlobalLookup>(), createConstant(index))); +} + +void GraphBuilder::generate_StoreNameSloppy(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameSloppy>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_StoreNameStrict(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameStrict>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_LoadElement(int base, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadElement>(), + env()->slot(base), + env()->accumulator())); +} + +void GraphBuilder::generate_StoreElement(int base, int index, int /*traceSlot*/) +{ + createNode(opBuilder()->get<Meta::JSStoreElement>(), + env()->slot(base), + env()->slot(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadProperty(int name, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSLoadProperty>(), + env()->accumulator(), + createConstant(name)); + bindAcc(n); +} + +void GraphBuilder::generate_GetLookup(int index, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSGetLookup>(), + env()->accumulator(), + createConstant(index)); + bindAcc(n); +} + +void GraphBuilder::generate_StoreProperty(int name, int base) +{ + createNode(opBuilder()->get<Meta::JSStoreProperty>(), + env()->slot(base), + createConstant(name), + env()->accumulator()); +} + +void GraphBuilder::generate_SetLookup(int index, int base) +{ + + function()->v4Function()->isStrict() + ? createNode(opBuilder()->get<Meta::JSSetLookupStrict>(), env()->slot(base), + createConstant(index), env()->accumulator()) + : createNode(opBuilder()->get<Meta::JSSetLookupSloppy>(), env()->slot(base), + createConstant(index), env()->accumulator()); +} + +void GraphBuilder::generate_LoadSuperProperty(int property) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperProperty>(), + env()->slot(property))); +} + +void GraphBuilder::generate_StoreSuperProperty(int property) +{ + createNode(opBuilder()->get<Meta::JSStoreSuperProperty>(), + createConstant(property), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadQmlContextPropertyLookup(int propertyIndex, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::QMLLoadQmlContextPropertyLookup>(), + createConstant(propertyIndex))); +} + +void GraphBuilder::generate_Yield() { Q_UNREACHABLE(); } +void GraphBuilder::generate_YieldStar() { Q_UNREACHABLE(); } +void GraphBuilder::generate_Resume(int /*offset*/) { Q_UNREACHABLE(); } + +void GraphBuilder::finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv) +{ + populate(args, argc, argv); + bindAcc(createAndLinkNode(opBuilder()->getJSVarArgsCall(kind, uint16_t(args.size())), + args.data(), size_t(args.size()))); +} + +void GraphBuilder::generate_CallValue(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + finalizeCall(Meta::JSCallValue, args, argc, argv); +} + +void GraphBuilder::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithReceiver, args, argc, argv); +} + +void GraphBuilder::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(name)); + finalizeCall(Meta::JSCallProperty, args, argc, argv); +} + +void GraphBuilder::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(lookupIndex)); + finalizeCall(Meta::JSCallLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(env()->slot(index)); + finalizeCall(Meta::JSCallElement, args, argc, argv); +} + +void GraphBuilder::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(name)); + finalizeCall(Meta::JSCallName, args, argc, argv); +} + +void GraphBuilder::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + finalizeCall(Meta::JSCallPossiblyDirectEval, args, argc, argv); +} + +void GraphBuilder::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(index)); + finalizeCall(Meta::JSCallGlobalLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(index)); + finalizeCall(Meta::QMLCallQmlContextPropertyLookup, args, argc, argv); +} + +void GraphBuilder::generate_SetUnwindHandler(int offset) +{ + m_currentUnwindHandlerOffset = offset ? absoluteOffset(offset) : 0; + env()->setUnwindHandlerOffset(m_currentUnwindHandlerOffset); +} + +void GraphBuilder::generate_UnwindDispatch() +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + { + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + startUnwinding(); + } + + createIfFalse(); + + const auto unwindHandlerOffset = env()->unwindHandlerOffset(); + const auto fallthroughSuccessor = nextInstructionOffset(); + auto nContinuations = m_func->unwindLabelOffsets().size() + 1; + if (unwindHandlerOffset) + ++nContinuations; + Q_ASSERT(nContinuations <= std::numeric_limits<unsigned>::max()); + createNode(opBuilder()->getUnwindDispatch(unsigned(nContinuations), unwindHandlerOffset, + fallthroughSuccessor)); + + { + InterpreterSubEnvironment fallthroughEnv(this); + mergeIntoSuccessor(fallthroughSuccessor); + } + + if (unwindHandlerOffset) { + InterpreterSubEnvironment unwindHandlerEnv(this); + createHandleUnwind(unwindHandlerOffset); + mergeIntoSuccessor(unwindHandlerOffset); + } + + for (int unwindLabelOffset : m_func->unwindLabelOffsets()) { + if (unwindLabelOffset <= currentInstructionOffset()) + continue; + InterpreterSubEnvironment unwindLabelEnv(this); + createHandleUnwind(unwindLabelOffset); + mergeIntoSuccessor(unwindLabelOffset); + } + + setEnv(nullptr); +} + +void GraphBuilder::generate_UnwindToLabel(int level, int offset) +{ + //### For de-optimization, the relative offset probably also needs to be stored + int unwinder = absoluteOffset(offset); + createNode(opBuilder()->get<Meta::UnwindToLabel>(), + createConstant(level), + createConstant(unwinder)); + m_func->addUnwindLabelOffset(unwinder); + startUnwinding(); +} + +void GraphBuilder::generate_DeadTemporalZoneCheck(int name) +{ + Node *check = createNode(opBuilder()->get<Meta::IsEmpty>(), env()->accumulator()); + createNode(opBuilder()->get<Meta::Branch>(), check); + + { //### it's probably better to handle this by de-optimizing + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + createNode(opBuilder()->get<Meta::ThrowReferenceError>(), + createConstant(name)); + startUnwinding(); + } + + createIfFalse(); +} + +void GraphBuilder::generate_ThrowException() +{ + createNode(opBuilder()->get<Meta::Throw>(), env()->accumulator()); + startUnwinding(); +} + +void GraphBuilder::generate_GetException() +{ + bindAcc(createNode(opBuilder()->get<Meta::GetException>())); +} + +void GraphBuilder::generate_SetException() +{ + createNode(opBuilder()->get<Meta::SetException>(), + env()->accumulator()); +} + +void GraphBuilder::generate_CreateCallContext() +{ + createNode(opBuilder()->get<Meta::JSCreateCallContext>()); +} + +void GraphBuilder::generate_PushCatchContext(int index, int name) +{ + createNode(opBuilder()->get<Meta::JSCreateCatchContext>(), + createConstant(index), + createConstant(name)); +} + +void GraphBuilder::generate_PushWithContext() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateWithContext>(), + env()->accumulator())); +} + +void GraphBuilder::generate_PushBlockContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateBlockContext>(), + createConstant(index)); +} + +void GraphBuilder::generate_CloneBlockContext() +{ + createNode(opBuilder()->get<Meta::JSCloneBlockContext>()); +} + +void GraphBuilder::generate_PushScriptContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateScriptContext>(), createConstant(index)); +} + +void GraphBuilder::generate_PopScriptContext() +{ + createNode(opBuilder()->get<Meta::JSPopScriptContext>()); +} + +void GraphBuilder::generate_PopContext() +{ + createNode(opBuilder()->get<Meta::PopContext>()); +} + +void GraphBuilder::generate_GetIterator(int iterator) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetIterator>(), + env()->accumulator(), + createConstant(iterator))); +} + +void GraphBuilder::generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, + int resultSlot) +{ + // See generate_IteratorNext for why this method exists. + + // check that no-one messed around with the operation and made it throwing + Q_ASSERT(iterationNode->operation()->controlOutputCount() == 1); + + // check that it's in the effect chain, because HasException relies on that + Q_ASSERT(iterationNode->operation()->effectOutputCount() == 1); + + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::SelectOutput>(), + iterationNode, + createConstant(1), + graph()->undefinedNode()), + resultSlot); + // Note: the following will NOT set the accumulator, because it contains the return value of + // the runtime call! + Node *ehCheck = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), ehCheck); + + { // EH path: + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + if (auto ehOffset = env()->unwindHandlerOffset()) { + // Ok, there is an exception handler, so go there: + mergeIntoSuccessor(ehOffset); + } else { + // No Exception Handler, so keep the exception set in the engine, and leave the function + // a.s.a.p.: + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } + } + + // Normal control flow: + createIfFalse(); +} + +void GraphBuilder::generate_IteratorNext(int value, int done) +{ + // The way we model exceptions in the graph is that a runtime function will either succeed and + // return a value, or it fails and throws an exception. If it throws, the return value is not + // used because the method did not complete normally, and therefore it might be tainted. + // + // This is a problem for (and only for) IteratorNext and IteratorNextForYieldStart. + // + // What would happen in the normal case, is that the return value (done) is not used/assigned + // when IteratorNext throws, because the exception handling path is chosen. However, the + // interpreter *does* assign it, and will only check for an exception *after* that assignment. + // + // So, in order to work around this odd-duck behavior, we mark the operation as NoThrow, + // override the runtime method and flag it to not throw, and insert extra exception check nodes + // after the SelectOutput that follows the IteratorNext(ForYieldStar). + // + // Also note that the IteratorNext and IteratorNextForYieldStar are the only operations that + // have an inout parameter, and thus require a SelectOutput node to retrieve this. + + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNext>(), + env()->accumulator(), + graph()->undefinedNode()); + bindAcc(n); + env()->bindNodeToSlot(n, done); + generate_IteratorNextAndFriends_TrailingStuff(n, value); +} + +void GraphBuilder::generate_IteratorNextForYieldStar(int iterator, int object) +{ + // Please, PLEASE read the comment in generate_IteratorNext. + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNextForYieldStar>(), + env()->accumulator(), + env()->slot(iterator), + graph()->undefinedNode()); + // Note: the following is a tiny bit different from what generate_IteratorNext does. + bindAcc(n); + generate_IteratorNextAndFriends_TrailingStuff(n, object); +} + +void GraphBuilder::generate_IteratorClose(int done) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIteratorClose>(), + env()->accumulator(), + env()->slot(done))); +} + +void GraphBuilder::generate_DestructureRestElement() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDestructureRestElement>(), + env()->accumulator())); +} + +void GraphBuilder::generate_DeleteProperty(int base, int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteProperty>(), + env()->slot(base), + env()->slot(index))); +} + +void GraphBuilder::generate_DeleteName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofValue() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofValue>(), env()->accumulator())); +} + +void GraphBuilder::generate_DeclareVar(int varName, int isDeletable) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeclareVar>(), + createConstant(isDeletable), + createConstant(varName))); +} + +void GraphBuilder::generate_DefineArray(int argc, int argv) +{ + VarArgNodes args; + finalizeCall(Meta::JSDefineArray, args, argc, argv); +} + +void GraphBuilder::generate_DefineObjectLiteral(int internalClassId, int argc, int argv) +{ + VarArgNodes args; + args.append(createConstant(internalClassId)); + finalizeCall(Meta::JSDefineObjectLiteral, args, argc, argv); +} + +void GraphBuilder::generate_CreateClass(int classIndex, int heritage, int computedNames) +{ + int argc = 0; + int argv = computedNames; + + const QV4::CompiledData::Class *cls = function()->v4Function()->compilationUnit->unitData() + ->classAt(classIndex); + const CompiledData::Method *methods = cls->methodTable(); + for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { + if (methods[i].name == std::numeric_limits<unsigned>::max()) + ++argc; + } + + VarArgNodes args; + args.append(createConstant(classIndex)); + args.append(env()->slot(heritage)); + finalizeCall(Meta::JSCreateClass, args, argc, argv); +} + +void GraphBuilder::generate_CreateMappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateMappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateUnmappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateUnmappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateRestParameter(int argIndex) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateRestParameter>(), + createConstant(argIndex))); +} + +void GraphBuilder::generate_ConvertThisToObject() +{ + Node* control = createNode(opBuilder()->get<Meta::JSThisToObject>(), + env()->slot(CallData::This)); + env()->bindNodeToSlot(control, CallData::This); +} + +void GraphBuilder::generate_LoadSuperConstructor() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperConstructor>(), + env()->slot(CallData::Function))); +} + +void GraphBuilder::generate_ToObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::ToObject>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_TailCall(int func, int thisObject, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + populate(args, argc, argv); + Node *n = createAndLinkNode(opBuilder()->getJSTailCall(uint16_t(args.size())), args.data(), + size_t(args.size())); + queueFunctionExit(n); +} + +void GraphBuilder::generate_Construct(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstruct, args, argc, argv); +} + +void GraphBuilder::generate_ConstructWithSpread(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstructWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_Jump(int offset) +{ + auto jumpTarget = absoluteOffset(offset); + mergeIntoSuccessor(jumpTarget); +} + +void GraphBuilder::generate_JumpTrue(int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(env()->accumulator())); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfTrue(); + mergeIntoSuccessor(jumpTarget); + } + + createIfFalse(); +} + +void GraphBuilder::generate_JumpFalse(int traceSlot, int offset) +{ + generate_JumpFalse(env()->accumulator(), traceSlot, offset); +} + +void GraphBuilder::generate_JumpFalse(Node *condition, int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(condition)); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNoException(int offset) +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNotUndefined(int offset) +{ + Node *condition = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->accumulator(), + graph()->undefinedNode()); + generate_JumpFalse(condition, NoTraceSlot, offset); +} + +void GraphBuilder::generate_CmpEqNull() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSEqual>(), + env()->accumulator(), + graph()->nullNode())); +} + +void GraphBuilder::generate_CmpNeNull() +{ + generate_CmpEqNull(); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEqInt(int lhs) +{ + auto left = createConstant(lhs); + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + left, + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNeInt(int lhs) +{ + generate_CmpEqInt(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEq(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNe(int lhs) +{ + generate_CmpEq(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpGt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpGe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictEqual(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictNotEqual(int lhs) +{ + generate_CmpStrictEqual(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpIn(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIn>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpInstanceOf(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSInstanceOf>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UNot() +{ + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + createToBoolean(env()->accumulator()))); +} + +void GraphBuilder::generate_UPlus(int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSToNumber>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UMinus(int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSNegate>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UCompl() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(-1))); +} + +void GraphBuilder::generate_Increment(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSAdd>(), + env()->accumulator(), + createConstant(1))); +} + + +void GraphBuilder::generate_Decrement(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->accumulator(), + createConstant(1))); +} + +void GraphBuilder::generate_Add(int lhs, int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSAdd>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_BitAnd(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitOr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitXor(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UShr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shl(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->slot(lhs), + env()->accumulator())); +} + + +void GraphBuilder::generate_BitAndConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitOrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitXorConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_UShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShlConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_Exp(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSExponentiate>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mul(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSMultiply>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Div(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDivide>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mod(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSModulo>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Sub(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) +{ + for (int reg = firstReg; reg < firstReg + count; ++reg) + env()->bindNodeToSlot(graph()->emptyNode(), reg); +} + +void GraphBuilder::generate_ThrowOnNullOrUndefined() +{ + createNode(opBuilder()->get<Meta::JSThrowOnNullOrUndefined>(), + env()->accumulator()); +} + +void GraphBuilder::generate_GetTemplateObject(int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetTemplateObject>(), + createConstant(index))); +} + +GraphBuilder::Verdict GraphBuilder::startInstruction(Moth::Instr::Type /*instr*/) +{ + // This handles a couple of cases on how flow control can end up at this instruction. + + const auto off = currentInstructionOffset(); + if (auto newEnv = m_envForOffset[off]) { + // Ok, there was a jump from before to this point (which registered an environment), so we + // have two options: + if (env() != newEnv && env() != nullptr) { + // There is a current environment different from the environment active when we took the + // jump. This happens with e.g. an if-then-else: + // + // acc = condition + // JumpFalse else-block + // ... then block + // Jump end-if + // else-block: + // ... else block + // end-if: + // .. some instruction <--- we're here + // + // in that case we merge the after-else environment into the after-then environment: + newEnv->merge(env()); + } else { + // There is not a current environment. This can happen with e.g. a loop: + // loop-start: + // acc = condition + // JumpFalse loop-end + // ... loop body + // Jump loop-start + // loop-end: + // .... some instruction <--- we're here + // + // The last jump of the loop will clear the environment, so at this point we only have + // the environment registered by the JumpFalse. This is the asy case: no merges, just + // take the registered environment unchanged. + } + + // Leave the merged environment as-is, and continue with a copy. We cannot change the + // registered environment in case this point also happens to be a loop start. + setEnv(newEnv->copy()); + } + + if (env() == nullptr) { + // Ok, there is no environment, meaning nobody jumped to this instruction, and the previous + // instruction doesn't let control flow end up here. So, this is dead code. + // This can happen for JS like: + // + // if (condition) { + // return something + // } else { + // return somethingElse + // } + // someCode <--- we're here + return SkipInstruction; + } + + const LabelInfo *info = isLoopStart(off); + if (info && env()) { + // Ok, this instruction is the start of a loop, meaning there will be a jump backwards to + // this point. Make sure there is a Region node with Phi nodes here. + handleLoopStart(*info); + } + + return ProcessInstruction; +} + +void GraphBuilder::endInstruction(Moth::Instr::Type /*instr*/) {} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graphbuilder_p.h b/src/qml/jit/qv4graphbuilder_p.h new file mode 100644 index 0000000000..450d8640b7 --- /dev/null +++ b/src/qml/jit/qv4graphbuilder_p.h @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GRAPHBUILDER_P_H +#define QV4GRAPHBUILDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4global_p.h> +#include <private/qv4bytecodehandler_p.h> +#include <private/qv4ir_p.h> +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// The graph builder walks the byte-code, and produces a graph. The graph is a digraph, where the +// nodes have operations, and the edges are dependencies (or inputs). +class GraphBuilder: protected Moth::ByteCodeHandler +{ + Q_DISABLE_COPY_MOVE(GraphBuilder) + + enum { NoTraceSlot = -1 }; + + struct LabelInfo { //### extend this to also capture the amount of slots that are live + LabelInfo() = default; + LabelInfo(unsigned label) : labelOffset(label) {} + unsigned labelOffset = 0; + }; + +public: + static void buildGraph(IR::Function *function); + + class InterpreterEnvironment; + + void setEnv(InterpreterEnvironment *newEnv) + { m_currentEnv = newEnv; } + + InterpreterEnvironment *env() const + { return m_currentEnv; } + +private: + GraphBuilder(IR::Function *function); + ~GraphBuilder() override = default; + + void startGraph(); + void endGraph(); + + Node *bindAcc(Node *n); + Node *createAndLinkNode(Operation *op, Node *operands[], size_t opCount, bool incomplete = false); + Node *createNode(Operation *op, bool incomplete = false); + Node *createNode(Operation *op, Node *n1); + Node *createNode(Operation *op, Node *n1, Node *n2); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4); + Node *createRegion(unsigned nControlInputs); + Node *createIfTrue(); + Node *createIfFalse(); + Node *createConstant(int v); + Node *createPhi(unsigned nInputs, Node *input, Node *control); + Node *createEffectPhi(unsigned nInputs, Node *input, Node *control); + Node *createHandleUnwind(int offset); + Node *mergeControl(Node *c1, Node *c2); + Node *mergeEffect(Node *e1, Node *e2, Node *control); + Node *mergeValue(Node *v1, Node *v2, Node *control); + + Node *createToBoolean(Node *input); + + using VarArgNodes = QVarLengthArray<Node *, 32>; + void populate(VarArgNodes &args, int argc, int argv); + + void queueFunctionExit(Node *exitNode); + + Function *function() const + { return m_func; } + + Graph *graph() + { return m_graph; } + + Node *mergeIntoSuccessor(int offset); + + OperationBuilder *opBuilder() const + { return m_graph->opBuilder(); } + + int absoluteOffset(int offset) const + { return offset + nextInstructionOffset(); } + + const LabelInfo *labelInfoAt(unsigned offset) const; + const LabelInfo *isLoopStart(unsigned offset) const; + void handleLoopStart(const LabelInfo &labelInfo); + void startUnwinding(); + +protected: // ByteCodeHandler + void generate_Ret() override; + void generate_Debug() override; + void generate_LoadConst(int index) override; + void generate_LoadZero() override; + void generate_LoadTrue() override; + void generate_LoadFalse() override; + void generate_LoadNull() override; + void generate_LoadUndefined() override; + void generate_LoadInt(int value) override; + void generate_MoveConst(int constIndex, int destTemp) override; + void generate_LoadReg(int reg) override; + void generate_StoreReg(int reg) override; + void generate_MoveReg(int srcReg, int destReg) override; + void generate_LoadImport(int index) override; + void generate_LoadLocal(int index, int traceSlot) override; + void generate_StoreLocal(int index) override; + void generate_LoadScopedLocal(int scope, int index, int traceSlot) override; + void generate_StoreScopedLocal(int scope, int index) override; + void generate_LoadRuntimeString(int stringId) override; + void generate_MoveRegExp(int regExpId, int destReg) override; + void generate_LoadClosure(int value) override; + void generate_LoadName(int name, int traceSlot) override; + void generate_LoadGlobalLookup(int index, int traceSlot) override; + void generate_StoreNameSloppy(int name) override; + void generate_StoreNameStrict(int name) override; + void generate_LoadElement(int base, int traceSlot) override; + void generate_StoreElement(int base, int index, int traceSlot) override; + void generate_LoadProperty(int name, int traceSlot) override; + void generate_GetLookup(int index, int traceSlot) override; + void generate_StoreProperty(int name, int base) override; + void generate_SetLookup(int index, int base) override; + void generate_LoadSuperProperty(int property) override; + void generate_StoreSuperProperty(int property) override; + void generate_LoadQmlContextPropertyLookup(int property, int traceSlot) override; + void generate_Yield() override; + void generate_YieldStar() override; + void generate_Resume(int offset) override; + void finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv); + void generate_CallValue(int name, int argc, int argv, int traceSlot) override; + void generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_CallProperty(int name, int base, int argc, int argv, int traceSlot) override; + void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int traceSlot) override; + void generate_CallElement(int base, int index, int argc, int argv, int traceSlot) override; + void generate_CallName(int name, int argc, int argv, int traceSlot) override; + void generate_CallPossiblyDirectEval(int argc, int argv, int traceSlot) override; + void generate_CallGlobalLookup(int index, int argc, int argv, int traceSlot) override; + void generate_CallQmlContextPropertyLookup(int index, int argc, int argv, int traceSlot) override; + void generate_SetUnwindHandler(int offset) override; + void generate_UnwindDispatch() override; + void generate_UnwindToLabel(int level, int offset) override; + void generate_DeadTemporalZoneCheck(int name) override; + void generate_ThrowException() override; + void generate_GetException() override; + void generate_SetException() override; + void generate_CreateCallContext() override; + void generate_PushCatchContext(int index, int name) override; + void generate_PushWithContext() override; + void generate_PushBlockContext(int index) override; + void generate_CloneBlockContext() override; + void generate_PushScriptContext(int index) override; + void generate_PopScriptContext() override; + void generate_PopContext() override; + void generate_GetIterator(int iterator) override; + void generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, int resultSlot); + void generate_IteratorNext(int value, int done) override; + void generate_IteratorNextForYieldStar(int iterator, int object) override; + void generate_IteratorClose(int done) override; + void generate_DestructureRestElement() override; + void generate_DeleteProperty(int base, int index) override; + void generate_DeleteName(int name) override; + void generate_TypeofName(int name) override; + void generate_TypeofValue() override; + void generate_DeclareVar(int varName, int isDeletable) override; + void generate_DefineArray(int argc, int argv) override; + void generate_DefineObjectLiteral(int internalClassId, int argc, int argv) override; + void generate_CreateClass(int classIndex, int heritage, int computedNames) override; + void generate_CreateMappedArgumentsObject() override; + void generate_CreateUnmappedArgumentsObject() override; + void generate_CreateRestParameter(int argIndex) override; + void generate_ConvertThisToObject() override; + void generate_LoadSuperConstructor() override; + void generate_ToObject() override; + void generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_TailCall(int func, int thisObject, int argc, int argv) override; + void generate_Construct(int func, int argc, int argv) override; + void generate_ConstructWithSpread(int func, int argc, int argv) override; + void generate_Jump(int offset) override; + void generate_JumpTrue(int traceSlot, int offset) override; + void generate_JumpFalse(int traceSlot, int offset) override; + void generate_JumpFalse(Node *condition, int traceSlot, int offset); + void generate_JumpNoException(int offset) override; + void generate_JumpNotUndefined(int offset) override; + void generate_CmpEqNull() override; + void generate_CmpNeNull() override; + void generate_CmpEqInt(int lhs) override; + void generate_CmpNeInt(int lhs) override; + void generate_CmpEq(int lhs) override; + void generate_CmpNe(int lhs) override; + void generate_CmpGt(int lhs) override; + void generate_CmpGe(int lhs) override; + void generate_CmpLt(int lhs) override; + void generate_CmpLe(int lhs) override; + void generate_CmpStrictEqual(int lhs) override; + void generate_CmpStrictNotEqual(int lhs) override; + void generate_CmpIn(int lhs) override; + void generate_CmpInstanceOf(int lhs) override; + void generate_UNot() override; + void generate_UPlus(int traceSlot) override; + void generate_UMinus(int traceSlot) override; + void generate_UCompl() override; + void generate_Increment(int traceSlot) override; + void generate_Decrement(int traceSlot) override; + void generate_Add(int lhs, int traceSlot) override; + void generate_BitAnd(int lhs) override; + void generate_BitOr(int lhs) override; + void generate_BitXor(int lhs) override; + void generate_UShr(int lhs) override; + void generate_Shr(int lhs) override; + void generate_Shl(int lhs) override; + void generate_BitAndConst(int rhs) override; + void generate_BitOrConst(int rhs) override; + void generate_BitXorConst(int rhs) override; + void generate_UShrConst(int rhs) override; + void generate_ShrConst(int rhs) override; + void generate_ShlConst(int rhs) override; + void generate_Exp(int lhs) override; + void generate_Mul(int lhs, int traceSlot) override; + void generate_Div(int lhs) override; + void generate_Mod(int lhs, int traceSlot) override; + void generate_Sub(int lhs, int traceSlot) override; + void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; + void generate_ThrowOnNullOrUndefined() override; + void generate_GetTemplateObject(int index) override; + + Verdict startInstruction(Moth::Instr::Type instr) override; + void endInstruction(Moth::Instr::Type instr) override; + +private: + IR::Function *m_func; + Graph *m_graph; + InterpreterEnvironment *m_currentEnv; + std::vector<Node *> m_exitControls; + QHash<int, InterpreterEnvironment *> m_envForOffset; + std::vector<LabelInfo> m_labelInfos; + int m_currentUnwindHandlerOffset = 0; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPHBUILDER_P_H diff --git a/src/qml/jit/qv4ir.cpp b/src/qml/jit/qv4ir.cpp new file mode 100644 index 0000000000..cb3eeeec60 --- /dev/null +++ b/src/qml/jit/qv4ir.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qqmlglobal_p.h> +#include "qv4ir_p.h" +#include "qv4node_p.h" +#include "qv4function_p.h" +#include <qv4graph_p.h> +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" +#include "qv4util_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qfile.h> + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcJsonIR, "qt.v4.ir.json"); +Q_LOGGING_CATEGORY(lcDotIR, "qt.v4.ir.dot"); +Q_LOGGING_CATEGORY(lcVerify, "qt.v4.ir.verify"); + +Function::Function(QV4::Function *qv4Function) + : qv4Function(qv4Function) + , m_graph(Graph::create(this)) + , m_dumper(nullptr) + , m_nodeInfo(128, nullptr) +{ +} + +Function::~Function() +{ + delete m_dumper; +} + +QString Function::name() const +{ + QString name; + if (auto n = v4Function()->name()) + name = n->toQString(); + if (name.isEmpty()) + name = QString::asprintf("%p", v4Function()); + auto loc = v4Function()->sourceLocation(); + return name + QStringLiteral(" (%1:%2:%3)").arg(loc.sourceFile, QString::number(loc.line), + QString::number(loc.column)); +} + +void Function::dump(const QString &description) const +{ + Dumper::dump(this, description); +} + +void Function::dump() const +{ + dump(QStringLiteral("Debug:")); +} + +Dumper *Function::dumper() const +{ + if (!m_dumper) + m_dumper = new Dumper(this); + return m_dumper; +} + +Function::StringId Function::addString(const QString &s) +{ + m_stringPool.push_back(s); + return m_stringPool.size() - 1; +} + +NodeInfo *Function::nodeInfo(Node *n, bool createIfNecessary) const +{ + if (n->id() >= m_nodeInfo.size()) + m_nodeInfo.resize(n->id() * 2, nullptr); + + NodeInfo *&info = m_nodeInfo[n->id()]; + if (info == nullptr && createIfNecessary) { + info = m_pool.New<NodeInfo>(); + info->setType(n->operation()->type()); + } + return info; +} + +void Function::copyBytecodeOffsets(Node *from, Node *to) +{ + auto toInfo = nodeInfo(to); + if (auto fromInfo = nodeInfo(from)) { + toInfo->setBytecodeOffsets(fromInfo->currentInstructionOffset(), + fromInfo->nextInstructionOffset()); + } +} + +Dumper::Dumper(const Function *f) +{ + if (!f) + return; +} + +void Dumper::dump(const Function *f, const QString &description) +{ + if (false && lcJsonIR().isDebugEnabled()) { + Dumper *dumper = f->dumper(); + + qCDebug(lcJsonIR).noquote().nospace() << description + QLatin1String(":\n"); + for (const auto &line : dumper->dump(f).split('\n')) + qCDebug(lcJsonIR).noquote().nospace() << line; + } + + if (lcDotIR().isDebugEnabled()) + dot(f, description); +} + +QByteArray Dumper::dump(const Function *f) +{ + QJsonObject fo; + + { + QString name; + if (auto n = f->v4Function()->name()) + name = n->toQString(); + fo[QLatin1String("_searchKey")] = QStringLiteral("function %1").arg(name); + if (name.isEmpty()) + name = QString::asprintf("%p", f->v4Function()); + fo[QLatin1String("name")] = name; + } + + auto loc = f->v4Function()->sourceLocation(); + fo[QLatin1String("source")] = loc.sourceFile; + fo[QLatin1String("line")] = loc.line; + fo[QLatin1String("column")] = loc.column; + + { + QJsonArray gn; + QJsonArray ge; + NodeCollector nodes(f->graph(), /*collectUses =*/ true); + nodes.sortById(); + for (Node *n : nodes.reachable()) { + gn.append(dump(n, f)); + int inputIndex = 0; + for (Node *input : n->inputs()) { + QJsonObject edge; + edge[QLatin1String("from")] = int(input->id()); + edge[QLatin1String("to")] = int(n->id()); + edge[QLatin1String("index")] = inputIndex; + if (inputIndex < n->operation()->valueInputCount()) { + edge[QLatin1String("type")] = QLatin1String("value"); + } else if (inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount()) { + edge[QLatin1String("type")] = QLatin1String("effect"); + } else { + edge[QLatin1String("type")] = QLatin1String("control"); + } + Q_ASSERT(inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount() + + n->operation()->controlInputCount()); + ge.append(edge); + ++inputIndex; + } + } + QJsonObject g; + g[QLatin1String("nodes")] = gn; + g[QLatin1String("edges")] = ge; + fo[QLatin1String("graph")] = g; + } + + m_doc.setObject(fo); + return m_doc.toJson(QJsonDocument::Indented); +} + +QJsonValue toJSonValue(QV4::Value v) +{ + switch (v.type()) { + case QV4::Value::Undefined_Type: return QJsonValue(QJsonValue::Undefined); + case QV4::Value::Null_Type: return QJsonValue(QJsonValue::Null); + case QV4::Value::Boolean_Type: return QJsonValue(v.booleanValue()); + case QV4::Value::Integer_Type: return QJsonValue(v.int_32()); + case QV4::Value::Managed_Type: + if (String *s = v.stringValue()) + return QJsonValue(s->toQString()); + else + return QJsonValue(QLatin1String("<managed>")); + default: return QJsonValue(v.doubleValue()); + } +} + +QJsonValue Dumper::dump(const Node * const node, const Function *f) +{ + QJsonObject n; + n[QLatin1String("id")] = int(node->id()); + n[QLatin1String("kind")] = node->operation()->debugString(); + switch (node->operation()->kind()) { + case Meta::Parameter: { + auto info = ParameterPayload::get(*node->operation()); + n[QLatin1String("name")] = f->string(info->stringId()); + n[QLatin1String("index")] = int(info->parameterIndex()); + break; + } + case Meta::Constant: { + auto info = ConstantPayload::get(*node->operation()); + n[QLatin1String("value")] = toJSonValue(info->value()); + break; + } + default: + break; + } + return n; +} + +void Dumper::dot(const Function *f, const QString &description) +{ + static const bool skipFramestate = qEnvironmentVariableIsSet("QV4_JIT_DOT_SKIP_FRAMESTATE"); + + auto node = [](Node *n) { + return QStringLiteral("n%1[label=\"%1: %2%3\"];\n").arg(QString::number(n->id()), + n->operation()->debugString(), + n->isDead() ? QStringLiteral(" (dead)") + : QString()); + }; + + Graph *g = f->graph(); + QString out; + out += QLatin1Char('\n'); + out += QStringLiteral("digraph{root=\"n%1\" label=\"%2\";" + "node[shape=rect];" + "edge[dir=back fontsize=10];\n") + .arg(g->startNode()->id()) + .arg(description); + out += node(g->startNode()); + const bool dumpUses = false; // set to true to see all nodes + NodeCollector nodes(g, dumpUses, skipFramestate); + for (Node *n : nodes.reachable()) { + if (n == g->startNode()) + continue; + + out += node(n); + + unsigned inputIndex = 0; + for (Node *input : n->inputs()) { + if (input == nullptr) + continue; + out += QStringLiteral("n%2->n%1[style=").arg(QString::number(n->id()), + QString::number(input->id())); + if (inputIndex < n->operation()->valueInputCount() || + inputIndex == n->operation()->indexOfFrameStateInput()) { + out += QStringLiteral("solid headlabel=\"%1\"").arg(inputIndex); + } else if (inputIndex < unsigned(n->operation()->valueInputCount() + + n->operation()->effectInputCount())) { + out += QStringLiteral("dotted headlabel=\"%1\"").arg(inputIndex); + } else { + out += QStringLiteral("dashed headlabel=\"%1\"").arg(inputIndex); + } + out += QStringLiteral("];\n"); + ++inputIndex; + } + } + out += QStringLiteral("}\n"); + qCDebug(lcDotIR).nospace().noquote() << out; + + QFile of(description + QStringLiteral(".dot")); + of.open(QIODevice::WriteOnly); + of.write(out.toUtf8()); + of.close(); +} + +void Function::verify() const +{ +#ifndef QT_NO_DEBUG + unsigned problemsFound = 0; + + auto verifyNodeAgainstOperation = [&problemsFound](const Node *n) { + const Operation *op = n->operation(); + if (op->totalInputCount() != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Node" << n->id() << "has" << n->inputCount() + << "inputs, but it's operation" << op->debugString() + << "requires" << op->totalInputCount() << "inputs"; + } + + if (n->opcode() == Meta::Phi || n->opcode() == Meta::EffectPhi) { + if (n->controlInput()->opcode() != Meta::Region) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() << "is not a region"; + } + if (n->controlInput()->inputCount() + 1 != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() + << "has" << n->controlInput()->inputCount() + << "inputs while phi node has" << n->inputCount() + << "inputs"; + } + } + + //### todo: verify outputs: value outputs are allowed to be unused, but the effect and + // control outputs have to be linked up, except: + //### todo: verify if no use is a nullptr, except for operations that can throw, where the + // last one is allowed to be a nullptr when an unwind handler is missing. + }; + + NodeWorkList todo(graph()); + todo.enqueue(graph()->endNode()); + while (Node *n = todo.dequeueNextNodeForVisiting()) { + todo.enqueueAllInputs(n); + todo.enqueueAllUses(n); + + verifyNodeAgainstOperation(n); + } + //### TODO: + if (problemsFound != 0) { + dump(QStringLiteral("Problematic graph")); + qFatal("Found %u problems during graph verification!", problemsFound); + } +#endif // QT_NO_xDEBUG +} + +QString Type::debugString() const +{ + if (isNone()) + return QStringLiteral("none"); + if (isInvalid()) + return QStringLiteral("invalid"); + + QStringList s; + if (m_t & Bool) + s += QStringLiteral("boolean"); + if (m_t & Int32) + s += QStringLiteral("int32"); + if (m_t & Double) + s += QStringLiteral("double"); + if (m_t & Undefined) + s += QStringLiteral("undefined"); + if (m_t & Null) + s += QStringLiteral("null"); + if (m_t & Empty) + s += QStringLiteral("empty"); + if (m_t & RawPointer) + s += QStringLiteral("raw pointer"); + + return s.join(QLatin1String(" ")); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4ir_p.h b/src/qml/jit/qv4ir_p.h new file mode 100644 index 0000000000..e21a80528d --- /dev/null +++ b/src/qml/jit/qv4ir_p.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4IR_P_H +#define QV4IR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4function_p.h> +#include <QtCore/qjsondocument.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Dumper; +class Graph; + +class Node; +class NodeInfo; + +class Function +{ + Q_DISABLE_COPY_MOVE(Function) +public: + Function(QV4::Function *qv4Function); + ~Function(); + + void verify() const; + + QV4::Function *v4Function() const + { return qv4Function; } + + QString name() const; + + QQmlJS::MemoryPool *pool() + { return &m_pool; } + + Graph *graph() const + { return m_graph; } + + void dump(const QString &description) const; + void dump() const; // for calling in the debugger + Dumper *dumper() const; + + using StringId = size_t; + StringId addString(const QString &s); + QString string(StringId id) const + { return m_stringPool[id]; } + + NodeInfo *nodeInfo(Node *n, bool createIfNecessary = true) const; + void copyBytecodeOffsets(Node *from, Node *to); + + void addUnwindLabelOffset(int absoluteOffset) + { m_unwindLabelOffsets.push_back(absoluteOffset); } + + const std::vector<int> &unwindLabelOffsets() const + { return m_unwindLabelOffsets; } + +private: + QV4::Function *qv4Function; + mutable QQmlJS::MemoryPool m_pool; + Graph *m_graph; + mutable Dumper *m_dumper; + std::vector<QString> m_stringPool; + mutable std::vector<NodeInfo *> m_nodeInfo; //### move the into the _pool + std::vector<int> m_unwindLabelOffsets; +}; + +class Dumper +{ + Q_DISABLE_COPY_MOVE(Dumper) + +public: + Dumper(const Function *f); + ~Dumper() = default; + + static void dump(const Function *f, const QString &description); + static void dot(const Function *f, const QString &description); + +private: + QByteArray dump(const Function *f); + QJsonValue dump(const Node *node, const Function *f); + +private: + QJsonDocument m_doc; +}; + +class Type +{ + // None is for nodes with no type (e.g. a Return) + // The others form a lattice: + // Any -> Object -> Invalid + // ^^^ -> Number -> Integral -> Int32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> UInt32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> Bool -> ^^^^^^^ + // ^^^ -> Number -> Double -> ^^^^^^^ + // ^^^ -> Undefined -> ^^^^^^^ + // ^^^ -> Null -> ^^^^^^^ + // ^^^ -> Empty -> ^^^^^^^ + enum InternalType: int16_t { + None = 0, + + Object = 1 << 0, + Bool = 1 << 1, + Int32 = 1 << 2, + UInt32 = 1 << 3, + Double = 1 << 4, + Undefined = 1 << 5, + Null = 1 << 6, + Empty = 1 << 7, + RawPointer = 1 << 8, + Invalid = -1, + + Integral = Int32 | UInt32 | Bool, + Number = Integral | Double, + Any = Object | Number | Undefined | Empty | Null, + }; + + Type(InternalType t) : m_t(t) {} + +public: + Type() = default; + + bool operator==(const Type &other) const + { return m_t == other.m_t; } + + static Type noneType() { return Type(None); } + static Type anyType() { return Type(Any); } + static Type undefinedType() { return Type(Undefined); } + static Type emptyType() { return Type(Empty); } + static Type booleanType() { return Type(Bool); } + static Type int32Type() { return Type(Int32); } + static Type doubleType() { return Type(Double); } + static Type numberType() { return Type(Number); } + static Type nullType() { return Type(Null); } + static Type objectType() { return Type(Object); } + static Type rawPointerType() { return Type(RawPointer); } + + bool isAny() const { return m_t == Any; } + bool isBoolean() const { return m_t == Bool; } + bool isInt32() const { return m_t == Int32; } + bool isInvalid() const { return m_t == Invalid; } + bool isNone() const { return m_t == None; } + bool isDouble() const { return m_t == Double; } + bool isUndefined() const { return m_t == Undefined; } + bool isNull() const { return m_t == Null; } + bool isEmpty() const { return m_t == Empty; } + bool isObject() const { return m_t == Object; } + bool isRawPointer() const { return m_t == RawPointer; } + bool isIntegral() const { return matches(Integral); } + bool isNumber() const { return matches(Number); } + + Type operator|(Type other) const + { return Type(InternalType(int16_t(m_t) | int16_t(other.m_t))); } + + Type &operator|=(Type other) + { + m_t = (InternalType(int16_t(m_t) | int16_t(other.m_t))); + return *this; + } + + QString debugString() const; + +private: + bool matches(InternalType it) const + { + return (m_t & ~it) == 0 && (m_t & it) != 0; + } + +private: + InternalType m_t = None; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4IR_P_H diff --git a/src/qml/jit/qv4jithelpers.cpp b/src/qml/jit/qv4jithelpers.cpp deleted file mode 100644 index 674fd8c8c8..0000000000 --- a/src/qml/jit/qv4jithelpers.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qv4jithelpers_p.h" -#include "qv4engine_p.h" -#include "qv4function_p.h" -#include "qv4value_p.h" -#include "qv4object_p.h" -#include "qv4functionobject_p.h" -#include "qv4lookup_p.h" -#include <QtCore/private/qnumeric_p.h> - -#ifdef V4_ENABLE_JIT - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace JIT { -namespace Helpers { - -void convertThisToObject(ExecutionEngine *engine, Value *t) -{ - if (!t->isObject()) { - if (t->isNullOrUndefined()) { - *t = engine->globalObject->asReturnedValue(); - } else { - *t = t->toObject(engine)->asReturnedValue(); - } - } -} - -ReturnedValue loadGlobalLookup(Function *f, ExecutionEngine *engine, int index) -{ - Lookup *l = f->compilationUnit->runtimeLookups + index; - return l->globalGetter(l, engine); -} - -ReturnedValue loadQmlContextPropertyLookup(Function *f, ExecutionEngine *engine, int index) -{ - Lookup *l = f->compilationUnit->runtimeLookups + index; - return l->qmlContextPropertyGetter(l, engine, nullptr); -} - -ReturnedValue toObject(ExecutionEngine *engine, const Value &obj) -{ - if (obj.isObject()) - return obj.asReturnedValue(); - - return obj.toObject(engine)->asReturnedValue(); -} - -ReturnedValue exp(const Value &base, const Value &exp) -{ - double b = base.toNumber(); - double e = exp.toNumber(); - if (qt_is_inf(e) && (b == 1 || b == -1)) - return Encode(qt_snan()); - return Encode(pow(b,e)); -} - -ReturnedValue getLookup(Function *f, ExecutionEngine *engine, const Value &base, int index) -{ - Lookup *l = f->compilationUnit->runtimeLookups + index; - return l->getter(l, engine, base); -} - -void setLookupSloppy(Function *f, int index, Value &base, const Value &value) -{ - ExecutionEngine *engine = f->internalClass->engine; - QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; - l->setter(l, engine, base, value); -} - -void setLookupStrict(Function *f, int index, Value &base, const Value &value) -{ - ExecutionEngine *engine = f->internalClass->engine; - QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; - if (!l->setter(l, engine, base, value)) - engine->throwTypeError(); -} - - -void pushBlockContext(Value *stack, int index) -{ - ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); - stack[CallData::Context] = Runtime::method_createBlockContext(c, index); -} - -void cloneBlockContext(Value *contextSlot) -{ - *contextSlot = Runtime::method_cloneBlockContext(static_cast<QV4::ExecutionContext *>(contextSlot)); -} - -void pushScriptContext(Value *stack, ExecutionEngine *engine, int index) -{ - stack[CallData::Context] = Runtime::method_createScriptContext(engine, index); -} - -void popScriptContext(Value *stack, ExecutionEngine *engine) -{ - stack[CallData::Context] = Runtime::method_popScriptContext(engine); -} - -ReturnedValue deleteProperty(QV4::Function *function, const QV4::Value &base, const QV4::Value &index) -{ - auto engine = function->internalClass->engine; - if (!Runtime::method_deleteProperty(engine, base, index)) { - if (function->isStrict()) - engine->throwTypeError(); - return Encode(false); - } else { - return Encode(true); - } -} - -ReturnedValue deleteName(Function *function, int name) -{ - auto engine = function->internalClass->engine; - if (!Runtime::method_deleteName(engine, name)) { - if (function->isStrict()) - engine->throwTypeError(); - return Encode(false); - } else { - return Encode(true); - } -} - -void throwOnNullOrUndefined(ExecutionEngine *engine, const Value &v) -{ - if (v.isNullOrUndefined()) - engine->throwTypeError(); -} - -} // Helpers namespace -} // JIT namespace -} // QV4 namespace -QT_END_NAMESPACE - -#endif // V4_ENABLE_JIT diff --git a/src/qml/jit/qv4loopinfo.cpp b/src/qml/jit/qv4loopinfo.cpp new file mode 100644 index 0000000000..0366c49e30 --- /dev/null +++ b/src/qml/jit/qv4loopinfo.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4loopinfo_p.h" +#include "qv4domtree_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcLoopinfo, "qt.v4.ir.loopinfo") + +void LoopInfo::detectLoops() +{ + blockInfos.resize(dt.function()->blockCount()); + + std::vector<MIBlock *> backedges; + backedges.reserve(4); + + const auto order = dt.calculateDFNodeIterOrder(); + for (MIBlock *bb : order) { + if (bb->isDeoptBlock()) + continue; + + backedges.clear(); + + for (MIBlock *pred : bb->inEdges()) { + if (bb == pred || dt.dominates(bb->index(), pred->index())) + backedges.push_back(pred); + } + + if (!backedges.empty()) + subLoop(bb, backedges); + } + + collectLoopExits(); + + dump(); +} + +void LoopInfo::collectLoopExits() +{ + for (MIBlock::Index i = 0, ei = MIBlock::Index(blockInfos.size()); i != ei; ++i) { + BlockInfo &bi = blockInfos[i]; + MIBlock *currentBlock = dt.function()->block(i); + if (bi.isLoopHeader) { + for (MIBlock *outEdge : currentBlock->outEdges()) { + if (outEdge != currentBlock && !inLoopOrSubLoop(outEdge, currentBlock)) + bi.loopExits.push_back(outEdge); + } + } + if (MIBlock *containingLoop = bi.loopHeader) { + BlockInfo &loopInfo = blockInfos[containingLoop->index()]; + for (MIBlock *outEdge : currentBlock->outEdges()) { + if (outEdge != containingLoop && !inLoopOrSubLoop(outEdge, containingLoop)) + loopInfo.loopExits.push_back(outEdge); + } + } + } +} + +bool LoopInfo::inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const +{ + const BlockInfo &bi = blockInfos[block->index()]; + MIBlock *loopHeaderForBlock = bi.loopHeader; + if (loopHeaderForBlock == nullptr) + return false; // block is not in any loop + + while (loopHeader) { + if (loopHeader == loopHeaderForBlock) + return true; + // look into the parent loop of loopHeader to see if block is contained there + loopHeader = blockInfos[loopHeader->index()].loopHeader; + } + + return false; +} + +void LoopInfo::subLoop(MIBlock *loopHead, const std::vector<MIBlock *> &backedges) +{ + blockInfos[loopHead->index()].isLoopHeader = true; + + std::vector<MIBlock *> worklist; + worklist.reserve(backedges.size() + 8); + worklist.insert(worklist.end(), backedges.begin(), backedges.end()); + while (!worklist.empty()) { + MIBlock *predIt = worklist.back(); + worklist.pop_back(); + + MIBlock *subloop = blockInfos[predIt->index()].loopHeader; + if (subloop) { + // This is a discovered block. Find its outermost discovered loop. + while (MIBlock *parentLoop = blockInfos[subloop->index()].loopHeader) + subloop = parentLoop; + + // If it is already discovered to be a subloop of this loop, continue. + if (subloop == loopHead) + continue; + + // Yay, it's a subloop of this loop. + blockInfos[subloop->index()].loopHeader = loopHead; + predIt = subloop; + + // Add all predecessors of the subloop header to the worklist, as long as + // those predecessors are not in the current subloop. It might be the case + // that they are in other loops, which we will then add as a subloop to the + // current loop. + for (MIBlock *predIn : predIt->inEdges()) + if (blockInfos[predIn->index()].loopHeader != subloop) + worklist.push_back(predIn); + } else { + if (predIt == loopHead) + continue; + + // This is an undiscovered block. Map it to the current loop. + blockInfos[predIt->index()].loopHeader = loopHead; + + // Add all incoming edges to the worklist. + for (MIBlock *bb : predIt->inEdges()) + worklist.push_back(bb); + } + } +} + +void LoopInfo::dump() const +{ + if (!lcLoopinfo().isDebugEnabled()) + return; + + QString s = QStringLiteral("Loop information:\n"); + for (size_t i = 0, ei = blockInfos.size(); i != ei; ++i) { + const BlockInfo &bi = blockInfos[i]; + s += QStringLiteral(" %1 : is loop header: %2, contained in loop header's loop: ") + .arg(i).arg(bi.isLoopHeader ? QLatin1String("yes") : QLatin1String("no")); + if (bi.loopHeader) + s += QString::number(bi.loopHeader->index()); + else + s += QLatin1String("<none>"); + if (bi.isLoopHeader) { + s += QStringLiteral(", loop exits: "); + if (bi.loopExits.empty()) { + s += QLatin1String("<none>"); + } else { + bool first = true; + for (MIBlock *exit : bi.loopExits) { + if (first) + first = false; + else + s += QStringLiteral(", "); + s += QString::number(exit->index()); + } + } + } + s += QLatin1Char('\n'); + } + qCDebug(lcLoopinfo).noquote().nospace() << s; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4loopinfo_p.h b/src/qml/jit/qv4loopinfo_p.h new file mode 100644 index 0000000000..6a865e6dc6 --- /dev/null +++ b/src/qml/jit/qv4loopinfo_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4LOOPINFO_P_H +#define QV4LOOPINFO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" + +#include <QHash> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree; + +// Detect all (sub-)loops in a function. +// +// Doing loop detection on the CFG is better than relying on the statement information in +// order to mark loops. Although JavaScript only has natural loops, it can still be the case +// that something is not a loop even though a loop-like-statement is in the source. For +// example: +// while (true) { +// if (i > 0) +// break; +// else +// break; +// } +// +// Algorithm: +// - do a DFS on the dominator tree, where for each node: +// - collect all back-edges +// - if there are back-edges, the node is a loop-header for a new loop, so: +// - walk the CFG is reverse-direction, and for every node: +// - if the node already belongs to a loop, we've found a nested loop: +// - get the loop-header for the (outermost) nested loop +// - add that loop-header to the current loop +// - continue by walking all incoming edges that do not yet belong to the current loop +// - if the node does not belong to a loop yet, add it to the current loop, and +// go on with all incoming edges +// +// Loop-header detection by checking for back-edges is very straight forward: a back-edge is +// an incoming edge where the other node is dominated by the current node. Meaning: all +// execution paths that reach that other node have to go through the current node, that other +// node ends with a (conditional) jump back to the loop header. +// +// The exact order of the DFS on the dominator tree is not important. The only property has to +// be that a node is only visited when all the nodes it dominates have been visited before. +// The reason for the DFS is that for nested loops, the inner loop's loop-header is dominated +// by the outer loop's header. So, by visiting depth-first, sub-loops are identified before +// their containing loops, which makes nested-loop identification free. An added benefit is +// that the nodes for those sub-loops are only processed once. +// +// Note: independent loops that share the same header are merged together. For example, in +// the code snippet below, there are 2 back-edges into the loop-header, but only one single +// loop will be detected. +// while (a) { +// if (b) +// continue; +// else +// continue; +// } +class LoopInfo +{ + Q_DISABLE_COPY_MOVE(LoopInfo) + + struct BlockInfo + { + MIBlock *loopHeader = nullptr; + bool isLoopHeader = false; + std::vector<MIBlock *> loopExits; + }; + +public: + LoopInfo(const DominatorTree &dt) + : dt(dt) + {} + + ~LoopInfo() = default; + + void detectLoops(); + + MIBlock *loopHeaderFor(MIBlock *bodyBlock) const + { return blockInfos[bodyBlock->index()].loopHeader; } + + bool isLoopHeader(MIBlock *block) const + { return blockInfos[block->index()].isLoopHeader; } + + const std::vector<MIBlock *> loopExitsForLoop(MIBlock *loopHeader) const + { return blockInfos[loopHeader->index()].loopExits; } + +private: + void subLoop(MIBlock *loopHead, const std::vector<MIBlock *> &backedges); + void collectLoopExits(); + bool inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const; + + void dump() const; + +private: + const DominatorTree &dt; + std::vector<BlockInfo> blockInfos; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4LOOPINFO_P_H diff --git a/src/qml/jit/qv4lowering.cpp b/src/qml/jit/qv4lowering.cpp new file mode 100644 index 0000000000..3b3711e7fa --- /dev/null +++ b/src/qml/jit/qv4lowering.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QLoggingCategory> + +#include "qv4lowering_p.h" +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcLower, "qt.v4.ir.lowering") + +GenericLowering::GenericLowering(Function &f) + : m_function(f) +{} + +void GenericLowering::lower() +{ + NodeWorkList worklist(graph()); + // The order doesn't really matter for generic lowering, as long as it's done in 1 pass, and + // have any clean-up done afterwards. + worklist.enqueueAllInputs(graph()->endNode()); + + while (Node *n = worklist.dequeueNextNodeForVisiting()) { + worklist.enqueueAllInputs(n); + + if (!CallPayload::isRuntimeCall(n->opcode())) + continue; + + if (CallPayload::isVarArgsCall(n->opcode())) + replaceWithVarArgsCall(n); + else + replaceWithCall(n); + } +} + +void GenericLowering::replaceWithCall(Node *n) +{ + auto newOp = opBuilder()->getCall(n->opcode()); + + QVarLengthArray<Node *, 32> args; + if (CallPayload::takesEngineAsArg(n->opcode(), 0)) + args.append(graph()->engineNode()); + if (CallPayload::takesFunctionAsArg(n->opcode(), args.size())) + args.append(graph()->functionNode()); + if (CallPayload::takesFrameAsArg(n->opcode(), args.size())) + args.append(graph()->cppFrameNode()); + const int extraLeadingArguments = args.size(); + + for (unsigned arg = 0, earg = n->inputCount(); arg != earg; ++arg) { + Node *input = n->input(arg); + if (input->opcode() == Meta::FrameState) + continue; + + if (arg >= n->operation()->valueInputCount()) { + // effect or control input + args.append(input); + continue; + } + + if (CallPayload::needsStorageOnJSStack(n->opcode(), args.size(), input->operation(), + function().nodeInfo(input)->type())) + input = graph()->createNode(opBuilder()->get<Meta::Alloca>(), input); + + args.append(input); + } + + Node *newCall = graph()->createNode(newOp, args.data(), args.size()); + + qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() + << "with node" << newCall->id() << newOp->debugString(); + qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); + qCDebug(lcLower) << "... old node #uses:" << n->useCount(); + + function().nodeInfo(newCall)->setType(CallPayload::returnType(n->opcode())); + n->replaceAllUsesWith(newCall); + n->kill(); + + qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); + qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); + + for (Node *use : newCall->uses()) { + // fix-up indices for SelectOutput: + if (use->opcode() == Meta::SelectOutput) { + const int oldIndex = ConstantPayload::get(*use->input(1)->operation())->value().int_32(); + const int newIndex = oldIndex + extraLeadingArguments; + use->replaceInput(1, graph()->createConstantIntNode(newIndex)); + use->replaceInput(2, newCall->input(newIndex)); + break; + } + } +} + +void GenericLowering::replaceWithVarArgsCall(Node *n) +{ + const bool isTailCall = n->opcode() == Meta::JSTailCall; + Operation *newOp = isTailCall ? opBuilder()->getTailCall() + : opBuilder()->getCall(n->opcode()); + + //### optimize this for 0 and 1 argument: we don't need to create a VarArgs array for these cases + + const unsigned varArgsStart = CallPayload::varArgsStart(n->opcode()) - 1; // subtract 1 because the runtime calls all take the engine argument as arg0, which isn't in the graph before lowering. + Node *vaAlloc = graph()->createNode( + opBuilder()->get<Meta::VAAlloc>(), + graph()->createConstantIntNode(n->operation()->valueInputCount() - varArgsStart), + n->effectInput()); + QVarLengthArray<Node *, 32> vaSealIn; + vaSealIn.append(vaAlloc); + for (unsigned i = varArgsStart, ei = n->operation()->valueInputCount(); i != ei; ++i) { + vaSealIn.append(graph()->createNode(opBuilder()->get<Meta::VAStore>(), vaAlloc, + graph()->createConstantIntNode(vaSealIn.size() - 1), + n->input(i))); + } + vaSealIn.append(vaAlloc); + Node *vaSeal = graph()->createNode(opBuilder()->getVASeal(vaSealIn.size() - 2), + vaSealIn.data(), + vaSealIn.size()); + QVarLengthArray<Node *, 8> callArgs; + if (isTailCall) + callArgs.append(graph()->cppFrameNode()); + callArgs.append(graph()->engineNode()); + for (unsigned i = 0; i != varArgsStart; ++i) { + Node *input = n->input(i); + if (CallPayload::needsStorageOnJSStack(n->opcode(), callArgs.size(), input->operation(), + function().nodeInfo(input)->type())) + input = graph()->createNode(opBuilder()->get<Meta::Alloca>(), input); + callArgs.append(input); + } + callArgs.append(vaSeal); // args + if (n->opcode() != Meta::JSCreateClass) // JSCreateClass is the odd duck + callArgs.append(graph()->createConstantIntNode(vaSealIn.size() - 2)); // argc + callArgs.append(vaSeal); // effect + callArgs.append(n->controlInput(0)); // control flow + Node *newCall = graph()->createNode(newOp, callArgs.data(), unsigned(callArgs.size())); + + qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() + << "with node" << newCall->id() << newOp->debugString(); + qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); + qCDebug(lcLower) << "... old node #uses:" << n->useCount(); + + n->replaceAllUsesWith(newCall); + n->kill(); + + qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); + qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); +} + +bool GenericLowering::allUsesAsUnboxedBool(Node *n) +{ + for (Node *use : n->uses()) { + if (use->operation()->kind() != Meta::Branch) + return false; + } + + return true; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4jithelpers_p.h b/src/qml/jit/qv4lowering_p.h index d9abfc071e..0b482bc9f0 100644 --- a/src/qml/jit/qv4jithelpers_p.h +++ b/src/qml/jit/qv4lowering_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef TEMPLATE_H -#define TEMPLATE_H +#ifndef QV4LOWERING_P_H +#define QV4LOWERING_P_H // // W A R N I N G @@ -51,42 +51,57 @@ // We mean it. // +#include <private/qqmljsmemorypool_p.h> #include <private/qv4global_p.h> +#include <private/qv4ir_p.h> +#include <private/qv4util_p.h> +#include <private/qv4node_p.h> +#include <private/qv4graph_p.h> -//QT_REQUIRE_CONFIG(qml_jit); +QT_REQUIRE_CONFIG(qml_tracing); QT_BEGIN_NAMESPACE namespace QV4 { +namespace IR { -#ifdef V4_ENABLE_JIT +// Lowering replaces JS level operations with lower level ones. E.g. a JSAdd is lowered to an AddI32 +// if both inputs and the output are 32bit integers, or to a runtime call in all other cases. This +// transforms the graph into something that is closer to actual executable code. -namespace JIT { -namespace Helpers { -void convertThisToObject(ExecutionEngine *engine, Value *t); -ReturnedValue loadGlobalLookup(Function *f, ExecutionEngine *engine, int index); -ReturnedValue loadQmlContextPropertyLookup(Function *f, ExecutionEngine *engine, int index); -ReturnedValue toObject(ExecutionEngine *engine, const Value &obj); -ReturnedValue exp(const Value &base, const Value &exp); -ReturnedValue getLookup(Function *f, ExecutionEngine *engine, const Value &base, int index); -void setLookupStrict(Function *f, int index, Value &base, const Value &value); -void setLookupSloppy(Function *f, int index, Value &base, const Value &value); -void pushBlockContext(Value *stack, int index); -void cloneBlockContext(Value *contextSlot); -void pushScriptContext(Value *stack, ExecutionEngine *engine, int index); -void popScriptContext(Value *stack, ExecutionEngine *engine); -ReturnedValue deleteProperty(QV4::Function *function, const QV4::Value &base, const QV4::Value &index); -ReturnedValue deleteName(Function *function, int name); -void throwOnNullOrUndefined(ExecutionEngine *engine, const Value &v); +// Last lowering phase: replace all JSOperations that are left with runtime calls. There is nothing +// smart here, all that should have been done before this phase. +class GenericLowering final +{ + Q_DISABLE_COPY(GenericLowering) -} // Helpers namespace -} // JIT namespace +public: + GenericLowering(Function &f); -#endif // V4_ENABLE_JIT + void lower(); -} // QV4 namespace +private: + void replaceWithCall(Node *n); + void replaceWithVarArgsCall(Node *n); + static bool allUsesAsUnboxedBool(Node *n); + + Function &function() + { return m_function; } + + Graph *graph() + { return function().graph(); } + + OperationBuilder *opBuilder() + { return graph()->opBuilder(); } + +private: + Function &m_function; +}; + +} // namespace IR +} // namespace QV4 QT_END_NAMESPACE -#endif // TEMPLATE_H +#endif // QV4LOWERING_P_H diff --git a/src/qml/jit/qv4mi.cpp b/src/qml/jit/qv4mi.cpp new file mode 100644 index 0000000000..f0b172243d --- /dev/null +++ b/src/qml/jit/qv4mi.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> +#include <private/qqmlglobal_p.h> + +#include "qv4mi_p.h" +#include "qv4node_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcMI, "qt.v4.ir.mi") + +QString MIOperand::debugString() const +{ + switch (kind()) { + case Invalid: return QStringLiteral("<<INVALID>>"); + case Constant: return ConstantPayload::debugString(constantValue()); + case VirtualRegister: return QStringLiteral("vreg%1").arg(virtualRegister()); + case EngineRegister: return QStringLiteral("engine"); + case CppFrameRegister: return QStringLiteral("cppFrame"); + case Function: return QStringLiteral("function"); + case JSStackSlot: return QStringLiteral("jsstack[%1]").arg(stackSlot()); + case BoolStackSlot: return QStringLiteral("bstack[%1]").arg(stackSlot()); + case JumpTarget: return targetBlock() ? QStringLiteral("L%1").arg(targetBlock()->index()) + : QStringLiteral("<<INVALID BLOCK>>"); + default: Q_UNREACHABLE(); + } +} + +MIInstr *MIInstr::create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands) +{ + return pool->New<MIInstr>(irNode, pool, nOperands); +} + +static QString commentIndent(const QString &line) +{ + int spacing = std::max(70 - line.length(), 1); + return line + QString(spacing, QLatin1Char(' ')); +} + +static QString indent(int nr) +{ + QString s = nr == -1 ? QString() : QString::number(nr); + int padding = 6 - s.size(); + if (padding > 0) + s = QString(padding, QLatin1Char(' ')) + s; + return s; +} + +MIFunction::MIFunction(Function *irFunction) + : m_irFunction(irFunction) +{} + +void MIFunction::renumberBlocks() +{ + for (size_t i = 0, ei = m_blocks.size(); i != ei; ++i) { + MIBlock *b = m_blocks[i]; + b->setIndex(unsigned(i)); + } +} + +void MIFunction::renumberInstructions() +{ + int pos = 0; + for (MIBlock *b : m_blocks) { + for (MIInstr &instr : b->instructions()) { + pos += 2; + instr.setPosition(pos); + } + } +} + +void MIFunction::dump(const QString &description) const +{ + if (!lcMI().isDebugEnabled()) + return; + + QString s = description + QLatin1String(":\n"); + QString name; + if (auto n = irFunction()->v4Function()->name()) + name = n->toQString(); + if (name.isEmpty()) + QString::asprintf("%p", static_cast<void *>(irFunction()->v4Function())); + QString line = QStringLiteral("function %1 {").arg(name); + auto loc = irFunction()->v4Function()->sourceLocation(); + s += commentIndent(line) + QStringLiteral("; %1:%2:%3\n").arg(loc.sourceFile, + QString::number(loc.line), + QString::number(loc.column)); + for (const MIBlock *b : blocks()) { + line = QStringLiteral("L%1").arg(b->index()); + bool first = true; + if (!b->arguments().empty()) { + line += QLatin1Char('('); + for (const MIOperand &arg : b->arguments()) { + if (first) + first = false; + else + line += QStringLiteral(", "); + line += arg.debugString(); + } + line += QLatin1Char(')'); + } + line += QLatin1Char(':'); + line = commentIndent(line) + QStringLiteral("; preds: "); + if (b->inEdges().isEmpty()) { + line += QStringLiteral("<none>"); + } else { + bool first = true; + for (MIBlock *in : b->inEdges()) { + if (first) + first = false; + else + line += QStringLiteral(", "); + line += QStringLiteral("L%1").arg(in->index()); + } + } + s += line + QLatin1Char('\n'); + for (const MIInstr &i : b->instructions()) { + line = indent(i.position()) + QLatin1String(": "); + if (i.hasDestination()) + line += i.destination().debugString() + QStringLiteral(" = "); + line += i.irNode()->operation()->debugString(); + bool first = true; + for (const MIOperand &op : i.operands()) { + if (first) + first = false; + else + line += QLatin1Char(','); + line += QLatin1Char(' ') + op.debugString(); + } + line = commentIndent(line) + QStringLiteral("; node-id: %1").arg(i.irNode()->id()); + if (i.irNode()->operation()->needsBytecodeOffsets()) + line += QStringLiteral(", bytecode-offset: %1").arg(irFunction()->nodeInfo(i.irNode())->currentInstructionOffset()); + s += line + QLatin1Char('\n'); + } + s += commentIndent(QString()) + QStringLiteral("; succs: "); + if (b->outEdges().isEmpty()) { + s += QStringLiteral("<none>"); + } else { + bool first = true; + for (MIBlock *succ : b->outEdges()) { + if (first) + first = false; + else + s += QStringLiteral(", "); + s += QStringLiteral("L%1").arg(succ->index()); + } + } + s += QLatin1Char('\n'); + } + s += QLatin1Char('}'); + + for (const QStringRef &line : s.splitRef('\n')) + qCDebug(lcMI).noquote().nospace() << line; +} + +unsigned MIFunction::extraJSSlots() const +{ + uint interpreterFrameSize = CppStackFrame::requiredJSStackFrameSize(irFunction()->v4Function()); + if (m_jsSlotCount <= interpreterFrameSize) + return 0; + return m_jsSlotCount - interpreterFrameSize; +} + +void MIFunction::setStartBlock(MIBlock *newStartBlock) +{ + auto it = std::find(m_blocks.begin(), m_blocks.end(), newStartBlock); + Q_ASSERT(it != m_blocks.end()); + std::swap(*m_blocks.begin(), *it); +} + +void MIFunction::setStackSlotCounts(unsigned dword, unsigned qword, unsigned js) +{ + m_vregCount = 0; + m_dwordSlotCount = dword; + m_qwordSlotCount = qword; + m_jsSlotCount = js; +} + +void MIFunction::verifyCFG() const +{ + if (block(MIFunction::StartBlockIndex)->instructions().front().opcode() != Meta::Start) + qFatal("MIFunction block 0 is not the start block"); + + for (MIBlock *b : m_blocks) { + for (MIBlock *in : b->inEdges()) { + if (!in->outEdges().contains(b)) + qFatal("block %u has incoming edge from block %u, " + "but does not appear in that block's outgoing edges", + b->index(), in->index()); + } + for (MIBlock *out : b->outEdges()) { + if (!out->inEdges().contains(b)) + qFatal("block %u has outgoing edge from block %u, " + "but does not appear in that block's incoming edges", + b->index(), out->index()); + } + } +} + +MIBlock *MIBlock::findEdgeTo(Operation::Kind target) const +{ + for (MIBlock *outEdge : outEdges()) { + if (outEdge->instructions().front().opcode() == target) + return outEdge; + } + return nullptr; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4mi_p.h b/src/qml/jit/qv4mi_p.h new file mode 100644 index 0000000000..f976d1dc94 --- /dev/null +++ b/src/qml/jit/qv4mi_p.h @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MI_P_H +#define QV4MI_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4global_p.h> +#include <private/qv4ir_p.h> +#include <private/qv4node_p.h> +#include <private/qv4operation_p.h> + +#include <llvm/ADT/iterator.h> +#include <llvm/ADT/iterator_range.h> +#include <llvm/ADT/ilist.h> +#include <llvm/ADT/ilist_node.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// This file contains the Machine Interface (MI) data structures, on which ultimately the assembler +// will operate: + +class MIFunction; // containing all basic blocks, and a reference to the IR function + +class MIBlock; // containing an ordered sequence of instructions + +class MIInstr; // containing operands, and a reference to the IR node, that indicates which + // operation is represented by an instruction + +class MIOperand; // contains a description of where to get/put the input/result of an operation + +// A detail about the stack slots: there two stacks, the JS stack and the native stack. A frame on +// the native stack is divided in two parts: the quad-word part and the double-word part. The +// qword part holds 64bit values, like doubles, and pointers on 64bit architectures. The dword part +// holds 32bit values, like int32s, booleans, and pointers on 32bit architectures. We need to know +// the type of value a slot holds, because if we have to move it to the JS stack, we have to box it +// correctly. +class MIOperand final +{ +public: + enum Kind { + Invalid = 0, + Constant, + VirtualRegister, + + EngineRegister, + CppFrameRegister, + Function, + + JSStackSlot, + BoolStackSlot, + + JumpTarget, + }; + + using List = QQmlJS::FixedPoolArray<MIOperand>; + +public: + MIOperand() = default; + + static MIOperand createConstant(Node *irNode) + { + MIOperand op; + op.m_kind = Constant; + op.m_irNode = irNode; + return op; + } + + static MIOperand createVirtualRegister(Node *irNode, unsigned vreg) + { + MIOperand op; + op.m_kind = VirtualRegister; + op.m_irNode = irNode; + op.m_vreg = vreg; + return op; + } + + static MIOperand createEngineRegister(Node *irNode) + { + MIOperand op; + op.m_kind = EngineRegister; + op.m_irNode = irNode; + return op; + } + + static MIOperand createCppFrameRegister(Node *irNode) + { + MIOperand op; + op.m_kind = CppFrameRegister; + op.m_irNode = irNode; + return op; + } + + static MIOperand createFunction(Node *irNode) + { + MIOperand op; + op.m_kind = Function; + op.m_irNode = irNode; + return op; + } + + static MIOperand createJSStackSlot(Node *irNode, unsigned slot) + { + MIOperand op; + op.m_kind = JSStackSlot; + op.m_irNode = irNode; + op.m_slot = slot; + return op; + } + + static MIOperand createBoolStackSlot(Node *irNode, unsigned slot) + { + MIOperand op; + op.m_kind = BoolStackSlot; + op.m_irNode = irNode; + op.m_slot = slot; + return op; + } + + //### or name this createDeoptBlock? + static MIOperand createJumpTarget(Node *irNode, MIBlock *targetBlock) + { + MIOperand op; + op.m_kind = JumpTarget; + op.m_irNode = irNode; + op.m_targetBlock = targetBlock; + return op; + } + + Kind kind() const + { return m_kind; } + + bool isValid() const + { return m_kind != Invalid; } + + bool isConstant() const + { return m_kind == Constant; } + + bool isVirtualRegister() const + { return kind() == VirtualRegister; } + + bool isEngineRegister() const + { return kind() == EngineRegister; } + + bool isCppFrameRegister() const + { return kind() == CppFrameRegister; } + + bool isFunction() const + { return kind() == Function; } + + bool isJSStackSlot() const + { return kind() == JSStackSlot; } + + bool isBoolStackSlot() const + { return kind() == BoolStackSlot; } + + bool isStackSlot() const + { return isJSStackSlot() || isDWordSlot() || isQWordSlot(); } + + bool isJumpTarget() const + { return kind() == JumpTarget; } + + Node *irNode() const + { return m_irNode; } + + inline Type nodeType(MIFunction *f) const; + + QString debugString() const; + + QV4::Value constantValue() const + { + Q_ASSERT(isConstant()); + if (irNode()->opcode() == Meta::Undefined) + return QV4::Value::undefinedValue(); + if (irNode()->opcode() == Meta::Empty) + return QV4::Value::emptyValue(); + return ConstantPayload::get(*irNode()->operation())->value(); + } + + unsigned virtualRegister() const + { Q_ASSERT(isVirtualRegister()); return m_vreg; } + + unsigned stackSlot() const + { Q_ASSERT(isStackSlot()); return m_slot; } + + MIBlock *targetBlock() const + { Q_ASSERT(isJumpTarget()); return m_targetBlock; } + + inline bool operator==(const MIOperand &other) const + { + if (kind() != other.kind()) + return false; + + if (isStackSlot()) + return stackSlot() == other.stackSlot(); + + switch (kind()) { + case MIOperand::Invalid: + return !other.isValid(); + case MIOperand::Constant: + return constantValue().asReturnedValue() == other.constantValue().asReturnedValue(); + case MIOperand::VirtualRegister: + return virtualRegister() == other.virtualRegister(); + case MIOperand::JumpTarget: + return targetBlock() == other.targetBlock(); + default: + Q_UNREACHABLE(); + return false; + } + } + + bool isDWordSlot() const + { + switch (kind()) { + case BoolStackSlot: + return true; + default: + return false; + } + } + + bool isQWordSlot() const + { + switch (kind()) { + //### TODO: double slots + default: + return false; + } + } + + bool overlaps(const MIOperand &other) const + { + if ((isDWordSlot() && other.isDWordSlot()) || (isQWordSlot() && other.isQWordSlot())) + ; // fine, these are the same + else if (kind() != other.kind()) + return false; + + if (isStackSlot()) + return stackSlot() == other.stackSlot(); + + return false; + } + +private: + Node *m_irNode = nullptr; + union { + unsigned m_vreg; + unsigned m_slot; + MIBlock *m_targetBlock = nullptr; + }; + Kind m_kind = Invalid; +}; + +template <typename NodeTy> struct MIInstrListParentType {}; +template <> struct MIInstrListParentType<MIInstr> { using type = MIBlock; }; + +template <typename NodeTy> class MIInstrList; + +template <typename MISubClass> +class MIInstrListTraits : public llvm::ilist_noalloc_traits<MISubClass> +{ +protected: + using ListTy = MIInstrList<MISubClass>; + using iterator = typename llvm::simple_ilist<MISubClass>::iterator; + using ItemParentClass = typename MIInstrListParentType<MISubClass>::type; + +public: + MIInstrListTraits() = default; + +protected: + void setListOwner(ItemParentClass *listOwner) + { m_owner = listOwner; } + +private: + ItemParentClass *m_owner = nullptr; + + /// getListOwner - Return the object that owns this list. If this is a list + /// of instructions, it returns the BasicBlock that owns them. + ItemParentClass *getListOwner() const { + return m_owner; + } + + static ListTy &getList(ItemParentClass *Par) { + return Par->*(Par->getSublistAccess()); + } + + static MIInstrListTraits<MISubClass> *getSymTab(ItemParentClass *Par) { + return Par ? toPtr(Par->getValueSymbolTable()) : nullptr; + } + +public: + void addNodeToList(MISubClass *V) { V->setParent(getListOwner()); } + void removeNodeFromList(MISubClass *V) { V->setParent(nullptr); } + void transferNodesFromList(MIInstrListTraits &L2, iterator first, + iterator last); +}; + +template <class T> +class MIInstrList: public llvm::iplist_impl<llvm::simple_ilist<T>, MIInstrListTraits<T>> +{ +public: + MIInstrList(typename MIInstrListTraits<T>::ItemParentClass *owner) + { this->setListOwner(owner); } +}; + +class MIInstr final : public llvm::ilist_node_with_parent<MIInstr, MIBlock> +{ + Q_DISABLE_COPY_MOVE(MIInstr) // heap use only! + +protected: + friend class QQmlJS::MemoryPool; + MIInstr() : m_operands(nullptr, 0) {} + + explicit MIInstr(Node *irNode, QQmlJS::MemoryPool *pool, unsigned nOperands) + : m_irNode(irNode) + , m_operands(pool, nOperands) + {} + + ~MIInstr() = default; + +public: + static MIInstr *create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands); + + MIBlock *parent() const + { return m_parent; } + + MIBlock *getParent() const // for ilist_node_with_parent + { return parent(); } + + void setParent(MIBlock *parent) + { m_parent = parent; } + + Node *irNode() const + { return m_irNode; } + + Operation::Kind opcode() const + { return m_irNode->opcode(); } + + int position() const + { return m_position; } + + inline void insertBefore(MIInstr *insertPos); + inline void insertAfter(MIInstr *insertPos); + inline MIInstrList<MIInstr>::iterator eraseFromParent(); + + bool hasDestination() const + { return m_destination.isValid(); } + + MIOperand destination() const + { return m_destination; } + + void setDestination(const MIOperand &dest) + { m_destination = dest; } + + const MIOperand &operand(unsigned index) const + { return m_operands.at(index); } + + void setOperand(unsigned index, const MIOperand &op) + { m_operands.at(index) = op; } + + MIOperand &operand(unsigned index) + { return m_operands.at(index); } + + const MIOperand::List &operands() const + { return m_operands; } + + MIOperand::List &operands() + { return m_operands; } + +private: + friend MIFunction; + void setPosition(int newPosition) + { m_position = newPosition; } + +private: + MIBlock *m_parent = nullptr; + Node *m_irNode = nullptr; + int m_position = -1; + MIOperand m_destination; + MIOperand::List m_operands; +}; + +class MIBlock final +{ + Q_DISABLE_COPY_MOVE(MIBlock) + +public: + using Index = unsigned; + enum : Index { InvalidIndex = std::numeric_limits<Index>::max() }; + + using MIInstructionList = MIInstrList<MIInstr>; + + using InEdges = QVarLengthArray<MIBlock *, 4>; + using OutEdges = QVarLengthArray<MIBlock *, 2>; + +protected: + friend MIFunction; + explicit MIBlock(Index index) + : m_instructions(this), + m_index(index) + {} + + void setIndex(Index newIndex) + { m_index = newIndex; } + +public: + ~MIBlock() = default; + + const MIInstructionList &instructions() const + { return m_instructions; } + + MIInstructionList &instructions() + { return m_instructions; } + + static MIInstructionList MIBlock::*getSublistAccess(MIInstr * = nullptr) + { return &MIBlock::m_instructions; } + + void addArgument(MIOperand &&arg) + { m_arguments.push_back(arg); } + + const std::vector<MIOperand> &arguments() const + { return m_arguments; } + + std::vector<MIOperand> &arguments() + { return m_arguments; } + + void clearArguments() + { m_arguments.resize(0); } + + const InEdges &inEdges() const + { return m_inEdges; } + + void addInEdge(MIBlock *edge) + { m_inEdges.append(edge); } + + const OutEdges &outEdges() const + { return m_outEdges; } + + void addOutEdge(MIBlock *edge) + { m_outEdges.append(edge); } + + Index index() const + { return m_index; } + + MIBlock *findEdgeTo(Operation::Kind target) const; + + bool isDeoptBlock() const + { return m_isDeoptBlock; } + + void markAsDeoptBlock() + { m_isDeoptBlock = true; } + +private: + std::vector<MIOperand> m_arguments; + MIInstructionList m_instructions; + InEdges m_inEdges; + OutEdges m_outEdges; + Index m_index; + bool m_isDeoptBlock = false; +}; + +class MIFunction final +{ + Q_DISABLE_COPY_MOVE(MIFunction) + +public: + static constexpr MIBlock::Index StartBlockIndex = 0; + +public: + MIFunction(Function *irFunction); + ~MIFunction() + { qDeleteAll(m_blocks); } + + Function *irFunction() const + { return m_irFunction; } + + void setStartBlock(MIBlock *newStartBlock); + void renumberBlocks(); + void renumberInstructions(); + + void dump(const QString &description) const; + + size_t blockCount() const + { return blocks().size(); } + + MIBlock *block(MIBlock::Index index) const + { return m_blocks[index]; } + + const std::vector<MIBlock *> &blocks() const + { return m_blocks; } + + MIBlock *addBlock() + { + auto *b = new MIBlock(unsigned(m_blocks.size())); + m_blocks.push_back(b); + return b; + } + + void setBlockOrder(const std::vector<MIBlock *> &newSequence) + { m_blocks = newSequence; } + + unsigned vregCount() const + { return m_vregCount; } + + void setVregCount(unsigned vregCount) + { m_vregCount = vregCount; } + + unsigned dwordSlotCount() const + { return m_dwordSlotCount; } + + unsigned qwordSlotCount() const + { return m_qwordSlotCount; } + + unsigned jsSlotCount() const + { return m_jsSlotCount; } + + unsigned extraJSSlots() const; + + void setStackSlotCounts(unsigned dword, unsigned qword, unsigned js); + + void verifyCFG() const; + +private: + Function *m_irFunction = nullptr; + std::vector<MIBlock *> m_blocks; + unsigned m_vregCount = 0; + unsigned m_dwordSlotCount = 0; + unsigned m_qwordSlotCount = 0; + unsigned m_jsSlotCount = 0; +}; + +Type MIOperand::nodeType(MIFunction *f) const +{ + return f->irFunction()->nodeInfo(irNode())->type(); +} + +inline uint qHash(const MIOperand &key, uint seed) +{ + uint h = ::qHash(key.kind(), seed); + switch (key.kind()) { + case MIOperand::VirtualRegister: + h ^= key.virtualRegister(); + break; + case MIOperand::BoolStackSlot: Q_FALLTHROUGH(); + case MIOperand::JSStackSlot: + h ^= key.stackSlot(); + break; + default: + qFatal("%s: cannot hash %s", Q_FUNC_INFO, key.debugString().toUtf8().constData()); + } + return h; +} + +void MIInstr::insertBefore(MIInstr *insertPos) +{ + insertPos->parent()->instructions().insert(insertPos->getIterator(), this); +} + +void MIInstr::insertAfter(MIInstr *insertPos) +{ + insertPos->parent()->instructions().insert(++insertPos->getIterator(), this); +} + +MIInstrList<MIInstr>::iterator MIInstr::eraseFromParent() +{ + auto p = parent(); + setParent(nullptr); + return p->instructions().erase(getIterator()); +} + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4MI_P_H diff --git a/src/qml/jit/qv4miblockset_p.h b/src/qml/jit/qv4miblockset_p.h new file mode 100644 index 0000000000..5f814b99e0 --- /dev/null +++ b/src/qml/jit/qv4miblockset_p.h @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MIBLOCKSET_P_H +#define QV4MIBLOCKSET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class MIBlockSet +{ + using Flags = BitVector; + + QVarLengthArray<MIBlock::Index, 8> blockNumbers; + Flags *blockFlags = nullptr; + MIFunction *function = nullptr; + enum { MaxVectorCapacity = 8 }; + +public: + class const_iterator; + friend class const_iterator; + +public: + MIBlockSet(MIFunction *f = nullptr) + { + if (f) + init(f); + } + + MIBlockSet(MIBlockSet &&other) noexcept + { + std::swap(blockNumbers, other.blockNumbers); + std::swap(blockFlags, other.blockFlags); + std::swap(function, other.function); + } + + MIBlockSet(const MIBlockSet &other) + : function(other.function) + { + if (other.blockFlags) + blockFlags = new Flags(*other.blockFlags); + blockNumbers = other.blockNumbers; + } + + MIBlockSet &operator=(const MIBlockSet &other) + { + if (blockFlags) { + delete blockFlags; + blockFlags = nullptr; + } + function = other.function; + if (other.blockFlags) + blockFlags = new Flags(*other.blockFlags); + blockNumbers = other.blockNumbers; + return *this; + } + + MIBlockSet &operator=(MIBlockSet &&other) noexcept + { + if (&other != this) { + std::swap(blockNumbers, other.blockNumbers); + + delete blockFlags; + blockFlags = other.blockFlags; + other.blockFlags = nullptr; + + function = other.function; + } + return *this; + } + + ~MIBlockSet() + { + delete blockFlags; + } + + void init(MIFunction *f) + { + Q_ASSERT(!function); + Q_ASSERT(f); + function = f; + } + + bool empty() const; + + void insert(MIBlock *bb) + { + Q_ASSERT(function); + + if (blockFlags) { + blockFlags->setBit(bb->index()); + return; + } + + for (unsigned int blockNumber : qAsConst(blockNumbers)) { + if (blockNumber == bb->index()) + return; + } + + if (blockNumbers.size() == MaxVectorCapacity) { + blockFlags = new Flags(int(function->blockCount()), false); + for (unsigned int blockNumber : qAsConst(blockNumbers)) { + blockFlags->setBit(int(blockNumber)); + } + blockNumbers.clear(); + blockFlags->setBit(int(bb->index())); + } else { + blockNumbers.append(bb->index()); + } + } + + void remove(MIBlock *bb) + { + Q_ASSERT(function); + + if (blockFlags) { + blockFlags->clearBit(bb->index()); + return; + } + + for (int i = 0; i < blockNumbers.size(); ++i) { + if (blockNumbers[i] == bb->index()) { + blockNumbers.remove(i); + return; + } + } + } + + const_iterator begin() const; + const_iterator end() const; + + void collectValues(std::vector<MIBlock *> &bbs) const; + + bool contains(MIBlock *bb) const + { + Q_ASSERT(function); + + if (blockFlags) + return blockFlags->at(bb->index()); + + for (unsigned int blockNumber : blockNumbers) { + if (blockNumber == bb->index()) + return true; + } + + return false; + } +}; + +class MIBlockSet::const_iterator +{ + const MIBlockSet &set; + // ### These two members could go into a union, but clang won't compile + // (https://codereview.qt-project.org/#change,74259) + QVarLengthArray<MIBlock::Index, 8>::const_iterator numberIt; + MIBlock::Index flagIt; + + friend class MIBlockSet; + const_iterator(const MIBlockSet &set, bool end) + : set(set) + { + if (end || !set.function) { + if (!set.blockFlags) + numberIt = set.blockNumbers.end(); + else + flagIt = set.blockFlags->size(); + } else { + if (!set.blockFlags) + numberIt = set.blockNumbers.begin(); + else + findNextWithFlags(0); + } + } + + void findNextWithFlags(int start) + { + flagIt = MIBlock::Index(set.blockFlags->findNext(start, true, /*wrapAround = */false)); + Q_ASSERT(flagIt <= MIBlock::Index(set.blockFlags->size())); + } + +public: + MIBlock *operator*() const + { + if (!set.blockFlags) + return set.function->block(*numberIt); + + Q_ASSERT(flagIt <= set.function->blockCount()); + return set.function->block(flagIt); + + } + + bool operator==(const const_iterator &other) const + { + if (&set != &other.set) + return false; + if (!set.blockFlags) + return numberIt == other.numberIt; + return flagIt == other.flagIt; + } + + bool operator!=(const const_iterator &other) const + { return !(*this == other); } + + const_iterator &operator++() + { + if (!set.blockFlags) + ++numberIt; + else + findNextWithFlags(flagIt + 1); + + return *this; + } +}; + +inline bool MIBlockSet::empty() const +{ return begin() == end(); } + +inline MIBlockSet::const_iterator MIBlockSet::begin() const +{ return const_iterator(*this, false); } + +inline MIBlockSet::const_iterator MIBlockSet::end() const +{ return const_iterator(*this, true); } + +inline void MIBlockSet::collectValues(std::vector<MIBlock *> &bbs) const +{ + Q_ASSERT(function); + + for (auto it : *this) + bbs.push_back(it); +} + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4MIBLOCKSET_P_H diff --git a/src/qml/jit/qv4node.cpp b/src/qml/jit/qv4node.cpp new file mode 100644 index 0000000000..e059e9fef6 --- /dev/null +++ b/src/qml/jit/qv4node.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4node_p.h" +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Node *Node::create(Node::MemoryPool *pool, Node::Id id, const Operation *op, size_t nInputs, + Node *const *inputs, bool inputsAreExtensible) +{ + size_t capacity = nInputs; + if (inputsAreExtensible) + capacity += 3; + + Node *node = new (pool->allocate(sizeof(Node))) Node(pool, id, op, unsigned(nInputs), + int(capacity)); + for (uint i = 0; i < capacity; ++i) + new (&node->m_inputs[int(i)]) Use(node); + for (size_t i = 0; i < nInputs; ++i) { + Q_ASSERT(inputs[i] != nullptr); + node->replaceInput(unsigned(i), inputs[i]); + } + + return node; +} + +void Node::addInput(MemoryPool *pool, Node *in) +{ + Q_ASSERT(in); + ++m_nInputs; + if (m_nInputs >= unsigned(m_inputs.size())) { + QQmlJS::FixedPoolArray<Use> oldInputs = m_inputs; + m_inputs = QQmlJS::FixedPoolArray<Use>(pool, int(m_nInputs + 3)); + for (Use &input : m_inputs) + new (&input) Use(this); + for (int i = 0, ei = oldInputs.size(); i != ei; ++i) { + Node *in = oldInputs[i].m_input; + oldInputs[i].set(nullptr); + m_inputs[i].set(in); + } + } + m_inputs.at(int(m_nInputs - 1)).set(in); +} + +void Node::removeInput(unsigned index) +{ + Q_ASSERT(index < inputCount()); + for (unsigned i = index, ei = inputCount(); i < ei - 1; ++i) + replaceInput(i, input(i + 1)); + trimInputCount(inputCount() - 1); +} + +void Node::removeInputs(unsigned start, unsigned count) +{ + for (unsigned idx = start; idx < start + count; ++idx) + m_inputs.at(int(idx)).set(nullptr); +} + +void Node::removeAllInputs() +{ + removeInputs(0, inputCount()); +} + +void Node::trimInputCount(unsigned newCount) +{ + unsigned currentCount = inputCount(); + if (newCount == currentCount) + return; + Q_ASSERT(newCount < currentCount); + removeInputs(newCount, currentCount - newCount); + m_nInputs = newCount; +} + +void Node::removeExceptionHandlerUse() +{ + for (Use* use = m_firstUse; use; use = use->m_next) { + if (use->m_input->opcode() == Meta::OnException) { + use->set(nullptr); + break; + } + } +} + +void Node::insertInput(Node::MemoryPool *pool, unsigned index, Node *newInput) +{ + Q_ASSERT(index < inputCount()); + addInput(pool, input(inputCount() - 1)); + for (unsigned i = inputCount() - 1; i > index; --i) + replaceInput(i, input(i - 1)); + replaceInput(index, newInput); +} + +void Node::replaceAllUsesWith(Node *replacement) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const unsigned inIdx = use->inputIndex(); + use->user()->replaceInput(inIdx, replacement); + use = next; + } +} + +void Node::replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const Operation *inOp = use->user()->operation(); + const unsigned inIdx = use->inputIndex(); + if (inIdx < inOp->valueInputCount()) + use->user()->replaceInput(inIdx, newValueInput); + else if (inIdx < inOp->indexOfFirstControl()) + use->user()->replaceInput(inIdx, newEffectInput); + else + use->user()->replaceInput(inIdx, newControlInput); + use = next; + } +} + +Node *Node::firstValueUse() +{ + for (auto it = uses().begin(), eit = uses().end(); it != eit; ++it) { + if (it.isUsedAsValue()) + return *it; + } + return nullptr; +} + +Node::Node(MemoryPool *pool, Node::Id id, const Operation *op, unsigned nInputs, int capacity) + : m_op(op) + , m_inputs(pool, capacity) + , m_nInputs(nInputs) + , m_id(id) +{ +} + +NodeWorkList::NodeWorkList(const Graph *g) + : m_nodeState(g->nodeCount(), Unvisited) +{ m_worklist.reserve(64); } + +void NodeWorkList::reset() +{ + std::fill(m_nodeState.begin(), m_nodeState.end(), Unvisited); + m_worklist.clear(); + if (m_worklist.capacity() < 64) + m_worklist.reserve(64); +} + +NodeCollector::NodeCollector(const Graph *g, bool collectUses, bool skipFramestate) +{ + markReachable(g->endNode()); + for (size_t i = 0; i < m_reachable.size(); ++i) { // _reachable.size() is on purpose! + Node *n = m_reachable.at(i); + for (auto input : n->inputs()) { + if (input == nullptr) + continue; + if (isReachable(input->id())) + continue; + if (skipFramestate && input->opcode() == Meta::FrameState) + continue; + markReachable(input); + } + + if (collectUses) { + for (Node *use : n->uses()) { + if (use && !isReachable(use->id())) + markReachable(use); + } + } + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4node_p.h b/src/qml/jit/qv4node_p.h new file mode 100644 index 0000000000..76065fb1bc --- /dev/null +++ b/src/qml/jit/qv4node_p.h @@ -0,0 +1,626 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4NODE_P_H +#define QV4NODE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4operation_p.h> +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Use +{ + Q_DISABLE_COPY_MOVE(Use) + +public: + Use(Node *user) + : m_user(user) + {} + + ~Use() + { + if (m_input) + removeFromList(); + } + + operator Node *() const { return m_input; } + Node *input() const { return m_input; } + Node *user() const { return m_user; } + + inline void set(Node *newInput); + + void validate() const + { + Q_ASSERT(m_user); + if (m_input) { + if (m_prev != nullptr) + Q_ASSERT(*m_prev == this); + if (m_next) { + Q_ASSERT(m_next->m_input == m_input); + Q_ASSERT(m_next->m_prev == &m_next); + m_next->validate(); + } + } + } + + inline int inputIndex() const; + +protected: + friend class Node; + + void addToList(Use **list) { + validate(); + m_next = *list; + if (m_next) + m_next->m_prev = &m_next; + m_prev = list; + *list = this; + validate(); + } + + void removeFromList() { + validate(); + Use **newPrev = m_prev; + *newPrev = m_next; + m_prev = nullptr; + if (m_next) + m_next->m_prev = newPrev; + m_next = nullptr; + m_input = nullptr; + validate(); + } + +private: + Node *m_input = nullptr; + Node *m_user = nullptr; + Use *m_next = nullptr; + Use **m_prev = nullptr; +}; + +// A node represents an calculation, action, or marker in the graph. Each node has an operation, +// input dependencies and uses. The operation indicates what kind of node it is, e.g.: JSAdd, +// Constant, Region, and so on. Two nodes can have the same operation, but different inputs. +// For example, the expressions 1 + 2 and 3 + 4 will each have a node with an JSAdd operation +// (which is exactly the same operation for both nodes), but the nodes have different inputs (1, and +// 2 in the first expression, while the second operation has 3 and 4 as inputs). +class Node final +{ + Q_DISABLE_COPY_MOVE(Node) + +public: + using Id = uint32_t; + using MemoryPool = QQmlJS::MemoryPool; + class Inputs; + +public: + static Node *create(MemoryPool *pool, Id id, const Operation *op, size_t nInputs, + Node * const *inputs, bool inputsAreExtensible = false); + ~Node() = delete; + + inline bool isDead() const; + inline void kill(); + + Id id() const { return m_id; } + + const Operation *operation() const + { return m_op; } + + void setOperation(const Operation *op) + { m_op = op; } + + Operation::Kind opcode() const + { return operation()->kind(); } + + inline Inputs inputs() const; + void addInput(MemoryPool *pool, Node *in); + void removeInput(unsigned index); + void removeInputs(unsigned start, unsigned count); + void removeAllInputs(); + uint32_t inputCount() const + { return m_nInputs; } + void trimInputCount(unsigned newCount); + + void removeExceptionHandlerUse(); + + Node *input(unsigned idx) const + { + Q_ASSERT(idx < inputCount()); + return m_inputs.at(idx); + } + + Node *effectInput(unsigned effectIndex = 0) const + { + if (operation()->effectInputCount() == 0) + return nullptr; + Q_ASSERT(effectIndex < operation()->effectInputCount()); + return input(operation()->indexOfFirstEffect() + effectIndex); + } + + Node *controlInput(unsigned controlIndex = 0) const + { + if (operation()->controlInputCount() == 0) + return nullptr; + Q_ASSERT(controlIndex < operation()->controlInputCount()); + return input(operation()->indexOfFirstControl() + controlIndex); + } + + Node *frameStateInput() const + { + if (operation()->hasFrameStateInput()) + return input(operation()->indexOfFrameStateInput()); + return nullptr; + } + + void setFrameStateInput(Node *newFramestate) + { + if (operation()->hasFrameStateInput()) + replaceInput(operation()->indexOfFrameStateInput(), newFramestate); + } + + void insertInput(MemoryPool *pool, unsigned index, Node *newInput); + + void replaceInput(Node *oldIn, Node *newIn) + { + for (unsigned i = 0, ei = inputCount(); i != ei; ++i) { + if (input(i) == oldIn) + replaceInput(i, newIn); + } + } + + void replaceInput(unsigned idx, Node *newIn) + { + m_inputs[idx].set(newIn); + } + + class Uses + { + public: + explicit Uses(Node *node) + : m_node(node) + {} + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool isEmpty() const; + + private: + Node *m_node; + }; + + Uses uses() { return Uses(this); } + bool hasUses() const { return m_firstUse != nullptr; } + unsigned useCount() const + { + unsigned cnt = 0; + for (Use *it = m_firstUse; it; it = it->m_next) + ++cnt; + return cnt; + } + void replaceAllUsesWith(Node *replacement); + void replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput); + + Node *firstValueUse(); + +private: // types and utility methods + friend class Use; + Node(MemoryPool *pool, Id id, const Operation *op, unsigned nInputs, int capacity); + +private: // fields + Use *m_firstUse = nullptr; + const Operation *m_op = nullptr; + QQmlJS::FixedPoolArray<Use> m_inputs; + unsigned m_nInputs = 0; + Id m_id = 0; +}; + +void Use::set(Node *newInput) +{ + if (m_input) + removeFromList(); + m_input = newInput; + if (newInput) + addToList(&newInput->m_firstUse); +} + +class Node::Inputs final +{ +public: + using value_type = Node *; + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool empty() const + { return m_nInputs == 0; } + + unsigned count() const + { return m_nInputs; } + + explicit Inputs(const Use *inputs, unsigned nInputs) + : m_inputs(inputs), m_nInputs(nInputs) + {} + +private: + const Use *m_inputs = nullptr; + unsigned m_nInputs = 0; +}; + +class Node::Inputs::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Node *; + using pointer = const value_type *; + using reference = value_type &; + + Node *operator*() const + { return m_inputs->m_input; } + + bool operator==(const const_iterator &other) const + { return m_inputs == other.m_inputs; } + + bool operator!=(const const_iterator &other) const + { return !(*this == other); } + + const_iterator &operator++() + { ++m_inputs; return *this; } + + const_iterator& operator+=(difference_type offset) + { m_inputs += offset; return *this; } + + const_iterator operator+(difference_type offset) const + { return const_iterator(m_inputs + offset); } + + difference_type operator-(const const_iterator &other) const + { return m_inputs - other.m_inputs; } + +private: + friend class Node::Inputs; + + explicit const_iterator(const Use *inputs) + : m_inputs(inputs) + {} + + const Use *m_inputs; +}; + +Node::Inputs::const_iterator Node::Inputs::begin() const +{ return const_iterator(m_inputs); } + +Node::Inputs::const_iterator Node::Inputs::end() const +{ return const_iterator(m_inputs + m_nInputs); } + +Node::Inputs Node::inputs() const +{ + return Inputs(m_inputs.begin(), m_nInputs); +} + +class Node::Uses::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = int; + using value_type = Node *; + using pointer = Node **; + using reference = Node *&; + + Node *operator*() const + { return m_current->user(); } + + bool operator==(const const_iterator &other) const + { return other.m_current == m_current; } + + bool operator!=(const const_iterator &other) const + { return other.m_current != m_current; } + + const_iterator &operator++() + { m_current = m_next; setNext(); return *this; } + + unsigned inputIndex() const + { return m_current->inputIndex(); } + + bool isUsedAsValue() const + { return inputIndex() < operator*()->operation()->valueInputCount(); } + + bool isUsedAsControl() const + { return operator*()->operation()->indexOfFirstControl() <= inputIndex(); } + +private: + friend class Node::Uses; + + const_iterator() = default; + + explicit const_iterator(Node* node) + : m_current(node->m_firstUse) + { setNext(); } + + void setNext() + { + if (m_current) + m_next = m_current->m_next; + else + m_next = nullptr; + } + +private: + Use *m_current = nullptr; + Use *m_next = nullptr; +}; + +Node::Uses::const_iterator Node::Uses::begin() const +{ return const_iterator(this->m_node); } + +Node::Uses::const_iterator Node::Uses::end() const +{ return const_iterator(); } + +int Use::inputIndex() const +{ + if (!m_user) + return -1; + return int(this - m_user->m_inputs.begin()); +} + +bool Node::isDead() const +{ + Inputs in = inputs(); + return !in.empty() && *in.begin() == nullptr; +} + +void Node::kill() +{ + removeAllInputs(); +} + +class NodeWorkList final +{ + enum State: uint8_t { + Unvisited = 0, + Queued, + Visited, + }; + +public: + NodeWorkList(const Graph *g); + + void reset(); + + bool enqueue(Node *n) + { + State &s = nodeState(n); + if (s == Queued || s == Visited) + return false; + + m_worklist.push_back(n); + s = Queued; + return true; + } + + void enqueue(const std::vector<Node *> &nodes) + { + m_worklist.insert(m_worklist.end(), nodes.begin(), nodes.end()); + for (Node *n : nodes) + nodeState(n) = Queued; + } + + void reEnqueue(Node *n) + { + if (!n) + return; + State &s = nodeState(n); + if (s == Queued) + return; + s = Queued; + m_worklist.push_back(n); + } + + void enqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + enqueue(input); + } + + void reEnqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + reEnqueue(input); + } + + void enqueueValueInputs(Node *n) + { + for (unsigned i = 0, ei = n->operation()->valueInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueEffectInputs(Node *n) + { + for (unsigned i = n->operation()->indexOfFirstEffect(), ei = n->operation()->effectInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueAllUses(Node *n) + { + for (Node *use : n->uses()) + enqueue(use); + } + + Node *dequeueNextNodeForVisiting() + { + while (!m_worklist.empty()) { + Node *n = m_worklist.back(); + m_worklist.pop_back(); + State &s = nodeState(n); + Q_ASSERT(s == Queued); + s = Visited; + return n; + } + + return nullptr; + } + + bool isVisited(Node *n) const + { return nodeState(n) == Visited; } + + bool isEmpty() const + { return m_worklist.empty(); } + + QString status(Node *n) const + { + QString s = QStringLiteral("status for node %1: ").arg(n->id()); + switch (nodeState(n)) { + case Queued: s += QLatin1String("queued"); break; + case Visited: s += QLatin1String("visited"); break; + case Unvisited: s += QLatin1String("unvisited"); break; + } + return s; + } + +private: + State &nodeState(Node *n) + { + const unsigned position(n->id()); + if (position >= m_nodeState.size()) + m_nodeState.resize(position + 1, Unvisited); + + return m_nodeState[position]; + } + + State nodeState(Node *n) const + { return m_nodeState[unsigned(n->id())]; } + +private: + std::vector<Node *> m_worklist; + std::vector<State> m_nodeState; +}; + +class NodeInfo +{ +public: + enum { NoInstructionOffset = -1 }; + +public: + NodeInfo() = default; + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + int currentInstructionOffset() const + { return m_currentInstructionOffset; } + + int nextInstructionOffset() const + { return m_nextInstructionOffset; } + + void setBytecodeOffsets(int current, int next) + { + Q_ASSERT(current != NoInstructionOffset); + Q_ASSERT(next != NoInstructionOffset); + m_currentInstructionOffset = current; + m_nextInstructionOffset = next; + } + +private: + Type m_type; + int m_currentInstructionOffset = NoInstructionOffset; + int m_nextInstructionOffset = NoInstructionOffset; +}; + +class NodeCollector +{ +public: + NodeCollector(const Graph *g, bool collectUses = false, bool skipFramestate = false); + + const std::vector<Node *> &reachable() const + { return m_reachable; } + + void sortById() + { + std::sort(m_reachable.begin(), m_reachable.end(), [](Node *n1, Node *n2) { + return n1->id() < n2->id(); + }); + } + + bool isReachable(Node::Id nodeId) const + { + if (nodeId >= Node::Id(m_isReachable.size())) + return false; + return m_isReachable.at(int(nodeId)); + } + + void markReachable(Node *node) + { + auto nodeId = node->id(); + m_reachable.push_back(node); + if (nodeId >= Node::Id(m_isReachable.size())) + m_isReachable.resize(int(nodeId + 1), false); + m_isReachable.setBit(int(nodeId)); + } + +private: + std::vector<Node *> m_reachable; + BitVector m_isReachable; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4NODE_P_H diff --git a/src/qml/jit/qv4operation.cpp b/src/qml/jit/qv4operation.cpp new file mode 100644 index 0000000000..acd5328fd0 --- /dev/null +++ b/src/qml/jit/qv4operation.cpp @@ -0,0 +1,770 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4operation_p.h" +#include "qv4runtimesupport_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +OperationBuilder::OperationBuilder(QQmlJS::MemoryPool *graphPool) + : m_graphPool(graphPool) +{} + +OperationBuilder *OperationBuilder::create(QQmlJS::MemoryPool *pool) +{ + return pool->New<OperationBuilder>(pool); +} + +Operation *OperationBuilder::getConstant(Value v) +{ + Type t; + switch (v.type()) { + case Value::Undefined_Type: t = Type::undefinedType(); break; + case Value::Integer_Type: t = Type::int32Type(); break; + case Value::Boolean_Type: t = Type::booleanType(); break; + case Value::Null_Type: t = Type::nullType(); break; + case Value::Double_Type: t = Type::doubleType(); break; + case Value::Managed_Type: t = Type::objectType(); break; + default: + if (v.isEmpty()) + t = Type::emptyType(); + else + Q_UNREACHABLE(); + } + return OperationWithPayload<ConstantPayload>::create( + m_graphPool, Meta::Constant, 0, 0, 0, 1, 0, 0, t, Operation::NoFlags, + ConstantPayload(v)); +} + +Operation *OperationBuilder::getParam(unsigned index, const Function::StringId name) +{ + return OperationWithPayload<ParameterPayload>::create(m_graphPool, Meta::Parameter, + 1, 0, 0, + 1, 0, 0, + Type::anyType(), Operation::NoFlags, + ParameterPayload(index, name)); +} + +Operation *OperationBuilder::getRegion(unsigned nControlInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Region, + 0, 0, uint16_t(nControlInputs), + 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getPhi(unsigned nValueInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Phi, + uint16_t(nValueInputs), 0, 1, + 1, 0, 0, + Type::anyType(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEffectPhi(unsigned nEffectInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::EffectPhi, + 0, uint16_t(nEffectInputs), 1, + 0, 1, 0, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getUnwindDispatch(unsigned nContinuations, int unwindHandlerOffset, + int fallthroughSuccessor) +{ + return OperationWithPayload<UnwindDispatchPayload>::create( + m_graphPool, Meta::UnwindDispatch, + 0, 1, 1, 0, nContinuations, nContinuations, + Type(), Operation::NoFlags, + UnwindDispatchPayload(unwindHandlerOffset, + fallthroughSuccessor)); +} + +Operation *OperationBuilder::getHandleUnwind(int unwindHandlerOffset) +{ + return OperationWithPayload<HandleUnwindPayload>::create(m_graphPool, Meta::HandleUnwind, + 0, 1, 1, 0, 1, 1, + Type(), Operation::NoFlags, + HandleUnwindPayload(unwindHandlerOffset)); +} + +Operation *OperationBuilder::getFrameState(uint16_t frameSize) +{ + if (m_opFrameState == nullptr) + m_opFrameState = Operation::create(m_graphPool, Meta::FrameState, + frameSize, 0, 0, 0, 0, 1, + Type(), Operation::NoFlags); + else + Q_ASSERT(frameSize == m_opFrameState->valueInputCount()); + + return m_opFrameState; +} + +Operation *OperationBuilder::getStart(uint16_t outputCount) +{ + return Operation::create(m_graphPool, Meta::Start, + 0, 0, 0, + outputCount, 1, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEnd(uint16_t controlInputCount) +{ + return Operation::create(m_graphPool, Meta::End, + 0, 0, controlInputCount, + 0, 0, 0, + Type(), Operation::NoFlags); +} + +inline Operation *createOperation(Operation::Kind kind, QQmlJS::MemoryPool *staticPool) +{ + auto get = [&](uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type (*typeCreator)(), uint8_t flags) { + return Operation::create(staticPool, kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, typeCreator(), + flags); + }; + + using K = Operation::Kind; + using F = Operation::Flags; + const auto none = &Type::noneType; + const auto any = &Type::anyType; + const auto number = &Type::numberType; + const auto boolean = &Type::booleanType; + + switch (kind) { + case K::Undefined: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Undefined, 0, 0, 0, 1, 0, 0, Type::undefinedType(), F::NoFlags, + ConstantPayload(Primitive::undefinedValue())); + case K::Empty: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Constant, 0, 0, 0, 1, 0, 0, Type::emptyType(), Operation::NoFlags, + ConstantPayload(Primitive::emptyValue())); + case K::Engine: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::CppFrame: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Function: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Jump: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::Return: return get(1, 1, 1, 0, 0, 1, none, F::NoFlags); + case K::Branch: return get(1, 0, 1, 0, 0, 2, none, F::HasFrameStateInput | F::NeedsBytecodeOffsets); + case K::IfTrue: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::IfFalse: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::SelectOutput: return get(3, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::Throw: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::OnException: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::ThrowReferenceError: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::UnwindToLabel: return get(2, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::LoadRegExp: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::ScopedLoad: return get(2, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::ScopedStore: return get(3, 1, 0, 0, 1, 0, none, F::NoFlags); + case K::JSLoadElement: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSGetLookup: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreElement: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupStrict: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupSloppy: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreProperty: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadGlobalLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreNameSloppy: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreNameStrict: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadSuperProperty: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreSuperProperty: return get(2, 1, 1, 0, 1, 2, any, F::CanThrow); + case K::JSLoadClosure: return get(1, 1, 0, 1, 1, 0, any, F::Pure); + case K::JSGetIterator: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNext: return get(2, 1, 1, 2, 1, 1, any, F::NoFlags); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNextForYieldStar: return get(3, 1, 1, 2, 1, 1, any, F::NoFlags); + + case K::JSIteratorClose: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSIn: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSInstanceOf: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLLoadQmlContextPropertyLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSStrictEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + + case K::JSAdd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSSubtract: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSMultiply: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSDivide: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSModulo: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSExponentiate: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + + case K::JSBitAnd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitOr: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitXor: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSUnsignedShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftLeft: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSNegate: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSToNumber: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::Alloca: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + //### it is questionable if VAAlloc/VASeal need effect edges + case K::VAAlloc: return get(1, 1, 0, 1, 1, 0, none, F::NoFlags); + + case K::VAStore: return get(3, 0, 0, 1, 0, 0, none, F::NoFlags); + + case K::JSTypeofName: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSTypeofValue: return get(1, 0, 0, 1, 0, 0, any, F::Pure); + case K::JSDeclareVar: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDestructureRestElement: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSCreateCallContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateCatchContext: return get(2, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCreateWithContext: return get(1, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::JSCreateBlockContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCloneBlockContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateScriptContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSPopScriptContext: return get(0, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::PopContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + + case K::JSThisToObject: return get(1, 1, 1, 0, 1, 2, any, F::NoFlags); + case K::JSCreateMappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateUnmappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateRestParameter: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::JSLoadSuperConstructor: return get(1, 1, 1, 1, 1, 2, any, F::NoFlags); + case K::JSThrowOnNullOrUndefined: return get(1, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSGetTemplateObject: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::StoreThis: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + + case K::GetException: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::SetException: return get(1, 1, 0, 0, 1, 0, any, F::NoFlags); + + case K::ToObject: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::ToBoolean: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::IsEmpty: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::BooleanNot: return get(1, 0, 0, 1, 0, 0, boolean, F::NoFlags); + case K::HasException: return get(1, 1, 0, 1, 1, 0, boolean, F::NoFlags); + + case K::Swap: return get(0, 0, 0, 0, 0, 0, none, F::NoFlags); + case K::Move: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + default: // Non-static operations: + return nullptr; + } +} + +Operation *OperationBuilder::staticOperation(Operation::Kind kind) +{ + static QAtomicPointer<Operation *> ops; + if (Operation **staticOps = ops.load()) + return staticOps[kind]; + + static QAtomicInt initializing = 0; + if (initializing.testAndSetOrdered(0, 1)) { + // This is safe now, because we can only run this piece of code once during the life time + // of the application as we can only change initializing from 0 to 1 once. + Operation **staticOps = new Operation *[Meta::KindsEnd]; + static QQmlJS::MemoryPool pool; + for (int i = 0; i < Meta::KindsEnd; ++i) + staticOps[i] = createOperation(Operation::Kind(i), &pool); + bool success = ops.testAndSetOrdered(nullptr, staticOps); + Q_ASSERT(success); + } else { + // Unfortunately we need to busy wait now until the other thread finishes the static + // initialization; + while (!ops.load()) {} + } + + return ops.load()[kind]; +} + +Operation *OperationBuilder::getJSVarArgsCall(Operation::Kind kind, uint16_t argc) +{ + return Operation::create(m_graphPool, kind, + argc, 1, 1, 1, 1, 2, + Type::anyType(), Operation::CanThrow); +} + +Operation *OperationBuilder::getJSTailCall(uint16_t argc) +{ + return Operation::create(m_graphPool, Meta::JSTailCall, + argc, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getTailCall() +{ + // special varargs call, takes cppframe, engine, func, thisObject, argv, argc + return Operation::create(m_graphPool, Meta::TailCall, + 6, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getCall(Operation::Kind callee) +{ + const bool canThrow = CallPayload::canThrow(callee); + const Type retTy = CallPayload::returnType(callee); + uint16_t nControlInputs = 0; + uint16_t nControlOutputs = 0; + if (canThrow) { + nControlInputs = 1; + nControlOutputs += 2; + } + if (CallPayload::changesContext(callee)) { + nControlInputs = 1; + nControlOutputs = std::max<uint16_t>(nControlInputs, 1); + } + if (callee == Meta::Throw || callee == Meta::ThrowReferenceError || + callee == Meta::JSIteratorNext || callee == Meta::JSIteratorNextForYieldStar) { + nControlInputs = 1; + nControlOutputs = 1; + } + Operation::Flags flags = Operation::NoFlags; + if (canThrow) + flags = Operation::Flags(flags | Operation::CanThrow); + if (CallPayload::isPure(callee)) + flags = Operation::Flags(flags | Operation::Pure); + const uint16_t nEffects = (flags & Operation::Pure) ? 0 : 1; + const uint16_t nValueOutputs = retTy.isNone() ? 0 : 1; + const uint16_t nValueInputs = CallPayload::argc(callee); + + return OperationWithPayload<CallPayload>::create( + m_graphPool, Meta::Call, + nValueInputs, nEffects, nControlInputs, + nValueOutputs, nEffects, nControlOutputs, + retTy, flags, + CallPayload(callee)); +} + +Operation *OperationBuilder::getVASeal(uint16_t nElements) +{ + return Operation::create(m_graphPool, Meta::VASeal, + nElements + 1, 1, 0, 1, 1, 0, + Type::anyType(), Operation::NoFlags); +} + +QString Operation::debugString() const +{ + switch (kind()) { + + case Meta::Constant: + return QStringLiteral("Constant[%1]").arg(ConstantPayload::get(*this)->debugString()); + case Meta::Parameter: + return QStringLiteral("Parameter[%1]").arg(ParameterPayload::get(*this)->debugString()); + case Meta::Call: + return QStringLiteral("Call[%1]").arg(CallPayload::get(*this)->debugString()); + case Meta::UnwindDispatch: + return QStringLiteral("UnwindDispatch[%1]").arg(UnwindDispatchPayload::get(*this) + ->debugString()); + case Meta::HandleUnwind: + return QStringLiteral("HandleUnwind[%1]").arg(HandleUnwindPayload::get(*this) + ->debugString()); + + default: + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(kind())); + } +} + +QString ConstantPayload::debugString() const +{ + return debugString(m_value); +} + +QString ConstantPayload::debugString(QV4::Value v) +{ + if (v.isManaged()) + return QString::asprintf("Ptr: %p", v.heapObject()); + if (v.isEmpty()) + return QStringLiteral("empty"); + return v.toQStringNoThrow(); +} + +QString ParameterPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_index); +} + +QString UnwindDispatchPayload::debugString() const +{ + return QStringLiteral("%1, %2").arg(QString::number(m_fallthroughSuccessor), + QString::number(m_unwindHandlerOffset)); +} + +QString HandleUnwindPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_unwindHandlerOffset); +} + +static Type translateType(RuntimeSupport::ArgumentType t) +{ + switch (t) { + case RuntimeSupport::ArgumentType::Int: return Type::int32Type(); + case RuntimeSupport::ArgumentType::Bool: return Type::booleanType(); + case RuntimeSupport::ArgumentType::Void: return Type(); + case RuntimeSupport::ArgumentType::Engine: return Type::rawPointerType(); + case RuntimeSupport::ArgumentType::ValueRef: return Type::anyType(); + case RuntimeSupport::ArgumentType::ValueArray: return Type::anyType(); + case RuntimeSupport::ArgumentType::ReturnedValue: return Type::anyType(); + default: Q_UNREACHABLE(); + } +} + +template<template<typename Operation> class M /* MetaOperation */, typename ReturnValue> +static ReturnValue operateOnRuntimeCall(Operation::Kind kind, bool abortOnMissingCall = true) +{ + using K = Operation::Kind; + using R = Runtime; + + switch (kind) { + case K::Throw: return M<R::ThrowException>::doIt(); + case K::ThrowReferenceError: return M<R::ThrowReferenceError>::doIt(); + + case K::JSEqual: return M<R::CompareEqual>::doIt(); + case K::JSGreaterThan: return M<R::CompareGreaterThan>::doIt(); + case K::JSGreaterEqual: return M<R::CompareGreaterEqual>::doIt(); + case K::JSLessThan: return M<R::CompareLessThan>::doIt(); + case K::JSLessEqual: return M<R::CompareLessEqual>::doIt(); + case K::JSStrictEqual: return M<R::CompareStrictEqual>::doIt(); + + case K::JSBitAnd: return M<R::BitAnd>::doIt(); + case K::JSBitOr: return M<R::BitOr>::doIt(); + case K::JSBitXor: return M<R::BitXor>::doIt(); + case K::JSUnsignedShiftRight: return M<R::UShr>::doIt(); + case K::JSShiftRight: return M<R::Shr>::doIt(); + case K::JSShiftLeft: return M<R::Shl>::doIt(); + + case K::JSAdd: return M<R::Add>::doIt(); + case K::JSSubtract: return M<R::Sub>::doIt(); + case K::JSMultiply: return M<R::Mul>::doIt(); + case K::JSDivide: return M<R::Div>::doIt(); + case K::JSModulo: return M<R::Mod>::doIt(); + case K::JSExponentiate: return M<R::Exp>::doIt(); + + case K::ToBoolean: return M<R::ToBoolean>::doIt(); + case K::ToObject: return M<R::ToObject>::doIt(); + + case K::JSNegate: return M<R::UMinus>::doIt(); + case K::JSToNumber: return M<R::ToNumber>::doIt(); + + case K::JSLoadName: return M<R::LoadName>::doIt(); + case K::JSLoadElement: return M<R::LoadElement>::doIt(); + case K::JSStoreElement: return M<R::StoreElement>::doIt(); + case K::JSGetLookup: return M<R::GetLookup>::doIt(); + case K::JSSetLookupStrict: return M<R::SetLookupStrict>::doIt(); + case K::JSSetLookupSloppy: return M<R::SetLookupSloppy>::doIt(); + case K::JSLoadProperty: return M<R::LoadProperty>::doIt(); + case K::JSStoreProperty: return M<R::StoreProperty>::doIt(); + case K::JSLoadGlobalLookup: return M<R::LoadGlobalLookup>::doIt(); + case K::JSStoreNameSloppy: return M<R::StoreNameSloppy>::doIt(); + case K::JSStoreNameStrict: return M<R::StoreNameStrict>::doIt(); + case K::JSLoadSuperProperty: return M<R::LoadSuperProperty>::doIt(); + case K::JSStoreSuperProperty: return M<R::StoreSuperProperty>::doIt(); + case K::JSLoadClosure: return M<R::Closure>::doIt(); + case K::JSGetIterator: return M<R::GetIterator>::doIt(); + case K::JSIteratorNext: return M<R::IteratorNext>::doIt(); + case K::JSIteratorNextForYieldStar: return M<R::IteratorNextForYieldStar>::doIt(); + case K::JSIteratorClose: return M<R::IteratorClose>::doIt(); + case K::JSDeleteProperty: return M<R::DeleteProperty>::doIt(); + case K::JSDeleteName: return M<R::DeleteName>::doIt(); + case K::JSIn: return M<R::In>::doIt(); + case K::JSInstanceOf: return M<R::Instanceof>::doIt(); + case K::QMLLoadQmlContextPropertyLookup: return M<R::LoadQmlContextPropertyLookup>::doIt(); + + case K::JSTypeofName: return M<R::TypeofName>::doIt(); + case K::JSTypeofValue: return M<R::TypeofValue>::doIt(); + case K::JSDeclareVar: return M<R::DeclareVar>::doIt(); + case K::JSDestructureRestElement: return M<R::DestructureRestElement>::doIt(); + case K::JSThisToObject: return M<R::ConvertThisToObject>::doIt(); + case K::JSCreateMappedArgumentsObject: return M<R::CreateMappedArgumentsObject>::doIt(); + case K::JSCreateUnmappedArgumentsObject: return M<R::CreateUnmappedArgumentsObject>::doIt(); + case K::JSCreateRestParameter: return M<R::CreateRestParameter>::doIt(); + case K::JSLoadSuperConstructor: return M<R::LoadSuperConstructor>::doIt(); + case K::JSThrowOnNullOrUndefined: return M<R::ThrowOnNullOrUndefined>::doIt(); + + case K::JSCreateCallContext: return M<R::PushCallContext>::doIt(); + case K::JSCreateCatchContext: return M<R::PushCatchContext>::doIt(); + case K::JSCreateWithContext: return M<R::PushWithContext>::doIt(); + case K::JSCreateBlockContext: return M<R::PushBlockContext>::doIt(); + case K::JSCloneBlockContext: return M<R::CloneBlockContext>::doIt(); + case K::JSCreateScriptContext: return M<R::PushScriptContext>::doIt(); + case K::JSPopScriptContext: return M<R::PopScriptContext>::doIt(); + + case K::LoadRegExp: return M<R::RegexpLiteral>::doIt(); + case K::JSGetTemplateObject: return M<R::GetTemplateObject>::doIt(); + + case K::JSCallName: return M<R::CallName>::doIt(); + case K::JSCallValue: return M<R::CallValue>::doIt(); + case K::JSCallElement: return M<R::CallElement>::doIt(); + case K::JSCallLookup: return M<R::CallPropertyLookup>::doIt(); + case K::JSCallProperty: return M<R::CallProperty>::doIt(); + case K::JSCallGlobalLookup: return M<R::CallGlobalLookup>::doIt(); + case K::JSCallPossiblyDirectEval: return M<R::CallPossiblyDirectEval>::doIt(); + case K::JSCallWithReceiver: return M<R::CallWithReceiver>::doIt(); + case K::JSDefineObjectLiteral: return M<R::ObjectLiteral>::doIt(); + case K::JSDefineArray: return M<R::ArrayLiteral>::doIt(); + case K::JSCallWithSpread: return M<R::CallWithSpread>::doIt(); + case K::JSConstruct: return M<R::Construct>::doIt(); + case K::JSConstructWithSpread: return M<R::ConstructWithSpread>::doIt(); + case K::JSTailCall: return M<R::TailCall>::doIt(); + case K::JSCreateClass: return M<R::CreateClass>::doIt(); + default: + if (abortOnMissingCall) + Q_UNREACHABLE(); + else + return ReturnValue(); + } +} + +template<typename Method> +struct IsRuntimeMethodOperation +{ + static constexpr bool doIt() { return true; } +}; + +bool CallPayload::isRuntimeCall(Operation::Kind m) +{ + return operateOnRuntimeCall<IsRuntimeMethodOperation, bool>(m, false); +} + +QString CallPayload::debugString() const +{ + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(m_callee)); +} + +template<typename Method> +struct MethodArgcOperation +{ + static constexpr unsigned doIt() { return RuntimeSupport::argumentCount<Method>(); } +}; + +unsigned CallPayload::argc(Operation::Kind callee) +{ + return operateOnRuntimeCall<MethodArgcOperation, unsigned>(callee); +} + +template<typename Method> struct MethodArg1TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg1Type<Method>(); } }; +template<typename Method> struct MethodArg2TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg2Type<Method>(); } }; +template<typename Method> struct MethodArg3TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg3Type<Method>(); } }; +template<typename Method> struct MethodArg4TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg4Type<Method>(); } }; +template<typename Method> struct MethodArg5TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg5Type<Method>(); } }; +template<typename Method> struct MethodArg6TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg6Type<Method>(); } }; + +static RuntimeSupport::ArgumentType untranslatedArgumentType(Operation::Kind m, unsigned arg) +{ + if (m == Meta::JSTailCall) { + if (arg < 4) + return RuntimeSupport::ArgumentType::ValueRef; + else + return RuntimeSupport::ArgumentType::Invalid; + } + + switch (arg) { + case 0: return operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m); + case 1: return operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m); + case 2: return operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m); + case 3: return operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m); + case 4: return operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m); + case 5: return operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m); + default: return RuntimeSupport::ArgumentType::Invalid; + } +} + +bool CallPayload::needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType) +{ + auto argTy = untranslatedArgumentType(m, arg); + if (argTy == RuntimeSupport::ArgumentType::ValueArray) + return true; + if (argTy != RuntimeSupport::ArgumentType::ValueRef) + return false; + + if (op->kind() == Meta::Constant) + return true; + + return !nodeType.isObject() && !nodeType.isRawPointer() && !nodeType.isAny(); +} + +template<typename Method> +struct MethodRetTyOperation +{ + static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::retType<Method>(); } +}; + +Type CallPayload::returnType(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return Type(); + + auto t = operateOnRuntimeCall<MethodRetTyOperation, RuntimeSupport::ArgumentType>(m); + return translateType(t); +} + +static int firstArgumentPositionForType(Operation::Kind m, RuntimeSupport::ArgumentType type) +{ + if (operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 1; + if (operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 2; + if (operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 3; + if (operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 4; + if (operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 5; + if (operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 6; + return -1; +} + +unsigned CallPayload::varArgsStart(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return 4 - 1; + + int pos = firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) - 1; + Q_ASSERT(pos >= 0); + return pos; +} + +bool CallPayload::isVarArgsCall(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return true; + if (lastArgumentIsOutputValue(m)) + return false; + return firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) != -1; +} + +bool CallPayload::isVarArgsCall() const +{ + return isVarArgsCall(m_callee); +} + +template<typename Method> +struct MethodsLastArgumentIsOutputValue +{ + static constexpr bool doIt() { return Method::lastArgumentIsOutputValue; } +}; + +bool CallPayload::lastArgumentIsOutputValue(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodsLastArgumentIsOutputValue, bool>(m); +} + +template<typename Method> +struct MethodChangesContext +{ + static constexpr bool doIt() { return Method::changesContext; } +}; + +bool CallPayload::changesContext(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodChangesContext, bool>(m); +} + +template<typename Method> +struct MethodIsPure +{ + static constexpr bool doIt() { return Method::pure; } +}; + +bool CallPayload::isPure(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodIsPure, bool>(m); +} + +template<typename Method> +struct MethodCanThrow +{ + static constexpr bool doIt() { return Method::throws; } +}; + +bool CallPayload::canThrow(Operation::Kind m) +{ + switch (m) { + case Meta::Throw: Q_FALLTHROUGH(); + case Meta::ThrowReferenceError: + // the execution path following these instructions is already linked up to the exception handler + return false; + case Meta::JSIteratorNext: Q_FALLTHROUGH(); + case Meta::JSIteratorNextForYieldStar: + // special case: see GraphBuilder::generate_IteratorNext + return false; + default: + return operateOnRuntimeCall<MethodCanThrow, bool>(m); + } +} + +bool CallPayload::takesEngineAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Engine; +} + +bool CallPayload::takesFunctionAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Function; +} + +bool CallPayload::takesFrameAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Frame; +} + +template<typename Method> +struct GetMethodPtr +{ + static constexpr void *doIt() { return reinterpret_cast<void *>(&Method::call); } +}; + +void *CallPayload::getMethodPtr(Operation::Kind m) +{ + return operateOnRuntimeCall<GetMethodPtr, void *>(m); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4operation_p.h b/src/qml/jit/qv4operation_p.h new file mode 100644 index 0000000000..43214023e8 --- /dev/null +++ b/src/qml/jit/qv4operation_p.h @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4OPERATION_P_H +#define QV4OPERATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4ir_p.h> +#include <private/qqmljsmemorypool_p.h> + +#include <QtCore/qatomic.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +namespace Meta { +enum OpKind: uint16_t { + FrameState, + Start, + End, + + Undefined, + Constant, + Parameter, + Empty, + Engine, + CppFrame, + Function, + + Jump, + Return, + JSTailCall, + TailCall, + Branch, + IfTrue, + IfFalse, + Region, + OnException, + Phi, + EffectPhi, + SelectOutput, + UnwindDispatch, + UnwindToLabel, + HandleUnwind, + Throw, + ThrowReferenceError, + + Call, + + LoadRegExp, + ScopedLoad, + ScopedStore, + + JSLoadElement, + JSStoreElement, + JSGetLookup, + JSSetLookupStrict, + JSSetLookupSloppy, + JSLoadProperty, + JSStoreProperty, + JSLoadName, + JSLoadGlobalLookup, + JSStoreNameSloppy, + JSStoreNameStrict, + JSLoadSuperProperty, + JSStoreSuperProperty, + JSLoadClosure, + JSGetIterator, + JSIteratorNext, + JSIteratorNextForYieldStar, + JSIteratorClose, + JSDeleteProperty, + JSDeleteName, + JSIn, + JSInstanceOf, + + /* ok, these are qml object ops, but we don't care for now and treat them as JS */ + QMLLoadQmlContextPropertyLookup, + QMLCallQmlContextPropertyLookup, + + JSEqual, + JSGreaterThan, + JSGreaterEqual, + JSLessThan, + JSLessEqual, + JSStrictEqual, + + JSAdd, + JSSubtract, + JSMultiply, + JSDivide, + JSModulo, + JSExponentiate, + + JSBitAnd, + JSBitOr, + JSBitXor, + JSUnsignedShiftRight, + JSShiftRight, + JSShiftLeft, + + JSNegate, + JSToNumber, + + JSCallName, + JSCallValue, + JSCallElement, + JSCallProperty, + JSCallLookup, + JSCallGlobalLookup, + JSCallPossiblyDirectEval, + JSCallWithReceiver, + JSCallWithSpread, + JSDefineObjectLiteral, + JSDefineArray, + JSCreateClass, + JSConstruct, + JSConstructWithSpread, + + JSTypeofName, + JSTypeofValue, + JSDeclareVar, + JSDestructureRestElement, + JSThisToObject, + JSCreateMappedArgumentsObject, + JSCreateUnmappedArgumentsObject, + JSCreateRestParameter, + JSLoadSuperConstructor, + JSThrowOnNullOrUndefined, + JSGetTemplateObject, + StoreThis, + + JSCreateCallContext, + JSCreateCatchContext, + JSCreateWithContext, + JSCreateBlockContext, + JSCloneBlockContext, + JSCreateScriptContext, + JSPopScriptContext, + PopContext, + + GetException, + SetException, + + ToObject, + ToBoolean, + + //### do we need this? Or should a later phase generate JumpIsEmpty? + IsEmpty, + + Alloca, + VAAlloc, + VAStore, + VASeal, + + BooleanNot, + HasException, + + // Low level, used by the register allocator and stack allocator: + Swap, + Move, + KindsEnd +}; +Q_NAMESPACE +Q_ENUM_NS(OpKind) +} // namespace Ops + +class Operation +{ + Q_DISABLE_COPY_MOVE(Operation) + +public: + using Kind = Meta::OpKind; + + enum Flags: uint8_t { + NoFlags = 0, + ThrowsFlag = 1 << 0, + Pure = 1 << 1, // no read/write side effect, cannot throw, cannot deopt, and is idempotent + NeedsBytecodeOffsets = 1 << 2, + + CanThrow = ThrowsFlag | NeedsBytecodeOffsets, + + HasFrameStateInput = 1 << 3, + }; + +public: + static Operation *create(QQmlJS::MemoryPool *pool, Kind kind, uint16_t inValueCount, + uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, + uint16_t outControlCount, Type type, uint8_t flags) + { + return pool->New<Operation>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, Flags(flags)); + } + + Kind kind() const + { return m_kind; } + + bool isConstant() const + { + switch (kind()) { + case Meta::Undefined: Q_FALLTHROUGH(); + case Meta::Constant: + case Meta::Empty: + return true; + default: + return false; + } + } + + QString debugString() const; + + uint16_t valueInputCount() const { return m_inValueCount; } + uint16_t effectInputCount() const { return m_inEffectCount; } + uint16_t controlInputCount() const { return m_inControlCount; } + uint16_t valueOutputCount() const { return m_outValueCount; } + uint16_t effectOutputCount() const { return m_outEffectCount; } + uint16_t controlOutputCount() const { return m_outControlCount; } + + unsigned indexOfFirstEffect() const { return m_inValueCount; } + unsigned indexOfFirstControl() const { return m_inValueCount + m_inEffectCount; } + unsigned indexOfFrameStateInput() const + { + return hasFrameStateInput() ? indexOfFirstControl() + m_inControlCount + : std::numeric_limits<unsigned>::max(); + } + + Type type() const + { return m_type; } + + bool canThrow() const + { return m_flags & ThrowsFlag; } + + bool isPure() const + { return m_flags & Pure; } + + bool needsBytecodeOffsets() const + { return m_flags & NeedsBytecodeOffsets; } + + bool hasFrameStateInput() const + { return m_flags & HasFrameStateInput; } + + unsigned totalInputCount() const + { + return valueInputCount() + effectInputCount() + controlInputCount() + + (hasFrameStateInput() ? 1 : 0); + } + unsigned totalOutputCount() const { return valueOutputCount() + effectOutputCount() + controlOutputCount(); } + +protected: + friend class QQmlJS::MemoryPool; + Operation(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, uint8_t flags) + : m_kind(kind) + , m_inValueCount(inValueCount) + , m_inEffectCount(inEffectCount) + , m_inControlCount(inControlCount) + , m_outValueCount(outValueCount) + , m_outEffectCount(outEffectCount) + , m_outControlCount(outControlCount) + , m_type(type) + , m_flags(Flags(flags)) + { + } + + ~Operation() = default; + +private: + Kind m_kind; + uint16_t m_inValueCount; + uint16_t m_inEffectCount; + uint16_t m_inControlCount; + uint16_t m_outValueCount; + uint16_t m_outEffectCount; + uint16_t m_outControlCount; + Type m_type; + Flags m_flags; +}; + +template <typename Payload> +class OperationWithPayload: public Operation +{ +public: + static OperationWithPayload *create(QQmlJS::MemoryPool *pool, Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + { + return pool->New<OperationWithPayload>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags, payload); + } + + const Payload &payload() const + { return m_payload; } + +protected: + friend class QQmlJS::MemoryPool; + OperationWithPayload(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + : Operation(kind, + inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags) + , m_payload(payload) + {} + + ~OperationWithPayload() = default; + +private: + Payload m_payload; +}; + +class ConstantPayload +{ +public: + explicit ConstantPayload(QV4::Value v) + : m_value(v) + {} + + QV4::Value value() const + { return m_value; } + + static const ConstantPayload *get(const Operation &op) + { + if (op.kind() != Meta::Constant) + return nullptr; + + return &static_cast<const OperationWithPayload<ConstantPayload>&>(op).payload(); + } + + QString debugString() const; + static QString debugString(QV4::Value v); + +private: + QV4::Value m_value; +}; + +class ParameterPayload +{ +public: + ParameterPayload(size_t index, Function::StringId stringId) + : m_index(index) + , m_stringId(stringId) + {} + + size_t parameterIndex() const + { return m_index; } + + Function::StringId stringId() const + { return m_stringId; } + + static const ParameterPayload *get(const Operation &op) + { + if (op.kind() != Meta::Parameter) + return nullptr; + + return &static_cast<const OperationWithPayload<ParameterPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + size_t m_index; + Function::StringId m_stringId; +}; + +class CallPayload +{ +public: + CallPayload(Operation::Kind callee) + : m_callee(callee) + {} + + static const CallPayload *get(const Operation &op) + { + if (op.kind() != Meta::Call) + return nullptr; + + return &static_cast<const OperationWithPayload<CallPayload>&>(op).payload(); + } + + static bool isRuntimeCall(Operation::Kind m); + + Operation::Kind callee() const { return m_callee; } + QString debugString() const; + + unsigned argc() const { return argc(m_callee); } + static unsigned argc(Operation::Kind callee); + static bool needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType); + static Type returnType(Operation::Kind m); + static int engineArgumentPosition(Operation::Kind m); + static int functionArgumentPosition(Operation::Kind m); + + static constexpr unsigned NoVarArgs = std::numeric_limits<unsigned>::max(); + static unsigned varArgsStart(Operation::Kind m); + static bool isVarArgsCall(Operation::Kind m); + bool isVarArgsCall() const; + static bool lastArgumentIsOutputValue(Operation::Kind m); + static bool changesContext(Operation::Kind m); + static bool isPure(Operation::Kind m); + static bool canThrow(Operation::Kind m); + static bool takesEngineAsArg(Operation::Kind m, int arg); + static bool takesFunctionAsArg(Operation::Kind m, int arg); + static bool takesFrameAsArg(Operation::Kind m, int arg); + static void *getMethodPtr(Operation::Kind m); + +private: + Operation::Kind m_callee; +}; + +class UnwindDispatchPayload +{ +public: + UnwindDispatchPayload(int unwindHandlerOffset, int fallthroughSuccessor) + : m_unwindHandlerOffset(unwindHandlerOffset) + , m_fallthroughSuccessor(fallthroughSuccessor) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + int fallthroughSuccessor() const //### unused... + { return m_fallthroughSuccessor; } + + static const UnwindDispatchPayload *get(const Operation &op) + { + if (op.kind() != Meta::UnwindDispatch) + return nullptr; + + return &static_cast<const OperationWithPayload<UnwindDispatchPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; + int m_fallthroughSuccessor; +}; + +class HandleUnwindPayload +{ +public: + HandleUnwindPayload(int unwindHandlerOffset) + : m_unwindHandlerOffset(unwindHandlerOffset) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + static const HandleUnwindPayload *get(const Operation &op) + { + if (op.kind() != Meta::HandleUnwind) + return nullptr; + + return &static_cast<const OperationWithPayload<HandleUnwindPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; +}; + +class OperationBuilder +{ + Q_DISABLE_COPY_MOVE(OperationBuilder) + + friend class QQmlJS::MemoryPool; + OperationBuilder(QQmlJS::MemoryPool *graphPool); + +public: + static OperationBuilder *create(QQmlJS::MemoryPool *pool); + ~OperationBuilder() = delete; + + Operation *getConstant(QV4::Value v); + Operation *getFrameState(uint16_t frameSize); + Operation *getStart(uint16_t outputCount); + Operation *getEnd(uint16_t controlInputCount); + Operation *getParam(unsigned index, Function::StringId name); + Operation *getRegion(unsigned nControlInputs); + Operation *getPhi(unsigned nValueInputs); + Operation *getEffectPhi(unsigned nEffectInputs); + Operation *getUnwindDispatch(unsigned nControlOutputs, int unwindHandlerOffset, int fallthroughSuccessor); + Operation *getHandleUnwind(int unwindHandlerOffset); + + template<Operation::Kind kind> + Operation *get() { + return staticOperation(kind); + } + + Operation *getVASeal(uint16_t nElements); + + Operation *getJSVarArgsCall(Operation::Kind kind, uint16_t argc); + Operation *getJSTailCall(uint16_t argc); + Operation *getTailCall(); + + Operation *getCall(Operation::Kind callee); + +private: + QQmlJS::MemoryPool *m_graphPool; // used to store per-graph nodes + Operation *m_opFrameState = nullptr; + static Operation *staticOperation(Operation::Kind kind); +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4OPERATION_P_H diff --git a/src/qml/jit/qv4runtimesupport_p.h b/src/qml/jit/qv4runtimesupport_p.h new file mode 100644 index 0000000000..0dc6022331 --- /dev/null +++ b/src/qml/jit/qv4runtimesupport_p.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4RUNTIMESUPPORT_P_H +#define QV4RUNTIMESUPPORT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qv4runtimeapi_p.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { +namespace RuntimeSupport { + +template <typename T> +struct CountArguments { + static constexpr unsigned count = 0; +}; +template <typename RetTy, typename... Args> +struct CountArguments<RetTy (*)(Args... args)> { + static constexpr unsigned count = sizeof...(Args) ; +}; + +template<typename M> +static constexpr unsigned argumentCount() { + using type = decltype(&M::call); + return CountArguments<type>::count; +} + +enum class ArgumentType { + Invalid, + Engine, + Frame, + Function, + ValueRef, + ValueArray, + ReturnedValue, + Int, + Bool, + Void, +}; + + +template <typename T> +struct JavaScriptType +{ + // No default type. We want to make sure everything we do is actually recognized. +}; + +template <typename T> +struct ReturnValue +{ + // No default type. +}; + +template <int I, typename T> +struct Argument +{ + // For simplicity, we add a default here. Otherwise we would need to spell out more + // combinations of I and number of arguments of T. + static constexpr ArgumentType type = ArgumentType::Invalid; +}; + +template <typename RetTy, typename T, typename... Args> +struct Argument<1, RetTy (*)(T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename T, typename... Args> +struct Argument<2, RetTy (*)(Arg1, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, typename T, + typename... Args> +struct Argument<3, RetTy (*)(Arg1, Arg2, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename T, typename... Args> +struct Argument<4, RetTy (*)(Arg1, Arg2, Arg3, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename T, typename... Args> +struct Argument<5, RetTy (*)(Arg1, Arg2, Arg3, Arg4, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename Arg5, typename T, typename... Args> +struct Argument<6, RetTy (*)(Arg1, Arg2, Arg3, Arg4, Arg5, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename... Args> +struct ReturnValue<RetTy (*)(Args... args)> { + static constexpr ArgumentType type = JavaScriptType<RetTy>::type; +}; + +template<> +struct JavaScriptType<QV4::ExecutionEngine *> +{ + static constexpr ArgumentType type = ArgumentType::Engine; +}; + +template<> +struct JavaScriptType<QV4::CppStackFrame *> +{ + static constexpr ArgumentType type = ArgumentType::Frame; +}; + +template<> +struct JavaScriptType<QV4::Function *> +{ + static constexpr ArgumentType type = ArgumentType::Function; +}; + +template<> +struct JavaScriptType<const QV4::Value &> +{ + static constexpr ArgumentType type = ArgumentType::ValueRef; +}; + +template<> +// We need to pass Value * in order to match a parmeter Value[]. +struct JavaScriptType<QV4::Value *> +{ + static constexpr ArgumentType type = ArgumentType::ValueArray; +}; + +template<> +struct JavaScriptType<int> +{ + static constexpr ArgumentType type = ArgumentType::Int; +}; + +template<> +struct JavaScriptType<QV4::Bool> +{ + static constexpr ArgumentType type = ArgumentType::Bool; +}; + +template<> +struct JavaScriptType<QV4::ReturnedValue> +{ + static constexpr ArgumentType type = ArgumentType::ReturnedValue; +}; + +template<> +struct JavaScriptType<void> +{ + static constexpr ArgumentType type = ArgumentType::Void; +}; + +template<typename M> +static constexpr ArgumentType retType() { + using Type = decltype(&M::call); + return ReturnValue<Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg1Type() { + using Type = decltype(&M::call); + return Argument<1, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg2Type() { + using Type = decltype(&M::call); + return Argument<2, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg3Type() { + using Type = decltype(&M::call); + return Argument<3, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg4Type() { + using Type = decltype(&M::call); + return Argument<4, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg5Type() { + using Type = decltype(&M::call); + return Argument<5, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg6Type() { + using Type = decltype(&M::call); + return Argument<6, Type>::type; +} + +} // namespace RuntimeSupport +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4RUNTIMESUPPORT_P_H diff --git a/src/qml/jit/qv4schedulers.cpp b/src/qml/jit/qv4schedulers.cpp new file mode 100644 index 0000000000..0dffefa951 --- /dev/null +++ b/src/qml/jit/qv4schedulers.cpp @@ -0,0 +1,912 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4schedulers_p.h" +#include "qv4util_p.h" +#include "qv4graph_p.h" +#include "qv4blockscheduler_p.h" +#include "qv4stackframe_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcSched, "qt.v4.ir.scheduling") +Q_LOGGING_CATEGORY(lcDotCFG, "qt.v4.ir.scheduling.cfg") + +static bool needsScheduling(Node *n) +{ + if (n->operation()->isConstant()) + return false; + switch (n->opcode()) { + case Meta::Function: Q_FALLTHROUGH(); + case Meta::CppFrame: + case Meta::Phi: + case Meta::EffectPhi: + return false; + default: + return true; + } +} + +bool NodeScheduler::canStartBlock(Node *node) const +{ + switch (node->operation()->kind()) { + case Meta::Start: Q_FALLTHROUGH(); + case Meta::IfTrue: + case Meta::IfFalse: + case Meta::Region: + case Meta::HandleUnwind: + case Meta::OnException: + return true; + + default: + return false; + } +} + +bool NodeScheduler::isControlFlowSplit(Node *node) const +{ + int nOutputs = node->operation()->controlOutputCount(); + if (nOutputs == 2) { + // if there is a "missing" control output, it's for exception flow without unwinder + int controlUses = 0; + auto uses = node->uses(); + for (auto it = uses.begin(), eit = uses.end(); it != eit; ++it) { + if (isLive(*it) && it.isUsedAsControl()) + ++controlUses; + } + return controlUses == 2; + } + return nOutputs > 2; +} + +bool NodeScheduler::isBlockTerminator(Node *node) const +{ + switch (node->operation()->kind()) { + case Meta::Branch: Q_FALLTHROUGH(); + case Meta::Jump: + case Meta::Return: + case Meta::TailCall: + case Meta::UnwindDispatch: + case Meta::End: + return true; + case Meta::Call: + return isControlFlowSplit(node); + default: + return false; + } +} + +MIBlock *NodeScheduler::getCommonDominator(MIBlock *one, MIBlock *other) const +{ + MIBlock::Index a = one->index(); + MIBlock::Index b = other->index(); + + while (a != b) { + if (m_dominatorDepthForBlock[a] < m_dominatorDepthForBlock[b]) + b = m_domTree->immediateDominator(b); + else + a = m_domTree->immediateDominator(a); + } + + return m_miFunction->block(a); +} + +// For Nodes that end up inside loops, it'd be great if we can move (hoist) them out of the loop. +// To do that, we need a block that preceeds the loop. (So the block before the loop header.) +// This function calculates that hoist block if the original block is in a loop. +MIBlock *NodeScheduler::getHoistBlock(MIBlock *block) const +{ + if (m_loopInfo->isLoopHeader(block)) + return m_miFunction->block(m_domTree->immediateDominator(block->index())); + + // make the loop header a candidate: + MIBlock *loopHeader = m_loopInfo->loopHeaderFor(block); + if (loopHeader == nullptr) + return nullptr; // block is not in a loop + + // And now the tricky part: block has to dominate all exits from the loop. If it does not do + // that, it meanse that there is an exit from the loop that can be reached before block. In + // that case, hoisting from "block" to "loopHeader" would mean there now is an extra calculation + // that is not needed for a certain loop exit. + for (MIBlock *outEdge : m_loopInfo->loopExitsForLoop(loopHeader)) { + if (getCommonDominator(block, outEdge) != block) + return nullptr; + } + + return m_miFunction->block(m_domTree->immediateDominator(loopHeader->index())); +} + +NodeScheduler::NodeScheduler(Function *irFunction) + : m_irFunction(irFunction) + , m_vregs(irFunction->graph()->nodeCount(), std::numeric_limits<unsigned>::max()) + , m_live(irFunction->graph(), /*collectUses =*/ false /* do explicitly NOT collect uses! */) +{ +} + +MIFunction *NodeScheduler::buildMIFunction() +{ + m_miFunction = new MIFunction(m_irFunction); + + // step 1: build the CFG + auto roots = buildCFG(); + m_miFunction->renumberBlocks(); + m_miFunction->dump(QStringLiteral("CFG after renumbering")); + + Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->index() + == MIFunction::StartBlockIndex); + Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->instructions().front().opcode() + == Meta::Start); + + // step 2: build the dominator tree + if (lcDotCFG().isDebugEnabled()) + dumpDotCFG(); + m_domTree.reset(new DominatorTree(m_miFunction)); + m_dominatorDepthForBlock = m_domTree->calculateNodeDepths(); + + // step 3: find loops + m_loopInfo.reset(new LoopInfo(*m_domTree.data())); + m_loopInfo->detectLoops(); + + // step 4: schedule early + scheduleEarly(roots); + showNodesByBlock(QStringLiteral("nodes per block after early scheduling")); + + // step 5: schedule late + scheduleLate(roots); + showNodesByBlock(QStringLiteral("nodes per block after late scheduling")); + + // step 6: schedule instructions in each block + scheduleNodesInBlock(); + + m_miFunction->dump(QStringLiteral("MI before block scheduling")); + + // step 7: order the basic blocks in the CFG + BlockScheduler blockScheduler(*m_domTree.data(), *m_loopInfo.data()); + m_miFunction->setBlockOrder(blockScheduler.scheduledBlockSequence()); + + // we're done + m_miFunction->renumberInstructions(); + m_miFunction->setVregCount(m_nextVReg); + m_miFunction->dump(QStringLiteral("MI after scheduling")); + return m_miFunction; +} + +static Node *splitEdge(Function *irFunction, Node *node, unsigned inputIndex) +{ + Graph *g = irFunction->graph(); + Node *in = node->input(inputIndex); + Node *region = g->createNode(g->opBuilder()->getRegion(1), &in, 1); + Node *jump = g->createNode(g->opBuilder()->get<Meta::Jump>(), ®ion, 1); + + qCDebug(lcSched) << "splitting critical edge from node" << node->id() + << "to node" << node->input(inputIndex)->id() + << "by inserting jump node" << jump->id() + << "and region node" << region->id(); + + node->replaceInput(inputIndex, jump); + return jump; +} + +// See Chapter 6.3.1 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +std::vector<Node *> NodeScheduler::buildCFG() +{ + std::vector<Node *> roots; + roots.reserve(32); + NodeWorkList todo(m_irFunction->graph()); + + auto enqueueControlInputs = [this, &todo](Node *node) { + for (unsigned i = 0, ei = node->operation()->controlInputCount(); i != ei; ++i) { + const auto inputIndex = node->operation()->indexOfFirstControl() + i; + Node *input = node->input(inputIndex); + Q_ASSERT(input); + if (node->operation()->kind() == Meta::Region + && node->operation()->controlInputCount() > 1 + && isControlFlowSplit(input)) { + // critical edge! + input = splitEdge(m_irFunction, node, inputIndex); + m_live.markReachable(input); + m_live.markReachable(input->controlInput(0)); + } + if (!isBlockTerminator(input)) { + auto g = m_irFunction->graph(); + Node *jump = g->createNode(g->opBuilder()->get<Meta::Jump>(), &input, 1); + node->replaceInput(inputIndex, jump); + m_live.markReachable(jump); + qCDebug(lcSched) << "inserting jump node" << jump->id() + << "between node" << node->id() + << "and node" << input->id(); + input = jump; + } + todo.enqueue(input); + } + }; + + // create the CFG by scheduling control dependencies that start/end blocks: + todo.enqueue(m_irFunction->graph()->endNode()); + while (Node *node = todo.dequeueNextNodeForVisiting()) { + Q_ASSERT(isBlockTerminator(node)); + + if (schedulerData(node)->minimumBlock) + continue; + + MIBlock *b = m_miFunction->addBlock(); + + qCDebug(lcSched) << "scheduling node" << node->id() << "as terminator for new block" + << b->index(); + b->instructions().push_front(createMIInstruction(node)); + placeFixed(node, b, Schedule); + roots.push_back(node); + + if (Node *framestate = node->frameStateInput()) { + placeFixed(framestate, b, DontSchedule); + qCDebug(lcSched) << ".. also scheduling framestate dependency node" << node->id() + << "in block" << b->index(); + } + + if (node->opcode() == Meta::End) { + enqueueControlInputs(node); + continue; + } + + while (true) { + Node *controlDependency = node->controlInput(0); + if (!controlDependency) + break; + if (todo.isVisited(controlDependency)) + break; + if (schedulerData(controlDependency)->isFixed) + break; + + if (controlDependency->opcode() == Meta::Start) { + qCDebug(lcSched) << "placing start node" << controlDependency->id() + << "in block" << b->index(); + handleStartNode(controlDependency, b); + placeFixed(controlDependency, b, Schedule); + roots.push_back(controlDependency); + break; // we're done with this block + } + if (isBlockTerminator(controlDependency)) { + qCDebug(lcSched) << "found terminator node" << controlDependency->id() + << "for another block, so finish block" << b->index(); + Node *merge = m_irFunction->graph()->createNode( + m_irFunction->graph()->opBuilder()->getRegion(1), &controlDependency, 1); + node->replaceInput(node->operation()->indexOfFirstControl(), merge); + addBlockStart(roots, merge, b); + placeFixed(merge, b, Schedule); + m_live.markReachable(merge); + todo.enqueue(controlDependency); + break; // we're done with this block + } + if (canStartBlock(controlDependency) + || schedulerData(controlDependency->controlInput())->isFixed) { + qCDebug(lcSched) << "found block start node" << controlDependency->id() + << "for this block, so finish block" << b->index(); + addBlockStart(roots, controlDependency, b); + placeFixed(controlDependency, b, Schedule); + roots.push_back(controlDependency); + enqueueControlInputs(controlDependency); + break; // we're done with this block + } + qCDebug(lcSched) << "skipping node" << controlDependency->id(); + node = controlDependency; + } + } + + // link the edges of the MIBlocks, and add basic-block arguments: + for (MIBlock *toBlock : m_miFunction->blocks()) { + Q_ASSERT(!toBlock->instructions().empty()); + MIInstr &instr = toBlock->instructions().front(); + Node *toNode = instr.irNode(); + const auto opcode = toNode->operation()->kind(); + if (opcode == Meta::Region) { + unsigned inputNr = 0; + for (Node *input : toNode->inputs()) { + MIBlock *fromBlock = schedulerData(input)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + MIInstr &fromTerminator = fromBlock->instructions().back(); + if (fromTerminator.irNode()->opcode() == Meta::Jump || + fromTerminator.irNode()->opcode() == Meta::UnwindDispatch) { + unsigned arg = 0; + for (const MIOperand &bbArg : toBlock->arguments()) { + fromTerminator.setOperand(arg++, + createMIOperand(bbArg.irNode()->input(inputNr))); + } + } + ++inputNr; + } + } else if (opcode == Meta::End) { + for (Node *input : toNode->inputs()) { + MIBlock *fromBlock = schedulerData(input)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + } + } else if (Node *fromNode = toNode->controlInput()) { + MIBlock *fromBlock = schedulerData(fromNode)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + } + } + + m_irFunction->dump(QStringLiteral("graph after building CFG")); + + auto startBlock = schedulerData(m_irFunction->graph()->startNode())->minimumBlock; + m_miFunction->setStartBlock(startBlock); + + if (lcSched().isDebugEnabled()) + m_miFunction->dump(QStringLiteral("control flow graph before renumbering")); + m_miFunction->verifyCFG(); + + return roots; +} + +// See Chapter 6.3.3 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +void NodeScheduler::scheduleEarly(const std::vector<Node *> &roots) +{ + // scheduling one node might have the effect of queueing its dependencies + NodeWorkList todo(m_irFunction->graph()); + for (Node *root : roots) { + todo.enqueue(root); + while (Node *node = todo.dequeueNextNodeForVisiting()) + scheduleEarly(node, todo); + } +} + +void NodeScheduler::scheduleEarly(Node *node, NodeWorkList &todo) +{ + qCDebug(lcSched) << "Scheduling node" << node->id() << "early..."; + + SchedulerData *sd = schedulerData(node); + + if (sd->isFixed) { + // Fixed nodes already know their schedule early position. + qCDebug(lcSched) << ".. Fixed node" << node->id() << "is on minimum block" + << sd->minimumBlock->index() + << "which has dominator depth" + << m_dominatorDepthForBlock[sd->minimumBlock->index()]; + } + + for (Node *use : node->uses()) { + if (isLive(use)) + propagateMinimumPosition(sd->minimumBlock, use, todo); + else + qCDebug(lcSched) << ".. Skipping node" << use->id() << "as it's not live"; + } +} + +void NodeScheduler::propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, + NodeWorkList &todo) +{ + Q_ASSERT(newMinimumPosition); + + SchedulerData *sd = schedulerData(toNode); + if (sd->isFixed) // nothing to do + return; + + MIBlock::Index minimumBlockIndex = sd->minimumBlock + ? sd->minimumBlock->index() + : MIFunction::StartBlockIndex; + Q_ASSERT(m_domTree->insideSameDominatorChain(newMinimumPosition->index(), minimumBlockIndex)); + if (sd->minimumBlock == nullptr + || m_dominatorDepthForBlock[newMinimumPosition->index()] + > m_dominatorDepthForBlock[minimumBlockIndex]) { + // ok, some input for toNode is scheduled *after* our current minimum depth, so we need + // to adjust out minimal position. (This might involve rescheduling toNode's uses.) + place(toNode, newMinimumPosition); + todo.reEnqueue(toNode); + qCDebug(lcSched) << ".. Propagating minimum block" << sd->minimumBlock->index() + << "which has dominator depth" + << m_dominatorDepthForBlock[newMinimumPosition->index()] + << "to use node" << toNode->id(); + } else { + qCDebug(lcSched) << ".. Minimum position" << newMinimumPosition->index() + << "is not better than" << minimumBlockIndex + << "for node" << toNode->id(); + } +} + +// See Chapter 6.3.4 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +// +// There is one extra detail not described in the thesis mentioned above: loop hoisting. Before we +// place a node in the latest block that dominates all uses, we check if we accidentally sink it +// *into* a loop (meaning the latest block is inside a loop, where it is not if the earliest +// possible block would be chosen). If we detect that a nodes is going to sink into a loop, we walk +// the dominator path from the latest block up to the earliest block, and pick the first block that +// is in the same loop (if any) as the earlieast block. +// +// As noted in the thesis, this strategy might enlongen life times, which could be harmful for +// values that are simple to re-materialized or re-calculate. +void NodeScheduler::scheduleLate(const std::vector<Node *> &roots) +{ + NodeWorkList todo(m_irFunction->graph()); + for (Node *root : roots) + todo.enqueue(root); + + while (Node *node = todo.dequeueNextNodeForVisiting()) + scheduleNodeLate(node, todo); +} + +void NodeScheduler::scheduleNodeLate(Node *node, NodeWorkList &todo) +{ + if (!needsScheduling(node)) + return; + qCDebug(lcSched) << "Scheduling node" << node->id() << "late..."; + + auto sd = schedulerData(node); + if (sd->unscheduledUses == SchedulerData::NotYetCalculated) { + sd->unscheduledUses = 0; + for (Node *use : node->uses()) { + if (!isLive(use)) + continue; + if (!needsScheduling(use)) + continue; + if (schedulerData(use)->isFixed) + continue; + todo.enqueue(use); + ++sd->unscheduledUses; + } + } + + if (sd->isFixed) { + qCDebug(lcSched) << ".. it's fixed"; + enqueueInputs(node, todo); + return; + } + + if (sd->unscheduledUses) { + qCDebug(lcSched).noquote() << ".. not all uses are fixed, postpone it."<< todo.status(node); + return; + } + + MIBlock *&minBlock = sd->minimumBlock; + if (minBlock == nullptr) + minBlock = m_miFunction->block(MIFunction::StartBlockIndex); + MIBlock *commonUseDominator = commonDominatorOfUses(node); + qCDebug(lcSched) << ".. common use dominator: block" << commonUseDominator->index(); + + // the minBlock has to dominate the uses, *and* the common dominator of the uses. + Q_ASSERT(minBlock->index() == commonUseDominator->index() || + m_domTree->dominates(minBlock->index(), commonUseDominator->index())); + + // we now found the deepest block, so use it as the target block: + MIBlock *targetBlock = commonUseDominator; + + if (node->opcode() == Meta::FrameState) { + // never hoist framestates: they're used (among other things) to keep their inputs alive, so + // hoisting them out would end the life-time of those inputs prematurely + } else { + // but we want to prevent blocks sinking into loops unnecessary + MIBlock *hoistBlock = getHoistBlock(targetBlock); + while (hoistBlock + && m_dominatorDepthForBlock[hoistBlock->index()] + >= m_dominatorDepthForBlock[minBlock->index()]) { + qCDebug(lcSched) << ".. hoisting node" << node->id() << "from block" + << targetBlock->index() << "to block" << hoistBlock->index(); + // ok, so there a) is a hoist block and b) it's deeper than the minimum block, + // so lift it up one level ... + targetBlock = hoistBlock; + // ... and see if we can lift it one more level + hoistBlock = getHoistBlock(targetBlock); + } + } + + qCDebug(lcSched) << ".. fixating it in block" << targetBlock->index() + << "where the minimum block was" << minBlock->index(); + + placeFixed(node, targetBlock, DontSchedule); + enqueueInputs(node, todo); +} + +void NodeScheduler::enqueueInputs(Node *node, NodeWorkList &todo) +{ + for (Node *input : node->inputs()) { + if (!input) + continue; + if (!needsScheduling(input)) + continue; + if (!isLive(input)) + continue; + auto sd = schedulerData(input); + if (sd->isFixed) + continue; + qCDebug(lcSched).noquote() << "... enqueueing input node" << input->id() + << todo.status(input); + if (sd->unscheduledUses != SchedulerData::NotYetCalculated) { + if (sd->unscheduledUses > 0) + --sd->unscheduledUses; + if (sd->unscheduledUses == 0) + todo.reEnqueue(input); + } else { + todo.reEnqueue(input); + } + } +} + +Node *NodeScheduler::firstNotFixedUse(Node *node) +{ + for (Node *use : node->uses()) { + if (!isLive(use)) + continue; + if (!schedulerData(use)->isFixed) + return use; + } + return nullptr; +} + +MIBlock *NodeScheduler::commonDominatorOfUses(Node *node) +{ + MIBlock *commonDominator = nullptr; + for (auto useIt = node->uses().begin(), useEIt = node->uses().end(); useIt != useEIt; ++useIt) { + Node *use = *useIt; + if (!isLive(use)) + continue; + // region nodes use other nodes through their control dependency. But those nodes should + // already have been placed as block terminators before. + Q_ASSERT(use->opcode() != Meta::Region); + if (use->opcode() == Meta::Phi || use->opcode() == Meta::EffectPhi) { + // find the predecessor block defining this input + Node *region = use->controlInput(0); + Node *input = region->controlInput(useIt.inputIndex()); + use = input; + } + auto minBlock = schedulerData(use)->minimumBlock; + if (commonDominator == nullptr) + commonDominator = minBlock; + else + commonDominator = getCommonDominator(commonDominator, minBlock); + } + return commonDominator; +} + +void NodeScheduler::scheduleNodesInBlock() +{ + auto startBlock = m_miFunction->block(MIFunction::StartBlockIndex); + for (Node *n : m_live.reachable()) { + auto sd = schedulerData(n); + if (!sd->minimumBlock) + sd->minimumBlock = startBlock; + } + + std::vector<std::vector<SchedulerData *>> nodesForBlock; + nodesForBlock.resize(m_miFunction->blockCount()); + + for (auto sd : m_schedulerData) { + if (sd == nullptr) + continue; + if (!isLive(sd->node)) + continue; + sd->unscheduledUses = 0; + for (Node *use : sd->node->uses()) { + if (!needsScheduling(use)) + continue; + if (schedulerData(use)->isScheduledInBlock) + continue; + if (schedulerData(use)->minimumBlock == sd->minimumBlock) + ++sd->unscheduledUses; + } + if (sd->unscheduledUses == 0) + nodesForBlock[sd->minimumBlock->index()].push_back(sd); + } + + NodeWorkList todo(m_irFunction->graph()); + for (MIBlock *b : m_miFunction->blocks()) { + qCDebug(lcSched) << "Scheduling inside block" << b->index(); + MIInstr *insertionPoint = &b->instructions().back(); + todo.enqueue(insertionPoint->irNode()); + scheduleNodesInBlock(insertionPoint, b, todo); + Q_ASSERT(todo.isEmpty()); + for (auto sd : nodesForBlock[b->index()]) { + if (!sd->isScheduledInBlock) + todo.enqueue(sd->node); + } + scheduleNodesInBlock(insertionPoint, b, todo); + Q_ASSERT(todo.isEmpty()); + todo.reset(); + } +} + +void NodeScheduler::scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo) +{ + while (Node *n = todo.dequeueNextNodeForVisiting()) + scheduleNodeInBlock(n, insertionPoint, b, todo); +} + +void NodeScheduler::scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, + NodeWorkList &todo) +{ + Q_ASSERT(!node->isDead()); + + if (!isLive(node)) + return; + + if (!needsScheduling(node)) + return; + + auto nodeData = schedulerData(node); + if (nodeData->minimumBlock != b) + return; + + const bool wasAlreadyScheduled = nodeData->isScheduledInBlock; + if (!wasAlreadyScheduled) { + if (nodeData->unscheduledUses) + return; + + scheduleNodeNow(node, insertionPoint); + } + + if (Node *framestate = node->frameStateInput()) + scheduleNodeInBlock(framestate, insertionPoint, b, todo); + + for (Node *input : node->inputs()) { + if (!input) + continue; + if (!needsScheduling(input)) + continue; + if (!isLive(input)) + continue; + auto inputInfo = schedulerData(input); + if (inputInfo->isScheduledInBlock) + continue; + Q_ASSERT(inputInfo->minimumBlock != nullptr); + if (inputInfo->minimumBlock != b) + continue; + Q_ASSERT(!input->isDead()); + Q_ASSERT(inputInfo->unscheduledUses != SchedulerData::NotYetCalculated); + if (!wasAlreadyScheduled && inputInfo->unscheduledUses > 0) + --inputInfo->unscheduledUses; + if (inputInfo->unscheduledUses == 0) + todo.enqueue(input); + } +} + +void NodeScheduler::scheduleNodeNow(Node *node, MIInstr *&insertionPoint) +{ + qCDebug(lcSched) << ".. scheduling node" << node->id() + << "in block" << insertionPoint->parent()->index() + << "before node" << insertionPoint->irNode()->id(); + + MIInstr *newInstr = createMIInstruction(node); + newInstr->insertBefore(insertionPoint); + insertionPoint = newInstr; +} + +void NodeScheduler::place(Node *node, MIBlock *b) +{ + Q_ASSERT(!node->isDead()); + + if (b == nullptr) + return; + + schedulerData(node)->minimumBlock = b; +} + +void NodeScheduler::placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled) +{ + place(node, b); + auto sd = schedulerData(node); + Q_ASSERT(!sd->isFixed); + sd->isFixed = true; + sd->isScheduledInBlock = markScheduled == Schedule; +} + +unsigned NodeScheduler::vregForNode(Node *node) +{ + unsigned &vreg = m_vregs[unsigned(node->id())]; + if (vreg == std::numeric_limits<unsigned>::max()) + vreg = m_nextVReg++; + return vreg; +} + +void NodeScheduler::addBlockStart(std::vector<Node *> &roots, Node *startNode, MIBlock *block) +{ + block->instructions().insert(block->instructions().begin(), createMIInstruction(startNode)); + if (startNode->opcode() == Meta::Region) { + for (Node *use : startNode->uses()) { + if (use->opcode() == Meta::Phi && isLive(use)) { + block->addArgument(MIOperand::createVirtualRegister(use, vregForNode(use))); + placeFixed(use, block, Schedule); + roots.push_back(use); + } else if (use->opcode() == Meta::EffectPhi && isLive(use)) { + placeFixed(use, block, Schedule); + roots.push_back(use); + } + } + } +} + +void NodeScheduler::handleStartNode(Node *startNode, MIBlock *startBlock) +{ + startBlock->instructions().push_front(createMIInstruction(startNode)); + + QVarLengthArray<Node *, 32> args; + for (Node *use : startNode->uses()) { + switch (use->opcode()) { + case Meta::Engine: Q_FALLTHROUGH(); + case Meta::CppFrame: + case Meta::Function: + placeFixed(use, startBlock, Schedule); + break; + case Meta::Parameter: { + auto param = ParameterPayload::get(*use->operation()); + int idx = int(param->parameterIndex()); + if (args.size() <= idx) + args.resize(idx + 1); + args[int(idx)] = use; + placeFixed(use, startBlock, Schedule); + } + break; + default: + break; + } + } + + for (unsigned i = 0, ei = unsigned(args.size()); i != ei; ++i) { + if (Node *arg = args.at(int(i))) + startBlock->addArgument(MIOperand::createJSStackSlot(arg, i)); + } +} + +static Node *firstControlOutput(Node *n) +{ + for (auto it = n->uses().begin(), eit = n->uses().end(); it != eit; ++it) { + if (it.isUsedAsControl()) + return *it; + } + return nullptr; +} + +MIInstr *NodeScheduler::createMIInstruction(Node *node) +{ + const auto opcode = node->operation()->kind(); + + unsigned nArgs = 0; + switch (opcode) { + case Meta::UnwindDispatch: Q_FALLTHROUGH(); + case Meta::Jump: { + Node *target = firstControlOutput(node); + if (target->opcode() == Meta::Region) { + for (Node *n : target->uses()) { + if (n->opcode() == Meta::Phi && isLive(n)) + ++nArgs; + } + } + } + break; + case Meta::Branch: + nArgs = 1; + break; + case Meta::Return: + nArgs = 1; + break; + default: + nArgs = node->operation()->valueInputCount(); + break; + } + + MIInstr *instr = MIInstr::create(m_irFunction->pool(), node, nArgs); + for (unsigned i = 0, ei = node->operation()->valueInputCount(); i != ei; ++i) + instr->setOperand(i, createMIOperand(node->input(i))); + if (node->opcode() != Meta::Start && node->operation()->valueOutputCount() > 0) + instr->setDestination(createMIOperand(node)); + + schedulerData(node)->isScheduledInBlock = true; + return instr; +} + +MIOperand NodeScheduler::createMIOperand(Node *node) +{ + if (node->operation()->isConstant()) + return MIOperand::createConstant(node); + + auto opcode = node->operation()->kind(); + switch (opcode) { + case Meta::Parameter: + return MIOperand::createJSStackSlot( + node, unsigned(ParameterPayload::get(*node->operation())->parameterIndex())); + case Meta::Engine: + return MIOperand::createEngineRegister(node); + case Meta::CppFrame: + return MIOperand::createCppFrameRegister(node); + case Meta::Function: + return MIOperand::createFunction(node); + default: + if ((node->opcode() == Meta::Call + && CallPayload::get(*node->operation())->callee() == Meta::JSThisToObject) + || node->opcode() == Meta::StoreThis) { + return MIOperand::createJSStackSlot(node, CallData::This); + } else { + return MIOperand::createVirtualRegister(node, vregForNode(node)); + } + } +} + +void NodeScheduler::showNodesByBlock(const QString &description) const +{ + if (!lcSched().isDebugEnabled()) + return; + + qCDebug(lcSched) << description; + + for (MIBlock *b : m_miFunction->blocks()) { + QString s; + for (const SchedulerData *sd : m_schedulerData) { + if (!sd) + continue; + if (!isLive(sd->node)) + continue; + if (sd->minimumBlock == b) { + if (!s.isEmpty()) + s += QStringLiteral(", "); + s += QStringLiteral("%1 (%2)").arg(QString::number(sd->node->id()), + sd->node->operation()->debugString()); + } + } + if (s.isEmpty()) + s = QStringLiteral("<<none>>"); + qCDebug(lcSched, "Nodes in block %u: %s", b->index(), s.toUtf8().constData()); + } +} + +void NodeScheduler::dumpDotCFG() const +{ + QString out; + out += QLatin1Char('\n'); + out += QStringLiteral("digraph{root=\"L%1\" label=\"Control Flow Graph\";" + "node[shape=circle];edge[dir=forward fontsize=10]\n") + .arg(MIFunction::StartBlockIndex); + for (MIBlock *src : m_miFunction->blocks()) { + for (MIBlock *dst : src->outEdges()) { + out += QStringLiteral("L%1->L%2\n").arg(QString::number(src->index()), + QString::number(dst->index())); + } + } + out += QStringLiteral("}\n"); + qCDebug(lcDotCFG).nospace().noquote() << out; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4schedulers_p.h b/src/qml/jit/qv4schedulers_p.h new file mode 100644 index 0000000000..f9179816df --- /dev/null +++ b/src/qml/jit/qv4schedulers_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4SCHEDULER_P_H +#define QV4SCHEDULER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4global_p.h" +#include "qv4mi_p.h" +#include "qv4node_p.h" +#include "qv4domtree_p.h" +#include "qv4loopinfo_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// Node scheduling "flattens" the graph into basic blocks with an ordered list of instructions. +// +// The various steps are mentioned in buildMIFunction, but the general idea is described in +// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf in chapter 6. +class NodeScheduler final +{ + Q_DISABLE_COPY_MOVE(NodeScheduler) + + class SchedulerData final { + Q_DISABLE_COPY_MOVE(SchedulerData) + public: + static SchedulerData *create(QQmlJS::MemoryPool *pool) + { return pool->New<SchedulerData>(); } + + SchedulerData() = default; + ~SchedulerData() = default; + + Node *node = nullptr; + MIBlock *minimumBlock = nullptr; + bool isFixed = false; + bool isScheduledInBlock = false; + static constexpr unsigned NotYetCalculated = std::numeric_limits<unsigned>::max(); + unsigned unscheduledUses = NotYetCalculated; + }; + +public: + NodeScheduler(Function *irFunction); + ~NodeScheduler() = default; + + MIFunction *buildMIFunction(); + +private: + std::vector<Node *> buildCFG(); + void scheduleEarly(const std::vector<Node *> &roots); + void scheduleEarly(Node *node, NodeWorkList &todo); + void propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, NodeWorkList &todo); + void scheduleLate(const std::vector<Node *> &roots); + void scheduleNodeLate(Node *node, NodeWorkList &todo); + void enqueueInputs(Node *node, NodeWorkList &todo); + Node *firstNotFixedUse(Node *node); + MIBlock *commonDominatorOfUses(Node *node); + void scheduleNodesInBlock(); + void scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); + void scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); + void scheduleNodeNow(Node *node, MIInstr *&insertionPoint); + + void place(Node *node, MIBlock *b); + enum ScheduleOrNot { DontSchedule, Schedule }; + void placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled); + unsigned vregForNode(Node *node); + void addBlockStart(std::vector<Node *> &roots, Node *startNode, MIBlock *block); + void enqueueControlInputs(Node *node); + void handleStartNode(Node *startNode, MIBlock *startBlock); + MIInstr *createMIInstruction(Node *node); + MIOperand createMIOperand(Node *node); + SchedulerData *schedulerData(Node *n) + { + if (Q_UNLIKELY(m_schedulerData.size() <= n->id())) + m_schedulerData.resize(n->id() + 8); + SchedulerData *&sd = m_schedulerData[n->id()]; + if (Q_UNLIKELY(sd == nullptr)) { + sd = SchedulerData::create(m_irFunction->pool()); + sd->node = n; + } + return sd; + } + bool isLive(Node *n) const + { return m_live.isReachable(n->id()); } + bool canStartBlock(Node *node) const; + bool isControlFlowSplit(Node *node) const; + bool isBlockTerminator(Node *node) const; + MIBlock *getCommonDominator(MIBlock *one, MIBlock *other) const; + MIBlock *getHoistBlock(MIBlock *block) const; + + void showNodesByBlock(const QString &description) const; + + void dumpDotCFG() const; + +private: + Function *m_irFunction = nullptr; + MIFunction *m_miFunction = nullptr; + QScopedPointer<LoopInfo> m_loopInfo; + QScopedPointer<DominatorTree> m_domTree; + std::vector<int> m_dominatorDepthForBlock; + std::vector<unsigned> m_vregs; + std::vector<SchedulerData *> m_schedulerData; + NodeCollector m_live; + unsigned m_nextVReg = 0; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4SCHEDULER_P_H diff --git a/src/qml/jit/qv4tracingjit.cpp b/src/qml/jit/qv4tracingjit.cpp new file mode 100644 index 0000000000..c8974b3a1b --- /dev/null +++ b/src/qml/jit/qv4tracingjit.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4vme_moth_p.h" +#include "qv4graphbuilder_p.h" +#include "qv4lowering_p.h" +#include "qv4mi_p.h" +#include "qv4schedulers_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTracing, "qt.v4.tracing") + +namespace QV4 { + +// This is the entry point for the "tracing JIT". It uses the sea-of-nodes concept as described in +// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf +// +// The minimal pipeline is as follows: +// - create the graph for the function +// - do generic lowering +// - schedule the nodes +// - run minimal stack slot allocation (no re-use of slots) +// - run the assembler +// +// This pipeline has no optimizations, and generates quite inefficient code. It does have the +// advantage that no trace information is used, so it can be used for testing where it replaces +// the baseline JIT. Any optimizations are additions to this pipeline. +// +// Note: generators (or resuming functions in general) are not supported by this JIT. +void Moth::runTracingJit(QV4::Function *function) +{ + IR::Function irFunction(function); + qCDebug(lcTracing).noquote() << "runTracingJit called for" << irFunction.name() << "..."; + + qCDebug(lcTracing).noquote().nospace() << function->traceInfoToString(); + + IR::GraphBuilder::buildGraph(&irFunction); + irFunction.dump(QStringLiteral("initial IR")); + irFunction.verify(); + + IR::GenericLowering(irFunction).lower(); + irFunction.dump(QStringLiteral("after generic lowering")); + irFunction.verify(); + + IR::NodeScheduler scheduler(&irFunction); + QScopedPointer<IR::MIFunction> miFunction(scheduler.buildMIFunction()); + miFunction->dump(QStringLiteral("initial MI")); + irFunction.verify(); +} + +} // QV4 namespace +QT_END_NAMESPACE |