aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-01-11 17:46:00 +0100
committerUlf Hermann <ulf.hermann@qt.io>2021-01-12 11:18:51 +0100
commit7660c30e08975011c7bd47bcb1796139b9d77196 (patch)
tree26e86cbc35560ac5bdf3327ea29f146cc1821787
parentccc4b52e95ac90c14c59001f675409c1a13c183b (diff)
Cache static compilation units
If we load the same file multiple times, we can re-use the old mapping. In fact we may leak memory if we don't. The fact that we have to use a mutex here is somewhat regrettable, but I haven't found a better way of serializing access. Task-number: QTBUG-89659 Pick-to: 5.15 Change-Id: Iaa44ac80faa5e95f30c05e950ab35083a8b0416b Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/jsruntime/qv4compilationunitmapper.cpp58
-rw-r--r--src/qml/jsruntime/qv4compilationunitmapper_p.h13
-rw-r--r--src/qml/jsruntime/qv4executablecompilationunit.cpp2
-rw-r--r--tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp128
4 files changed, 159 insertions, 42 deletions
diff --git a/src/qml/jsruntime/qv4compilationunitmapper.cpp b/src/qml/jsruntime/qv4compilationunitmapper.cpp
index 74f34a284d..f3e2b445d1 100644
--- a/src/qml/jsruntime/qv4compilationunitmapper.cpp
+++ b/src/qml/jsruntime/qv4compilationunitmapper.cpp
@@ -40,23 +40,69 @@
#include "qv4compilationunitmapper_p.h"
#include <private/qv4compileddata_p.h>
-#include <QFileInfo>
-#include <QDateTime>
-#include <QCoreApplication>
+#include <private/qv4executablecompilationunit_p.h>
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qhash.h>
QT_BEGIN_NAMESPACE
using namespace QV4;
-CompilationUnitMapper::CompilationUnitMapper()
- : dataPtr(nullptr)
+class StaticUnitCache
{
+public:
+ StaticUnitCache() : m_lock(&s_mutex) {}
-}
+ CompilationUnitMapper get(const QString &file)
+ {
+ const auto it = s_staticUnits.constFind(file);
+ return it == s_staticUnits.constEnd() ? CompilationUnitMapper() : *it;
+ }
+
+ void set(const QString &file, const CompilationUnitMapper &staticUnit) {
+ s_staticUnits.insert(file, staticUnit);
+ }
+
+private:
+ QMutexLocker<QMutex> m_lock;
+
+ static QMutex s_mutex;
+
+ // We can copy the mappers around because they're all static, that is the dtors are noops.
+ static QHash<QString, CompilationUnitMapper> s_staticUnits;
+};
+
+QHash<QString, CompilationUnitMapper> StaticUnitCache::s_staticUnits;
+QMutex StaticUnitCache::s_mutex;
CompilationUnitMapper::~CompilationUnitMapper()
{
close();
}
+CompiledData::Unit *CompilationUnitMapper::get(
+ const QString &cacheFilePath, const QDateTime &sourceTimeStamp, QString *errorString)
+{
+ StaticUnitCache cache;
+
+ CompilationUnitMapper mapper = cache.get(cacheFilePath);
+ if (mapper.dataPtr) {
+ auto *unit = reinterpret_cast<CompiledData::Unit *>(mapper.dataPtr);
+ if (ExecutableCompilationUnit::verifyHeader(unit, sourceTimeStamp, errorString)) {
+ *this = mapper;
+ return unit;
+ }
+
+ return nullptr;
+ }
+
+ CompiledData::Unit *data = open(cacheFilePath, sourceTimeStamp, errorString);
+ if (data && (data->flags & CompiledData::Unit::StaticData))
+ cache.set(cacheFilePath, *this);
+
+ return data;
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/jsruntime/qv4compilationunitmapper_p.h b/src/qml/jsruntime/qv4compilationunitmapper_p.h
index 80f914c141..10ad0a6ede 100644
--- a/src/qml/jsruntime/qv4compilationunitmapper_p.h
+++ b/src/qml/jsruntime/qv4compilationunitmapper_p.h
@@ -65,17 +65,20 @@ struct Unit;
class CompilationUnitMapper
{
public:
- CompilationUnitMapper();
~CompilationUnitMapper();
- CompiledData::Unit *open(const QString &cacheFilePath, const QDateTime &sourceTimeStamp, QString *errorString);
- void close();
+ CompiledData::Unit *get(
+ const QString &cacheFilePath, const QDateTime &sourceTimeStamp, QString *errorString);
private:
+ CompiledData::Unit *open(
+ const QString &cacheFilePath, const QDateTime &sourceTimeStamp, QString *errorString);
+ void close();
+
#if defined(Q_OS_UNIX)
- size_t length;
+ size_t length = 0;
#endif
- void *dataPtr;
+ void *dataPtr = nullptr;
};
}
diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp
index 5aaec63a05..8ef8ae2221 100644
--- a/src/qml/jsruntime/qv4executablecompilationunit.cpp
+++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp
@@ -764,7 +764,7 @@ bool ExecutableCompilationUnit::loadFromDisk(const QUrl &url, const QDateTime &s
const QStringList cachePaths = { sourcePath + QLatin1Char('c'), localCacheFilePath(url) };
for (const QString &cachePath : cachePaths) {
- CompiledData::Unit *mappedUnit = cacheFile->open(cachePath, sourceTimeStamp, errorString);
+ CompiledData::Unit *mappedUnit = cacheFile->get(cachePath, sourceTimeStamp, errorString);
if (!mappedUnit)
continue;
diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
index d0c8390a74..d96231e81e 100644
--- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
+++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
@@ -64,6 +64,7 @@ private slots:
void singletonDependency();
void cppRegisteredSingletonDependency();
void cacheModuleScripts();
+ void reuseStaticMappings();
private:
QDir m_qmlCacheDirectory;
@@ -127,34 +128,43 @@ struct TestCompiler
mappedFile.setFileName(cacheFilePath);
}
- bool compile(const QByteArray &contents)
+ void reset()
{
closeMapping();
engine->clearComponentCache();
-
waitForFileSystem();
+ }
- {
- QFile f(testFilePath);
- if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- lastErrorString = f.errorString();
- return false;
- }
- if (f.write(contents) != contents.size()) {
- lastErrorString = f.errorString();
- return false;
- }
+ bool writeTestFile(const QByteArray &contents)
+ {
+ QFile f(testFilePath);
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ lastErrorString = f.errorString();
+ return false;
+ }
+ if (f.write(contents) != contents.size()) {
+ lastErrorString = f.errorString();
+ return false;
}
+ return true;
+ }
+ bool loadTestFile()
+ {
CleanlyLoadingComponent component(engine, testFilePath);
if (!component.isReady()) {
lastErrorString = component.errorString();
return false;
}
-
return true;
}
+ bool compile(const QByteArray &contents)
+ {
+ reset();
+ return writeTestFile(contents) && loadTestFile();
+ }
+
const QV4::CompiledData::Unit *mapUnit()
{
if (!mappedFile.open(QIODevice::ReadOnly)) {
@@ -173,29 +183,62 @@ struct TestCompiler
}
typedef void (*HeaderTweakFunction)(QV4::CompiledData::Unit *header);
- bool tweakHeader(HeaderTweakFunction function)
+ bool tweakHeader(HeaderTweakFunction function, const QString &newName)
{
closeMapping();
- QFile f(cacheFilePath);
- if (!f.open(QIODevice::ReadWrite))
+ const QString targetTestFilePath = tempDir.path() + "/" + newName;
+
+ {
+ QFile testFile(testFilePath);
+ if (!testFile.copy(targetTestFilePath))
+ return false;
+ }
+
+ const QString targetCacheFilePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
+ QUrl::fromLocalFile(targetTestFilePath));
+
+ QFile source(cacheFilePath);
+ if (!source.copy(targetCacheFilePath))
return false;
+
+ if (!source.open(QIODevice::ReadOnly))
+ return false;
+
+ QFile target(targetCacheFilePath);
+ if (!target.open(QIODevice::WriteOnly))
+ return false;
+
QV4::CompiledData::Unit header;
- if (f.read(reinterpret_cast<char *>(&header), sizeof(header)) != sizeof(header))
+ if (source.read(reinterpret_cast<char *>(&header), sizeof(header)) != sizeof(header))
return false;
function(&header);
- f.seek(0);
- return f.write(reinterpret_cast<const char *>(&header), sizeof(header)) == sizeof(header);
+
+ return target.write(reinterpret_cast<const char *>(&header), sizeof(header))
+ == sizeof(header);
}
- bool verify()
+ bool verify(const QString &fileName = QString())
+ {
+ const QString path = fileName.isEmpty() ? testFilePath : tempDir.path() + "/" + fileName;
+
+ QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
+ = QV4::ExecutableCompilationUnit::create();
+ return unit->loadFromDisk(QUrl::fromLocalFile(path),
+ QFileInfo(path).lastModified(), &lastErrorString);
+ }
+
+ quintptr unitData()
{
QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
= QV4::ExecutableCompilationUnit::create();
return unit->loadFromDisk(QUrl::fromLocalFile(testFilePath),
- QFileInfo(testFilePath).lastModified(), &lastErrorString);
+ QFileInfo(testFilePath).lastModified(), &lastErrorString)
+ ? quintptr(unit->unitData())
+ : 0;
}
+
void closeMapping()
{
if (currentMapping) {
@@ -205,10 +248,11 @@ struct TestCompiler
mappedFile.close();
}
- void clearCache()
+ void clearCache(const QString &fileName = QString())
{
+ const QString path = fileName.isEmpty() ? testFilePath : tempDir.path() + "/" + fileName;
closeMapping();
- QFile::remove(cacheFilePath);
+ QFile::remove(path);
}
QQmlEngine *engine;
@@ -404,25 +448,28 @@ void tst_qmldiskcache::basicVersionChecks()
testCompiler.clearCache();
QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
- testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
+ const QString qtVersionFile = QStringLiteral("qtversion.qml");
+ QVERIFY(testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
header->qtVersion = 0;
- });
+ }, qtVersionFile));
- QVERIFY(!testCompiler.verify());
+ QVERIFY(!testCompiler.verify(qtVersionFile));
QCOMPARE(testCompiler.lastErrorString, QString::fromUtf8("Qt version mismatch. Found 0 expected %1").arg(QT_VERSION, 0, 16));
- testCompiler.clearCache();
+ testCompiler.clearCache(qtVersionFile);
}
{
testCompiler.clearCache();
QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
- testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
+ const QString versionFile = QStringLiteral("version.qml");
+ QVERIFY(testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
header->version = 0;
- });
+ }, versionFile));
- QVERIFY(!testCompiler.verify());
+ QVERIFY(!testCompiler.verify(versionFile));
QCOMPARE(testCompiler.lastErrorString, QString::fromUtf8("V4 data structure version mismatch. Found 0 expected %1").arg(QV4_DATA_STRUCTURE_VERSION, 0, 16));
+ testCompiler.clearCache(versionFile);
}
}
@@ -963,6 +1010,27 @@ void tst_qmldiskcache::cacheModuleScripts()
}
}
+void tst_qmldiskcache::reuseStaticMappings()
+{
+ QQmlEngine engine;
+
+ TestCompiler testCompiler(&engine);
+ QVERIFY(testCompiler.tempDir.isValid());
+
+ testCompiler.reset();
+ QVERIFY(testCompiler.writeTestFile("import QtQml\nQtObject { objectName: 'foobar' }\n"));
+ QVERIFY(testCompiler.loadTestFile());
+
+ const quintptr data1 = testCompiler.unitData();
+ QVERIFY(data1 != 0);
+ QCOMPARE(testCompiler.unitData(), data1);
+
+ testCompiler.reset();
+ QVERIFY(testCompiler.loadTestFile());
+
+ QCOMPARE(testCompiler.unitData(), data1);
+}
+
QTEST_MAIN(tst_qmldiskcache)
#include "tst_qmldiskcache.moc"