Description: Add optional JIT unit cache This patch adds a JIT unit cache to QML that speeds up application launches after the first launch has been done. It is activated only when environment variable QV4_ENABLE_JIT_CACHE is set. . It has not yet been sent upstream for discussion, but that's being planned. Author: Ricardo Mendoza Origin: Canonical Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/qtdeclarative-opensource-src/+bug/1418060 Forwarded: No Last-Update: 2015-02-25 Index: qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/ARMv7Assembler.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/3rdparty/masm/assembler/ARMv7Assembler.h +++ qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/ARMv7Assembler.h @@ -445,6 +445,12 @@ public: ConditionInvalid } Condition; + // Code inherited from the QMLC project + void appendData(char *data, int len) + { + return m_formatter.appendData(data, len); + } + #define JUMP_ENUM_WITH_SIZE(index, value) (((value) << 3) | (index)) #define JUMP_ENUM_SIZE(jump) ((jump) >> 3) enum JumpType { JumpFixed = JUMP_ENUM_WITH_SIZE(0, 0), @@ -2782,6 +2788,7 @@ private: AssemblerLabel label() const { return m_buffer.label(); } bool isAligned(int alignment) const { return m_buffer.isAligned(alignment); } void* data() const { return m_buffer.data(); } + void appendData(char *data, int len) { return m_buffer.appendData(data, len); } unsigned debugOffset() { return m_buffer.debugOffset(); } Index: qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/AbstractMacroAssembler.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/3rdparty/masm/assembler/AbstractMacroAssembler.h +++ qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/AbstractMacroAssembler.h @@ -486,6 +486,11 @@ public: return Call(jump.m_label, Linkable); } + unsigned int getFlags(void) + { + return m_flags; + } + AssemblerLabel m_label; private: Flags m_flags; @@ -746,6 +751,12 @@ public: { AssemblerType::cacheFlush(code, size); } + + void appendData(char *data, int len) + { + return m_assembler.appendData(data, len); + } + protected: AbstractMacroAssembler() : m_randomSource(cryptographicallyRandomNumber()) Index: qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/AssemblerBuffer.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/3rdparty/masm/assembler/AssemblerBuffer.h +++ qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/AssemblerBuffer.h @@ -86,6 +86,14 @@ namespace JSC { grow(); } + void appendData(char *data, int len) + { + if (!isAvailable(len)) + grow(len); + memcpy(m_buffer + m_index, data, len); + m_index += len; + } + bool isAligned(int alignment) const { return !(m_index & (alignment - 1)); Index: qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/LinkBuffer.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/3rdparty/masm/assembler/LinkBuffer.h +++ qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/LinkBuffer.h @@ -109,11 +109,13 @@ public: // These methods are used to link or set values at code generation time. - void link(Call call, FunctionPtr function) + unsigned int link(Call call, FunctionPtr function) { ASSERT(call.isFlagSet(Call::Linkable)); call.m_label = applyOffset(call.m_label); MacroAssembler::linkCall(code(), call, function); + + return call.m_label.m_offset; } void link(Jump jump, CodeLocationLabel label) Index: qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/X86Assembler.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/3rdparty/masm/assembler/X86Assembler.h +++ qtdeclarative-opensource-src-5.4.0/src/3rdparty/masm/assembler/X86Assembler.h @@ -101,6 +101,11 @@ public: ConditionNC = ConditionAE, } Condition; + void appendData(char *data, int len) + { + return m_formatter.appendData(data, len); + } + private: typedef enum { OP_ADD_EvGv = 0x01, @@ -2380,6 +2385,7 @@ private: AssemblerLabel label() const { return m_buffer.label(); } bool isAligned(int alignment) const { return m_buffer.isAligned(alignment); } void* data() const { return m_buffer.data(); } + void appendData(char *data, int len) { return m_buffer.appendData(data, len); } PassRefPtr executableCopy(JSGlobalData& globalData, void* ownerUID, JITCompilationEffort effort) { Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/compiler.pri =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/compiler.pri +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/compiler.pri @@ -28,10 +28,10 @@ HEADERS += \ $$PWD/qv4isel_moth_p.h \ $$PWD/qv4instr_moth_p.h - SOURCES += \ $$PWD/qqmltypecompiler.cpp \ $$PWD/qv4instr_moth.cpp \ $$PWD/qv4isel_moth.cpp +DEFINES += V4_UNIT_CACHE } Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmlirbuilder.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qqmlirbuilder.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmlirbuilder.cpp @@ -1502,7 +1502,12 @@ bool IRBuilder::isStatementNodeScript(QQ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) { QQmlRefPointer compilationUnit = output.javaScriptCompilationUnit; - QV4::CompiledData::Unit *jsUnit = compilationUnit->createUnitData(&output); + QV4::CompiledData::Unit *jsUnit; + if (!compilationUnit->data) + jsUnit = compilationUnit->createUnitData(&output); + else + jsUnit = compilationUnit->data; + const uint unitSize = jsUnit->unitSize; const int importSize = sizeof(QV4::CompiledData::Import) * output.imports.count(); @@ -1527,7 +1532,6 @@ QV4::CompiledData::Unit *QmlUnitGenerato memcpy(data, jsUnit, unitSize); if (jsUnit != compilationUnit->data) free(jsUnit); - jsUnit = 0; QV4::CompiledData::Unit *qmlUnit = reinterpret_cast(data); qmlUnit->unitSize = totalSize; @@ -1538,8 +1542,22 @@ QV4::CompiledData::Unit *QmlUnitGenerato qmlUnit->offsetToObjects = unitSize + importSize; qmlUnit->nObjects = output.objects.count(); qmlUnit->indexOfRootObject = output.indexOfRootObject; + +#ifdef ENABLE_UNIT_CACHE + if (compilationUnit->isRestored) { + qmlUnit->offsetToStringTable = jsUnit->offsetToStringTable; + qmlUnit->stringTableSize = jsUnit->stringTableSize; + } else { + qmlUnit->offsetToStringTable = totalSize - output.jsGenerator.stringTable.sizeOfTableAndData(); + qmlUnit->stringTableSize = output.jsGenerator.stringTable.stringCount(); + } +#else qmlUnit->offsetToStringTable = totalSize - output.jsGenerator.stringTable.sizeOfTableAndData(); qmlUnit->stringTableSize = output.jsGenerator.stringTable.stringCount(); +#endif + + // Release + jsUnit = 0; // write imports char *importPtr = data + qmlUnit->offsetToImports; @@ -1631,7 +1649,12 @@ QV4::CompiledData::Unit *QmlUnitGenerato } } +#ifdef ENABLE_UNIT_CACHE + if (!compilationUnit->isRestored) + output.jsGenerator.stringTable.serialize(qmlUnit); +#else output.jsGenerator.stringTable.serialize(qmlUnit); +#endif return qmlUnit; } Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmltypecompiler.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qqmltypecompiler.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmltypecompiler.cpp @@ -219,6 +219,7 @@ bool QQmlTypeCompiler::compile() QScopedPointer isel(v4->iselFactory->create(engine, v4->executableAllocator, &document->jsModule, &document->jsGenerator)); isel->setUseFastLookups(false); isel->setUseTypeInference(true); + isel->setEngine(engine); document->javaScriptCompilationUnit = isel->compile(/*generated unit data*/false); } @@ -419,6 +420,7 @@ QQmlPropertyCacheCreator::QQmlPropertyCa , qmlObjects(*typeCompiler->qmlObjects()) , imports(typeCompiler->imports()) , resolvedTypes(typeCompiler->resolvedTypes()) + , m_url(typeCompiler->url()) { } Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmltypecompiler_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qqmltypecompiler_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qqmltypecompiler_p.h @@ -140,6 +140,7 @@ protected: QHash *resolvedTypes; QVector vmeMetaObjects; QVector propertyCaches; + QUrl m_url; }; // "Converts" signal expressions to full-fleged function declarations with Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4compileddata_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qv4compileddata_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4compileddata_p.h @@ -594,6 +594,11 @@ struct Q_QML_PRIVATE_EXPORT CompilationU QVector runtimeFunctions; mutable QQmlNullableValue m_url; +#ifdef ENABLE_UNIT_CACHE + QVector lookupTable; + bool isRestored; +#endif + QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_moth_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qv4isel_moth_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_moth_p.h @@ -64,9 +64,11 @@ public: ~InstructionSelection(); virtual void run(int functionIndex); + virtual QV4::JIT::InstructionSelection* impl() { return NULL; }; protected: virtual QQmlRefPointer backendCompileStep(); + virtual QV4::CompiledData::CompilationUnit* mutableCompilationUnit(); virtual void visitJump(IR::Jump *); virtual void visitCJump(IR::CJump *); Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_p.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qv4isel_p.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_p.cpp @@ -3,6 +3,12 @@ ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** +** Copyright (C) 2015 Nomovok Ltd. All rights reserved. +** Contact: info@nomovok.com +** +** Copyright (C) 2015 Canonical Limited and/or its subsidiary(-ies). +** Contact: ricardo.mendoza@canonical.com +** ** This file is part of the QtQml module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ @@ -41,9 +47,65 @@ #include +#ifndef V4_UNIT_CACHE +#undef ENABLE_UNIT_CACHE +#endif + +#ifdef ENABLE_UNIT_CACHE +#include +#include +#include +#include +#include +#include "../jit/qv4cachedlinkdata_p.h" +#include "../jit/qv4assembler_p.h" +#include +#include +#include +#include +#include +#include +#include +#endif + +bool writeData(QDataStream& stream, const char* data, int len) +{ + if (stream.writeRawData(data, len) != len) + return false; + else + return true; +} + +bool writeDataWithLen(QDataStream& stream, const char* data, int len) +{ + quint32 l = len; + if (!writeData(stream, (const char *)&l, sizeof(quint32))) + return false; + if (!writeData(stream, data, len)) + return false; + return true; +} + +bool readData(char *data, int len, QDataStream &stream) +{ + if (stream.readRawData(data, len) != len) { + return false; + } else { + return true; + } +} + using namespace QV4; using namespace QV4::IR; +static bool do_cache = false; + +enum CacheState { + UNTESTED = 0, + VALID = 1, + INVALID = 2 +}; + EvalInstructionSelection::EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) : useFastLookups(true) , useTypeInference(true) @@ -64,6 +126,9 @@ EvalInstructionSelection::EvalInstructio Q_ASSERT(execAllocator); #endif Q_ASSERT(module); + + // Enable JIT cache only when explicitly requested and only cache files-on-disk (no qrc or inlines) + do_cache = !qgetenv("QV4_ENABLE_JIT_CACHE").isEmpty() && irModule->fileName.startsWith(QStringLiteral("file://")); } EvalInstructionSelection::~EvalInstructionSelection() @@ -72,15 +137,307 @@ EvalInstructionSelection::~EvalInstructi EvalISelFactory::~EvalISelFactory() {} -QQmlRefPointer EvalInstructionSelection::compile(bool generateUnitData) +QQmlRefPointer EvalInstructionSelection::runAll(bool generateUnitData) { - for (int i = 0; i < irModule->functions.size(); ++i) - run(i); + for (int i = 0; i < irModule->functions.size(); ++i) { + run(i); // Performs the actual compilation + } + + QQmlRefPointer result = backendCompileStep(); + +#ifdef ENABLE_UNIT_CACHE + result->isRestored = false; +#endif - QQmlRefPointer unit = backendCompileStep(); if (generateUnitData) + result->data = jsGenerator->generateUnit(); + + return result; +} + +QQmlRefPointer EvalInstructionSelection::compile(bool generateUnitData) +{ +#ifndef ENABLE_UNIT_CACHE + return runAll(generateUnitData); +#else + QQmlRefPointer result(nullptr); + + // Check if running JIT mode and if cache is enabled + if (!do_cache || !this->impl()) { + return runAll(generateUnitData); + } + + QV4::CompiledData::CompilationUnit *unit; + bool loaded = false; + bool do_save = true; + + QByteArray path(qgetenv("HOME") + QByteArray("/.cache/QML/Apps/") + (qgetenv("APP_ID").isEmpty() ? QCoreApplication::applicationName().toLatin1() : qgetenv("APP_ID"))); + + if (m_engine && m_engine->qmlCacheValid == CacheState::UNTESTED) { + bool valid = true; + QDir cacheDir; + cacheDir.setPath(QLatin1String(path)); + QStringList files = cacheDir.entryList(); + for (int i = 0; i < files.size(); i++) { + if (valid == false) + break; + + QFile cacheFile(path + QDir::separator() + files.at(i)); + if (cacheFile.open(QIODevice::ReadOnly)) { + QDataStream stream(&cacheFile); + quint32 strLen = 0; + readData((char *)&strLen, sizeof(quint32), stream); + if (strLen == 0) { + cacheFile.close(); + continue; + } + + char *tmpStr = (char *) malloc(strLen); + readData((char *)tmpStr, strLen, stream); + quint32 mtime = 0; + readData((char *)&mtime, sizeof(quint32), stream); + + struct stat sb; + stat(tmpStr, &sb); + if (QFile::exists(QLatin1String(tmpStr))) { + QByteArray time(ctime(&sb.st_mtime)); + if (mtime != (quint32) sb.st_mtime) { + if (m_engine) m_engine->qmlCacheValid = CacheState::INVALID; + valid = false; + } + } else { + // Compilation unit of unresolvable type (inline), remove + cacheFile.remove(); + } + + free(tmpStr); + cacheFile.close(); + } + } + if (valid) { + m_engine->qmlCacheValid = CacheState::VALID; + } else { + for (int i = 0; i < files.size(); i++) + cacheDir.remove(files.at(i)); + } + } + + // Search for cache blob by mtime/app_id/file hash + struct stat sb; + stat(irModule->fileName.toLatin1().remove(0, 7).constData(), &sb); + QByteArray time(ctime(&sb.st_mtime)); + QByteArray urlHash(QCryptographicHash::hash((irModule->fileName.toLatin1() + qgetenv("APP_ID") + time), QCryptographicHash::Md5).toHex()); + QDir dir; + dir.mkpath(QLatin1String(path)); + QFile cacheFile(path + QDir::separator() + urlHash); + + QByteArray fileData; + + // TODO: Support inline compilation units + if (cacheFile.exists() && cacheFile.open(QIODevice::ReadOnly)) { + if (!irModule->fileName.isEmpty() && !irModule->fileName.contains(QStringLiteral("inline")) && m_engine) { + loaded = true; + fileData.append(cacheFile.readAll()); + } else { + loaded = false; + cacheFile.close(); + return runAll(generateUnitData); + } + } + + // Check file integrity + QString fileHash(QLatin1String(QCryptographicHash::hash(fileData.left(fileData.size()-32), QCryptographicHash::Md5).toHex())); + if (!fileHash.contains(QLatin1String(fileData.right(32)))) { + cacheFile.close(); + cacheFile.remove(); + loaded = false; + } + + // This code has been inspired and influenced by Nomovok's QMLC compiler available at + // https://github.com/qmlc/qmlc. All original Copyrights are maintained for the + // basic code snippets. + if (loaded) { + // Retrieve unit skeleton from isel implementation + unit = mutableCompilationUnit(); + QV4::JIT::CompilationUnit *tmpUnit = (QV4::JIT::CompilationUnit *) unit; + tmpUnit->codeRefs.resize(irModule->functions.size()); + + QDataStream stream(fileData); + + quint32 strLen = 0; + readData((char *)&strLen, sizeof(quint32), stream); + char *tmpStr = (char *) malloc(strLen); + readData((char *)tmpStr, strLen, stream); + quint32 mtime = 0; + readData((char *)&mtime, sizeof(quint32), stream); + + unit->lookupTable.reserve(tmpUnit->codeRefs.size()); + quint32 len; + for (int i = 0; i < tmpUnit->codeRefs.size(); i++) { + quint32 strLen; + readData((char *)&strLen, sizeof(quint32), stream); + + char *fStr = (char *) malloc(strLen); + readData(fStr, strLen, stream); + + QString hashString(QLatin1String(irModule->functions.at(i)->name->toLatin1().constData())); + hashString.append(QString::number(irModule->functions.at(i)->line)); + hashString.append(QString::number(irModule->functions.at(i)->column)); + + if (!hashString.contains(QLatin1String(fStr))) + return runAll(generateUnitData); + + unit->lookupTable.append(i); + + len = 0; + readData((char *)&len, sizeof(quint32), stream); + + // Temporary unlinked code buffer + executableAllocator->allocate(len); + char *data = (char *) malloc(len); + readData(data, len, stream); + + quint32 linkCallCount = 0; + readData((char *)&linkCallCount, sizeof(quint32), stream); + + QVector linkCalls; + linkCalls.resize(linkCallCount); + readData((char *)linkCalls.data(), linkCallCount * sizeof(QV4::JIT::CachedLinkData), stream); + + quint32 constVectorLen = 0; + readData((char *)&constVectorLen, sizeof(quint32), stream); + + QVector constantVector; + if (constVectorLen > 0) { + constantVector.resize(constVectorLen); + readData((char *)constantVector.data(), constVectorLen * sizeof(QV4::Primitive), stream); + } + + // Pre-allocate link buffer to append code + QV4::ExecutableAllocator* executableAllocator = m_engine->v4engine()->executableAllocator; + + QV4::IR::Function nullFunction(0, 0, QLatin1String("")); + + QV4::JIT::Assembler* as = new QV4::JIT::Assembler(this->impl(), &nullFunction, executableAllocator); + + QList& callsToLink = as->callsToLink(); + for (int i = 0; i < linkCalls.size(); i++) { + QV4::JIT::CachedLinkData& call = linkCalls[i]; + void *functionPtr = CACHED_LINK_TABLE[call.index].addr; + QV4::JIT::Assembler::CallToLink c; + JSC::AssemblerLabel label(call.offset); + c.call = QV4::JIT::Assembler::Call(label, QV4::JIT::Assembler::Call::Linkable); + c.externalFunction = JSC::FunctionPtr((quint32(*)(void))functionPtr); + c.functionName = CACHED_LINK_TABLE[call.index].name; + callsToLink.append(c); + } + + QV4::JIT::Assembler::ConstantTable& constTable = as->constantTable(); + foreach (const QV4::Primitive &p, constantVector) + constTable.add(p); + + as->appendData(data, len); + + int dummySize = -1; // Pass known value to trigger use of code buffer + tmpUnit->codeRefs[i] = as->link(&dummySize); + Q_ASSERT(dummySize == (int)codeRefLen); + + delete as; + } + + quint32 size = 0; + readData((char *)&size, sizeof(quint32), stream); + + void *dataPtr = malloc(size); + QV4::CompiledData::Unit *finalUnit = reinterpret_cast(dataPtr); + if (size > 0) + readData((char *)dataPtr, size, stream); + + result = backendCompileStep(); + unit = result.data(); + + unit->data = nullptr; + if (irModule->functions.size() > 0) + unit->data = finalUnit; + unit->isRestored = true; + } else { + // Not loading from cache, run all instructions + result = runAll(false); + unit = result.data(); + } + + if ((unit->data == nullptr) && (do_save || generateUnitData)) unit->data = jsGenerator->generateUnit(); - return unit; + + // Save compilation unit + QV4::JIT::CompilationUnit *jitUnit = (QV4::JIT::CompilationUnit *) unit; + if (!loaded) { + if (cacheFile.open(QIODevice::WriteOnly)) { + // TODO: Support inline compilation units + if (!irModule->fileName.isEmpty() && !irModule->fileName.contains(QLatin1String("inline")) && m_engine) { + QBuffer fillBuff; + fillBuff.open(QIODevice::WriteOnly); + QDataStream stream(&fillBuff); + + quint32 fileNameSize = irModule->fileName.size(); + writeData(stream, (const char *)&fileNameSize, sizeof(quint32)); + writeData(stream, (const char *)irModule->fileName.toLatin1().remove(0, 7).constData(), irModule->fileName.size()); + + struct stat sb; + stat(irModule->fileName.toLatin1().remove(0, 7).constData(), &sb); + writeData(stream, (const char *)&sb.st_mtime, sizeof(quint32)); + + for (int i = 0; i < jitUnit->codeRefs.size(); i++) { + const JSC::MacroAssemblerCodeRef &codeRef = jitUnit->codeRefs[i]; + const QVector &constantValue = jitUnit->constantValues[i]; + quint32 len = codeRef.size(); + + QString hashString(QLatin1String(irModule->functions.at(i)->name->toLatin1())); + hashString.append(QString::number(irModule->functions.at(i)->line)); + hashString.append(QString::number(irModule->functions.at(i)->column)); + + quint32 strLen = hashString.size(); + strLen += 1; // /0 char + writeData(stream, (const char *)&strLen, sizeof(quint32)); + writeData(stream, (const char *)hashString.toLatin1().constData(), strLen); + writeData(stream, (const char *)&len, sizeof(quint32)); + writeData(stream, (const char *)(((unsigned long)codeRef.code().executableAddress())&~1), len); + + const QVector &linkCalls = jitUnit->linkData[i]; + quint32 linkCallCount = linkCalls.size(); + + writeData(stream, (const char *)&linkCallCount, sizeof(quint32)); + if (linkCallCount > 0) + writeData(stream, (const char *)linkCalls.data(), linkCalls.size() * sizeof (QV4::JIT::CachedLinkData)); + + quint32 constTableCount = constantValue.size(); + writeData(stream, (const char *)&constTableCount, sizeof(quint32)); + + if (constTableCount > 0) + writeData(stream, (const char*)constantValue.data(), sizeof(QV4::Primitive) * constantValue.size()); + } + + QV4::CompiledData::Unit *retUnit = unit->data; + quint32 size = retUnit->unitSize; + + if (size > 0) { + writeData(stream, (const char*)&size, sizeof(quint32)); + writeData(stream, (const char*)retUnit, size); + } + + // Write MD5 hash of stored data for consistency check + QByteArray fileHash(QCryptographicHash::hash(fillBuff.data(), QCryptographicHash::Md5).toHex()); + fillBuff.write(fileHash); + cacheFile.write(fillBuff.data()); + } + cacheFile.close(); + } else { + cacheFile.close(); + } + } + + return result; +#endif } void IRDecoder::visitMove(IR::Move *s) Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qv4isel_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_p.h @@ -48,6 +48,10 @@ class QQmlEnginePrivate; namespace QV4 { +namespace JIT { + class InstructionSelection; +} + class ExecutableAllocator; struct Function; @@ -57,6 +61,7 @@ public: EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator); virtual ~EvalInstructionSelection() = 0; + QQmlRefPointer runAll(bool generateUnitData); QQmlRefPointer compile(bool generateUnitData = true); void setUseFastLookups(bool b) { useFastLookups = b; } @@ -71,10 +76,13 @@ public: int registerRegExp(IR::RegExp *regexp) { return jsGenerator->registerRegExp(regexp); } int registerJSClass(int count, IR::ExprList *args) { return jsGenerator->registerJSClass(count, args); } QV4::Compiler::JSUnitGenerator *jsUnitGenerator() const { return jsGenerator; } + void setEngine(QQmlEnginePrivate *qmlEngine) { m_engine = qmlEngine; } protected: virtual void run(int functionIndex) = 0; + virtual QV4::JIT::InstructionSelection* impl() = 0; virtual QQmlRefPointer backendCompileStep() = 0; + virtual QV4::CompiledData::CompilationUnit* mutableCompilationUnit() = 0; bool useFastLookups; bool useTypeInference; @@ -82,6 +90,7 @@ protected: QV4::Compiler::JSUnitGenerator *jsGenerator; QScopedPointer ownJSGenerator; IR::Module *irModule; + QQmlEnginePrivate *m_engine; }; class Q_QML_PRIVATE_EXPORT EvalISelFactory Index: qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4assembler.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jit/qv4assembler.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4assembler.cpp @@ -69,7 +69,15 @@ void CompilationUnit::linkBackendToEngin QV4::Function *runtimeFunction = new QV4::Function(engine, this, compiledFunction, (ReturnedValue (*)(QV4::ExecutionEngine *, const uchar *)) codeRefs[i].code().executableAddress()); - runtimeFunctions[i] = runtimeFunction; + +#if ENABLE_UNIT_CACHE + if (isRestored) + runtimeFunctions[lookupTable.at(i)] = runtimeFunction; + else + runtimeFunctions[i] = runtimeFunction; +#else + runtimeFunctions[i] = runtimeFunction; +#endif } } Index: qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4assembler_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jit/qv4assembler_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4assembler_p.h @@ -63,6 +63,16 @@ namespace JIT { class InstructionSelection; +struct CachedLinkData { + quint32 index; // Link table index + quint32 offset; // Offset inside the codeRef code +}; + +struct LinkableCode { + int len; + void* code; +}; + struct CompilationUnit : public QV4::CompiledData::CompilationUnit { virtual ~CompilationUnit(); @@ -75,6 +85,8 @@ struct CompilationUnit : public QV4::Com QVector codeRefs; QList > constantValues; + QList> linkData; + QList linkableCode; }; struct RelativeCall { @@ -1064,7 +1076,12 @@ public: move(TrustedImm64(u.i), ReturnValueRegister); move64ToDouble(ReturnValueRegister, target); #else - JSC::MacroAssembler::loadDouble(constantTable().loadValueAddress(c, ScratchRegister), target); + QV4::Primitive vv = convertToValue(c); + FPRegisterID scratchFp; + scratchFp = target; + move(TrustedImm32(vv.int_32), ReturnValueRegister); + move(TrustedImm32(vv.tag), ScratchRegister); + moveIntsToDouble(ReturnValueRegister, ScratchRegister, target, scratchFp); #endif return target; } @@ -1152,6 +1169,12 @@ public: Label exceptionReturnLabel; IR::BasicBlock * catchBlock; QVector exceptionPropagationJumps; + + LinkableCode tmpBuffer; + + LinkableCode& codeToLink() { return tmpBuffer; } + QList& callsToLink() { return _callsToLink; } + private: QScopedPointer _stackLayout; ConstantTable _constTable; Index: qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4cachedlinkdata_p.h =================================================================== --- /dev/null +++ qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4cachedlinkdata_p.h @@ -0,0 +1,188 @@ +/*************************************************************************** +** +** Copyright (C) 2014 Nomovok Ltd. All rights reserved. +** Contact: info@nomovok.com +** +** Copyright (C) 2014 Canonical Limited and/or its subsidiary(-ies). +** Contact: ricardo.mendoza@canonical.com +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +****************************************************************************/ + +#ifndef QV4CACHEDLINKDATA_P_H +#define QV4CACHEDLINKDATA_P_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct CachedLinkEntry { + const char *name; + void *addr; +}; + +#define CACHED_LINK_TABLE_ADD_NS(x) QV4::Runtime::x +#define CACHED_LINK_TABLE_STR(x) "Runtime::" #x +#define CACHED_LINK_TABLE_ENTRY_RUNTIME(x) { (const char *)CACHED_LINK_TABLE_STR(x), (void *)CACHED_LINK_TABLE_ADD_NS(x) } + +// table to link objects +// this table can be used to resolve functions, it is id -> object mapping to maximize performance in linking phase +// when adding new calls, add to end of the list to maintain compatibility +const CachedLinkEntry CACHED_LINK_TABLE[] = { + // call + CACHED_LINK_TABLE_ENTRY_RUNTIME(callGlobalLookup), + CACHED_LINK_TABLE_ENTRY_RUNTIME(callActivationProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(callProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(callPropertyLookup), + CACHED_LINK_TABLE_ENTRY_RUNTIME(callElement), + CACHED_LINK_TABLE_ENTRY_RUNTIME(callValue), + + // construct + CACHED_LINK_TABLE_ENTRY_RUNTIME(constructGlobalLookup), + CACHED_LINK_TABLE_ENTRY_RUNTIME(constructActivationProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(constructProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(constructPropertyLookup), + CACHED_LINK_TABLE_ENTRY_RUNTIME(constructValue), + + // set & get + CACHED_LINK_TABLE_ENTRY_RUNTIME(setActivationProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(setProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(setElement), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getActivationProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getElement), + + // typeof + CACHED_LINK_TABLE_ENTRY_RUNTIME(typeofValue), + CACHED_LINK_TABLE_ENTRY_RUNTIME(typeofName), + CACHED_LINK_TABLE_ENTRY_RUNTIME(typeofMember), + CACHED_LINK_TABLE_ENTRY_RUNTIME(typeofElement), + + // delete + CACHED_LINK_TABLE_ENTRY_RUNTIME(deleteElement), + CACHED_LINK_TABLE_ENTRY_RUNTIME(deleteMember), + CACHED_LINK_TABLE_ENTRY_RUNTIME(deleteName), + + // exceptions & scopes + CACHED_LINK_TABLE_ENTRY_RUNTIME(throwException), + CACHED_LINK_TABLE_ENTRY_RUNTIME(unwindException), + CACHED_LINK_TABLE_ENTRY_RUNTIME(pushWithScope), + CACHED_LINK_TABLE_ENTRY_RUNTIME(pushCatchScope), + CACHED_LINK_TABLE_ENTRY_RUNTIME(popScope), + + // closures + CACHED_LINK_TABLE_ENTRY_RUNTIME(closure), + + // function header + CACHED_LINK_TABLE_ENTRY_RUNTIME(declareVar), + CACHED_LINK_TABLE_ENTRY_RUNTIME(setupArgumentsObject), + CACHED_LINK_TABLE_ENTRY_RUNTIME(convertThisToObject), + + // literals + CACHED_LINK_TABLE_ENTRY_RUNTIME(arrayLiteral), + CACHED_LINK_TABLE_ENTRY_RUNTIME(objectLiteral), + CACHED_LINK_TABLE_ENTRY_RUNTIME(regexpLiteral), + + // foreach + CACHED_LINK_TABLE_ENTRY_RUNTIME(foreachIterator), + CACHED_LINK_TABLE_ENTRY_RUNTIME(foreachNextPropertyName), + + // unary operators + //typedef ReturnedValue (*UnaryOperation)(const ValueRef); + {"NOOP", (void *)qt_noop}, + CACHED_LINK_TABLE_ENTRY_RUNTIME(uPlus), + CACHED_LINK_TABLE_ENTRY_RUNTIME(uMinus), + CACHED_LINK_TABLE_ENTRY_RUNTIME(uNot), + CACHED_LINK_TABLE_ENTRY_RUNTIME(complement), + CACHED_LINK_TABLE_ENTRY_RUNTIME(increment), + CACHED_LINK_TABLE_ENTRY_RUNTIME(decrement), + + // binary operators + //typedef ReturnedValue (*BinaryOperation)(const ValueRef left, const ValueRef right); + {"NOOP", (void *)qt_noop}, + //typedef ReturnedValue (*BinaryOperationContext)(ExecutionContext *ctx, const ValueRef left, const ValueRef right); + {"NOOP", (void *)qt_noop}, + + CACHED_LINK_TABLE_ENTRY_RUNTIME(instanceof), + CACHED_LINK_TABLE_ENTRY_RUNTIME(in), + CACHED_LINK_TABLE_ENTRY_RUNTIME(add), + CACHED_LINK_TABLE_ENTRY_RUNTIME(addString), + CACHED_LINK_TABLE_ENTRY_RUNTIME(bitOr), + CACHED_LINK_TABLE_ENTRY_RUNTIME(bitXor), + CACHED_LINK_TABLE_ENTRY_RUNTIME(bitAnd), + CACHED_LINK_TABLE_ENTRY_RUNTIME(sub), + CACHED_LINK_TABLE_ENTRY_RUNTIME(mul), + CACHED_LINK_TABLE_ENTRY_RUNTIME(div), + CACHED_LINK_TABLE_ENTRY_RUNTIME(mod), + CACHED_LINK_TABLE_ENTRY_RUNTIME(shl), + CACHED_LINK_TABLE_ENTRY_RUNTIME(shr), + CACHED_LINK_TABLE_ENTRY_RUNTIME(ushr), + CACHED_LINK_TABLE_ENTRY_RUNTIME(greaterThan), + CACHED_LINK_TABLE_ENTRY_RUNTIME(lessThan), + CACHED_LINK_TABLE_ENTRY_RUNTIME(greaterEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(lessEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(equal), + CACHED_LINK_TABLE_ENTRY_RUNTIME(notEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(strictEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(strictNotEqual), + + // comparisons + //typedef Bool (*CompareOperation)(const ValueRef left, const ValueRef right);} + {"NOOP", (void *)qt_noop}, + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareGreaterThan), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareLessThan), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareGreaterEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareLessEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareNotEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareStrictEqual), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareStrictNotEqual), + + //typedef Bool (*CompareOperationContext)(ExecutionContext *ctx, const ValueRef left, const ValueRef right); + {"NOOP", (void *)qt_noop}, + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareInstanceof), + CACHED_LINK_TABLE_ENTRY_RUNTIME(compareIn), + + // conversions + CACHED_LINK_TABLE_ENTRY_RUNTIME(toBoolean), + CACHED_LINK_TABLE_ENTRY_RUNTIME(toDouble), + CACHED_LINK_TABLE_ENTRY_RUNTIME(toInt), + CACHED_LINK_TABLE_ENTRY_RUNTIME(doubleToInt), + CACHED_LINK_TABLE_ENTRY_RUNTIME(toUInt), + CACHED_LINK_TABLE_ENTRY_RUNTIME(doubleToUInt), + + // qml + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlIdArray), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlImportedScripts), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlContextObject), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlScopeObject), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlSingleton), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlAttachedProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(getQmlQObjectProperty), + CACHED_LINK_TABLE_ENTRY_RUNTIME(setQmlQObjectProperty) +}; + +QT_END_NAMESPACE + +#endif Index: qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4isel_masm.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jit/qv4isel_masm.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/jit/qv4isel_masm.cpp @@ -43,6 +43,7 @@ #include "qv4assembler_p.h" #include "qv4unop_p.h" #include "qv4binop_p.h" +#include "qv4cachedlinkdata_p.h" #include @@ -139,9 +140,13 @@ JSC::MacroAssemblerCodeRef Assembler::li JSC::LinkBuffer linkBuffer(dummy, this, 0); QHash functions; + int i = 0; foreach (CallToLink ctl, _callsToLink) { - linkBuffer.link(ctl.call, ctl.externalFunction); + unsigned int offset = linkBuffer.link(ctl.call, ctl.externalFunction); + ctl.call.m_label.m_offset = offset; functions[linkBuffer.locationOf(ctl.label).dataLocation()] = ctl.functionName; + _callsToLink.replace(i, ctl); + i++; } foreach (const DataLabelPatch &p, _dataLabelPatches) @@ -277,6 +282,25 @@ void InstructionSelection::run(int funct JSC::MacroAssemblerCodeRef codeRef =_as->link(&dummySize); compilationUnit->codeRefs[functionIndex] = codeRef; + QVector calls; + QList& callsToLink = _as->callsToLink(); + for (int i = 0; i < callsToLink.size(); i++) { + Assembler::CallToLink& ctl = callsToLink[i]; + int index = -1; + for (uint i = 0; i < sizeof(CACHED_LINK_TABLE) / sizeof (CachedLinkEntry); i++) { + if (CACHED_LINK_TABLE[i].addr == ctl.externalFunction.value()) { + index = i; + break; + } + } + CachedLinkData link; + link.index = index; + link.offset = ctl.call.m_label.m_offset; + calls.append(link); + } + compilationUnit->linkData.insert(functionIndex, calls); // link data; + compilationUnit->linkableCode.insert(functionIndex, _as->codeToLink()); // code to link + qSwap(_function, function); delete _as; _as = oldAssembler; @@ -300,6 +324,11 @@ QQmlRefPointer *values); + + virtual InstructionSelection* impl() { return this; }; protected: virtual QQmlRefPointer backendCompileStep(); + virtual QV4::CompiledData::CompilationUnit* mutableCompilationUnit(); virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Expr *result); virtual void callBuiltinTypeofMember(IR::Expr *base, const QString &name, IR::Expr *result); Index: qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4functionobject.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jsruntime/qv4functionobject.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4functionobject.cpp @@ -226,6 +226,7 @@ ReturnedValue FunctionCtor::construct(Ma Compiler::JSUnitGenerator jsGenerator(&module); QScopedPointer isel(scope.engine->iselFactory->create(QQmlEnginePrivate::get(scope.engine), scope.engine->executableAllocator, &module, &jsGenerator)); + isel->setEngine(QQmlEnginePrivate::get(scope.engine)); QQmlRefPointer compilationUnit = isel->compile(); Function *vmf = compilationUnit->linkToEngine(scope.engine); Index: qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4qobjectwrapper.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -629,6 +629,7 @@ ReturnedValue QObjectWrapper::getPropert QQmlPropertyCache *cache = ddata->propertyCache; Q_ASSERT(cache); QQmlPropertyData *property = cache->property(propertyIndex); + Q_ASSERT(property); // We resolved this property earlier, so it better exist! return getProperty(object, ctx, property, captureRequired); } Index: qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4script.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/jsruntime/qv4script.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/jsruntime/qv4script.cpp @@ -259,6 +259,7 @@ void Script::parse() QScopedPointer isel(v4->iselFactory->create(QQmlEnginePrivate::get(v4), v4->executableAllocator, &module, &jsGenerator)); if (inheritContext) isel->setUseFastLookups(false); + isel->setEngine(QQmlEnginePrivate::get(v4)); QQmlRefPointer compilationUnit = isel->compile(); vmFunction = compilationUnit->linkToEngine(v4); ScopedObject holder(valueScope, v4->memoryManager->alloc(v4, compilationUnit)); @@ -360,6 +361,7 @@ QQmlRefPointer isel(engine->iselFactory->create(QQmlEnginePrivate::get(engine), engine->executableAllocator, module, unitGenerator)); isel->setUseFastLookups(false); + isel->setEngine(QQmlEnginePrivate::get(engine)); return isel->compile(/*generate unit data*/false); } Index: qtdeclarative-opensource-src-5.4.0/src/qml/qml/qqmlengine.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/qml/qqmlengine.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/qml/qqmlengine.cpp @@ -559,7 +559,8 @@ QQmlEnginePrivate::QQmlEnginePrivate(QQm activeObjectCreator(0), networkAccessManager(0), networkAccessManagerFactory(0), urlInterceptor(0), scarceResourcesRefCount(0), typeLoader(e), importDatabase(e), uniqueId(1), - incubatorCount(0), incubationController(0) + incubatorCount(0), incubationController(0), + qmlCacheValid(0) { } Index: qtdeclarative-opensource-src-5.4.0/src/qml/qml/qqmlengine_p.h =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/qml/qqmlengine_p.h +++ qtdeclarative-opensource-src-5.4.0/src/qml/qml/qqmlengine_p.h @@ -263,6 +263,8 @@ public: mutable QMutex networkAccessManagerMutex; + int qmlCacheValid; + private: // Must be called locked QQmlPropertyCache *createCache(QQmlType *, int, QQmlError &error); Index: qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_moth.cpp =================================================================== --- qtdeclarative-opensource-src-5.4.0.orig/src/qml/compiler/qv4isel_moth.cpp +++ qtdeclarative-opensource-src-5.4.0/src/qml/compiler/qv4isel_moth.cpp @@ -457,6 +457,11 @@ QQmlRefPointer