diff options
Diffstat (limited to 'src/qml/compiler')
28 files changed, 5384 insertions, 3061 deletions
diff --git a/src/qml/compiler/compiler.pri b/src/qml/compiler/compiler.pri index 585fef7603..1de5dfa6fa 100644 --- a/src/qml/compiler/compiler.pri +++ b/src/qml/compiler/compiler.pri @@ -25,13 +25,29 @@ SOURCES += \ HEADERS += \ $$PWD/qqmltypecompiler_p.h \ - $$PWD/qv4isel_moth_p.h \ - $$PWD/qv4instr_moth_p.h + $$PWD/qqmlpropertycachecreator_p.h \ + $$PWD/qqmlpropertyvalidator_p.h \ + $$PWD/qv4compilationunitmapper_p.h SOURCES += \ $$PWD/qqmltypecompiler.cpp \ - $$PWD/qv4instr_moth.cpp \ - $$PWD/qv4isel_moth.cpp + $$PWD/qqmlpropertycachecreator.cpp \ + $$PWD/qqmlpropertyvalidator.cpp \ + $$PWD/qv4compilationunitmapper.cpp +unix: SOURCES += $$PWD/qv4compilationunitmapper_unix.cpp +else: SOURCES += $$PWD/qv4compilationunitmapper_win.cpp + +qtConfig(qml-interpreter) { + HEADERS += \ + $$PWD/qv4instr_moth_p.h \ + $$PWD/qv4isel_moth_p.h + SOURCES += \ + $$PWD/qv4instr_moth.cpp \ + $$PWD/qv4isel_moth.cpp +} + + +qtConfig(private_tests): LIBS_PRIVATE += $$QMAKE_LIBS_DYNLOAD } diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index eaf0e72296..eb83962630 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -44,12 +44,12 @@ #include <private/qqmljsparser_p.h> #include <private/qqmljslexer_p.h> #include <QCoreApplication> +#include <QCryptographicHash> #ifndef V4_BOOTSTRAP #include <private/qqmlglobal_p.h> #include <private/qqmltypeloader_p.h> #include <private/qqmlengine_p.h> -#include <private/qqmlcompiler_p.h> #endif #ifdef CONST @@ -72,21 +72,24 @@ using namespace QmlIR; return false; \ } -void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int id, const QQmlJS::AST::SourceLocation &loc) +void Object::init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex, const QQmlJS::AST::SourceLocation &loc) { inheritedTypeNameIndex = typeNameIndex; location.line = loc.startLine; location.column = loc.startColumn; - idIndex = id; - indexOfDefaultProperty = -1; + idNameIndex = idIndex; + id = -1; + indexOfDefaultPropertyOrAlias = -1; + defaultPropertyIsAlias = false; + flags = QV4::CompiledData::Object::NoFlag; properties = pool->New<PoolList<Property> >(); + aliases = pool->New<PoolList<Alias> >(); qmlSignals = pool->New<PoolList<Signal> >(); bindings = pool->New<PoolList<Binding> >(); functions = pool->New<PoolList<Function> >(); functionsAndExpressions = pool->New<PoolList<CompiledFunctionOrExpression> >(); - runtimeFunctionIndices = 0; declarationsOverride = 0; } @@ -145,15 +148,42 @@ QString Object::appendProperty(Property *prop, const QString &propertyName, bool const int index = target->properties->append(prop); if (isDefaultProperty) { - if (target->indexOfDefaultProperty != -1) { + if (target->indexOfDefaultPropertyOrAlias != -1) { *errorLocation = defaultToken; return tr("Duplicate default property"); } - target->indexOfDefaultProperty = index; + target->indexOfDefaultPropertyOrAlias = index; } return QString(); // no error } +QString Object::appendAlias(Alias *alias, const QString &aliasName, bool isDefaultProperty, const QQmlJS::AST::SourceLocation &defaultToken, QQmlJS::AST::SourceLocation *errorLocation) +{ + Object *target = declarationsOverride; + if (!target) + target = this; + + for (Alias *p = target->aliases->first; p; p = p->next) + if (p->nameIndex == alias->nameIndex) + return tr("Duplicate alias name"); + + if (aliasName.constData()->isUpper()) + return tr("Alias names cannot begin with an upper case letter"); + + const int index = target->aliases->append(alias); + + if (isDefaultProperty) { + if (target->indexOfDefaultPropertyOrAlias != -1) { + *errorLocation = defaultToken; + return tr("Duplicate default property"); + } + target->indexOfDefaultPropertyOrAlias = index; + target->defaultPropertyIsAlias = true; + } + + return QString(); // no error +} + void Object::appendFunction(QmlIR::Function *f) { Object *target = declarationsOverride; @@ -164,7 +194,7 @@ void Object::appendFunction(QmlIR::Function *f) QString Object::appendBinding(Binding *b, bool isListBinding) { - const bool bindingToDefaultProperty = (b->propertyNameIndex == 0); + const bool bindingToDefaultProperty = (b->propertyNameIndex == quint32(0)); if (!isListBinding && !bindingToDefaultProperty && b->type != QV4::CompiledData::Binding::Type_GroupProperty && b->type != QV4::CompiledData::Binding::Type_AttachedProperty @@ -190,7 +220,7 @@ Binding *Object::findBinding(quint32 nameIndex) const void Object::insertSorted(Binding *b) { - Binding *insertionPoint = bindings->findSortedInsertionPoint<QV4::CompiledData::Location, QV4::CompiledData::Binding, &QV4::CompiledData::Binding::valueLocation>(b); + Binding *insertionPoint = bindings->findSortedInsertionPoint<quint32, Binding, &Binding::offset>(b); bindings->insertAfter(insertionPoint, b); } @@ -222,32 +252,6 @@ static void replaceWithSpace(QString &str, int idx, int n) *data++ = space; } -void Document::collectTypeReferences() -{ - foreach (Object *obj, objects) { - if (obj->inheritedTypeNameIndex != emptyStringIndex) { - QV4::CompiledData::TypeReference &r = typeReferences.add(obj->inheritedTypeNameIndex, obj->location); - r.needsCreation = true; - r.errorWhenNotFound = true; - } - - for (const Property *prop = obj->firstProperty(); prop; prop = prop->next) { - if (prop->type >= QV4::CompiledData::Property::Custom) { - // ### FIXME: We could report the more accurate location here by using prop->location, but the old - // compiler can't and the tests expect it to be the object location right now. - QV4::CompiledData::TypeReference &r = typeReferences.add(prop->customTypeNameIndex, obj->location); - r.needsCreation = true; - r.errorWhenNotFound = true; - } - } - - for (const Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) - typeReferences.add(binding->propertyNameIndex, binding->location); - } - } -} - void Document::removeScriptPragmas(QString &script) { const QLatin1String pragma("pragma"); @@ -298,7 +302,6 @@ Document::Document(bool debugMode) , program(0) , indexOfRootObject(0) , jsGenerator(&jsModule) - , unitFlags(0) { } @@ -637,7 +640,10 @@ bool IRBuilder::visit(QQmlJS::AST::UiImport *node) } if (node->versionToken.isValid()) { - extractVersion(textRefAt(node->versionToken), &import->majorVersion, &import->minorVersion); + int major, minor; + extractVersion(textRefAt(node->versionToken), &major, &minor); + import->majorVersion = major; + import->minorVersion = minor; } else if (import->type == QV4::CompiledData::Import::ImportLibrary) { recordError(node->importIdToken, QCoreApplication::translate("QQmlParser","Library import requires a version")); return false; @@ -807,130 +813,87 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) } } else { const QStringRef &memberType = node->memberType; - const QStringRef &name = node->name; - - bool typeFound = false; - QV4::CompiledData::Property::Type type; - if (memberType == QLatin1String("alias")) { - type = QV4::CompiledData::Property::Alias; - typeFound = true; - } + return appendAlias(node); + } else { + const QStringRef &name = node->name; - for (int ii = 0; !typeFound && ii < propTypeNameToTypesCount; ++ii) { - const TypeNameToType *t = propTypeNameToTypes + ii; - if (memberType == QLatin1String(t->name, static_cast<int>(t->nameLength))) { - type = t->type; - typeFound = true; + bool typeFound = false; + QV4::CompiledData::Property::Type type = QV4::CompiledData::Property::Var; + + for (int ii = 0; !typeFound && ii < propTypeNameToTypesCount; ++ii) { + const TypeNameToType *t = propTypeNameToTypes + ii; + if (memberType == QLatin1String(t->name, static_cast<int>(t->nameLength))) { + type = t->type; + typeFound = true; + } } - } - if (!typeFound && memberType.at(0).isUpper()) { - const QStringRef &typeModifier = node->typeModifier; + if (!typeFound && memberType.at(0).isUpper()) { + const QStringRef &typeModifier = node->typeModifier; - if (typeModifier.isEmpty()) { - type = QV4::CompiledData::Property::Custom; - } else if (typeModifier == QLatin1String("list")) { - type = QV4::CompiledData::Property::CustomList; - } else { - recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Invalid property type modifier")); + if (typeModifier.isEmpty()) { + type = QV4::CompiledData::Property::Custom; + } else if (typeModifier == QLatin1String("list")) { + type = QV4::CompiledData::Property::CustomList; + } else { + recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Invalid property type modifier")); + return false; + } + typeFound = true; + } else if (!node->typeModifier.isNull()) { + recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Unexpected property type modifier")); return false; } - typeFound = true; - } else if (!node->typeModifier.isNull()) { - recordError(node->typeModifierToken, QCoreApplication::translate("QQmlParser","Unexpected property type modifier")); - return false; - } - - if (!typeFound) { - recordError(node->typeToken, QCoreApplication::translate("QQmlParser","Expected property type")); - return false; - } - - Property *property = New<Property>(); - property->flags = 0; - if (node->isReadonlyMember) - property->flags |= QV4::CompiledData::Property::IsReadOnly; - property->type = type; - if (type >= QV4::CompiledData::Property::Custom) - property->customTypeNameIndex = registerString(memberType.toString()); - else - property->customTypeNameIndex = emptyStringIndex; - - const QString propName = name.toString(); - property->nameIndex = registerString(propName); - QQmlJS::AST::SourceLocation loc = node->firstSourceLocation(); - property->location.line = loc.startLine; - property->location.column = loc.startColumn; - - property->aliasPropertyValueIndex = emptyStringIndex; - - if (type == QV4::CompiledData::Property::Alias) { - if (!node->statement && !node->binding) - COMPILE_EXCEPTION(loc, tr("No property alias location")); + if (!typeFound) { + recordError(node->typeToken, QCoreApplication::translate("QQmlParser","Expected property type")); + return false; + } - QQmlJS::AST::SourceLocation rhsLoc; - if (node->binding) - rhsLoc = node->binding->firstSourceLocation(); - else if (node->statement) - rhsLoc = node->statement->firstSourceLocation(); + Property *property = New<Property>(); + property->flags = 0; + if (node->isReadonlyMember) + property->flags |= QV4::CompiledData::Property::IsReadOnly; + property->type = type; + if (type >= QV4::CompiledData::Property::Custom) + property->customTypeNameIndex = registerString(memberType.toString()); else - rhsLoc = node->semicolonToken; - property->aliasLocation.line = rhsLoc.startLine; - property->aliasLocation.column = rhsLoc.startColumn; - - QStringList alias; - - if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(node->statement)) { - alias = astNodeToStringList(stmt->expression); - if (alias.isEmpty()) { - if (isStatementNodeScript(node->statement)) { - COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); - } else { - COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias location")); - } - } - } else { - COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); - } + property->customTypeNameIndex = emptyStringIndex; - if (alias.count() < 1 || alias.count() > 3) - COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); + const QString propName = name.toString(); + property->nameIndex = registerString(propName); - property->aliasIdValueIndex = registerString(alias.first()); + QQmlJS::AST::SourceLocation loc = node->firstSourceLocation(); + property->location.line = loc.startLine; + property->location.column = loc.startColumn; - QString propertyValue = alias.value(1); - if (alias.count() == 3) { - propertyValue += QLatin1Char('.'); - propertyValue += alias.at(2); - } - property->aliasPropertyValueIndex = registerString(propertyValue); - } - QQmlJS::AST::SourceLocation errorLocation; - QString error; + QQmlJS::AST::SourceLocation errorLocation; + QString error; - if (illegalNames.contains(propName)) - error = tr("Illegal property name"); - else - error = _object->appendProperty(property, propName, node->isDefaultMember, node->defaultToken, &errorLocation); + if (illegalNames.contains(propName)) + error = tr("Illegal property name"); + else + error = _object->appendProperty(property, propName, node->isDefaultMember, node->defaultToken, &errorLocation); - if (!error.isEmpty()) { - if (errorLocation.startLine == 0) - errorLocation = node->identifierToken; + if (!error.isEmpty()) { + if (errorLocation.startLine == 0) + errorLocation = node->identifierToken; - recordError(errorLocation, error); - return false; - } + recordError(errorLocation, error); + return false; + } - qSwap(_propertyDeclaration, property); - if (node->binding) { - // process QML-like initializers (e.g. property Object o: Object {}) - QQmlJS::AST::Node::accept(node->binding, this); - } else if (node->statement && type != QV4::CompiledData::Property::Alias) { - appendBinding(node->identifierToken, node->identifierToken, _propertyDeclaration->nameIndex, node->statement); + qSwap(_propertyDeclaration, property); + if (node->binding) { + // process QML-like initializers (e.g. property Object o: Object {}) + QQmlJS::AST::Node::accept(node->binding, this); + } else if (node->statement) { + if (!isRedundantNullInitializerForPropertyDeclaration(_propertyDeclaration, node->statement)) + appendBinding(node->identifierToken, node->identifierToken, _propertyDeclaration->nameIndex, node->statement); + } + qSwap(_propertyDeclaration, property); } - qSwap(_propertyDeclaration, property); } return false; @@ -952,6 +915,16 @@ bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) f->location.column = loc.startColumn; f->index = index; f->nameIndex = registerString(funDecl->name.toString()); + + int formalsCount = 0; + for (QQmlJS::AST::FormalParameterList *it = funDecl->formals; it; it = it->next) + ++formalsCount; + f->formals.allocate(pool, formalsCount); + + int i = 0; + for (QQmlJS::AST::FormalParameterList *it = funDecl->formals; it; it = it->next, ++i) + f->formals[i] = registerString(it->name.toString()); + _object->appendFunction(f); } else { recordError(node->firstSourceLocation(), QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element")); @@ -1027,13 +1000,13 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST binding->value.b = false; } else if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expr)) { binding->type = QV4::CompiledData::Binding::Type_Number; - binding->value.d = lit->value; + binding->setNumberValueInternal(lit->value); } else { if (QQmlJS::AST::UnaryMinusExpression *unaryMinus = QQmlJS::AST::cast<QQmlJS::AST::UnaryMinusExpression *>(expr)) { if (QQmlJS::AST::NumericLiteral *lit = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(unaryMinus->expression)) { binding->type = QV4::CompiledData::Binding::Type_Number; - binding->value.d = -lit->value; + binding->setNumberValueInternal(-lit->value); } } } @@ -1085,6 +1058,7 @@ void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLo { Binding *binding = New<Binding>(); binding->propertyNameIndex = propertyNameIndex; + binding->offset = nameLocation.offset; binding->location.line = nameLocation.startLine; binding->location.column = nameLocation.startColumn; binding->flags = 0; @@ -1104,6 +1078,7 @@ void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLo Binding *binding = New<Binding>(); binding->propertyNameIndex = propertyNameIndex; + binding->offset = nameLocation.offset; binding->location.line = nameLocation.startLine; binding->location.column = nameLocation.startColumn; @@ -1133,6 +1108,79 @@ void IRBuilder::appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLo } } +bool IRBuilder::appendAlias(QQmlJS::AST::UiPublicMember *node) +{ + Alias *alias = New<Alias>(); + alias->flags = 0; + if (node->isReadonlyMember) + alias->flags |= QV4::CompiledData::Alias::IsReadOnly; + + const QString propName = node->name.toString(); + alias->nameIndex = registerString(propName); + + QQmlJS::AST::SourceLocation loc = node->firstSourceLocation(); + alias->location.line = loc.startLine; + alias->location.column = loc.startColumn; + + alias->propertyNameIndex = emptyStringIndex; + + if (!node->statement && !node->binding) + COMPILE_EXCEPTION(loc, tr("No property alias location")); + + QQmlJS::AST::SourceLocation rhsLoc; + if (node->binding) + rhsLoc = node->binding->firstSourceLocation(); + else if (node->statement) + rhsLoc = node->statement->firstSourceLocation(); + else + rhsLoc = node->semicolonToken; + alias->referenceLocation.line = rhsLoc.startLine; + alias->referenceLocation.column = rhsLoc.startColumn; + + QStringList aliasReference; + + if (QQmlJS::AST::ExpressionStatement *stmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(node->statement)) { + aliasReference = astNodeToStringList(stmt->expression); + if (aliasReference.isEmpty()) { + if (isStatementNodeScript(node->statement)) { + COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); + } else { + COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias location")); + } + } + } else { + COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); + } + + if (aliasReference.count() < 1 || aliasReference.count() > 3) + COMPILE_EXCEPTION(rhsLoc, tr("Invalid alias reference. An alias reference must be specified as <id>, <id>.<property> or <id>.<value property>.<property>")); + + alias->idIndex = registerString(aliasReference.first()); + + QString propertyValue = aliasReference.value(1); + if (aliasReference.count() == 3) + propertyValue += QLatin1Char('.') + aliasReference.at(2); + alias->propertyNameIndex = registerString(propertyValue); + + QQmlJS::AST::SourceLocation errorLocation; + QString error; + + if (illegalNames.contains(propName)) + error = tr("Illegal property name"); + else + error = _object->appendAlias(alias, propName, node->isDefaultMember, node->defaultToken, &errorLocation); + + if (!error.isEmpty()) { + if (errorLocation.startLine == 0) + errorLocation = node->identifierToken; + + recordError(errorLocation, error); + return false; + } + + return false; +} + Object *IRBuilder::bindingsTarget() const { if (_propertyDeclaration && _object->declarationsOverride) @@ -1178,10 +1226,10 @@ bool IRBuilder::setId(const QQmlJS::AST::SourceLocation &idLocation, QQmlJS::AST if (illegalNames.contains(idQString)) COMPILE_EXCEPTION(loc, tr( "ID illegally masks global JavaScript property")); - if (_object->idIndex != emptyStringIndex) + if (_object->idNameIndex != emptyStringIndex) COMPILE_EXCEPTION(idLocation, tr("Property value set multiple times")); - _object->idIndex = registerString(idQString); + _object->idNameIndex = registerString(idQString); _object->locationOfIdProperty.line = idLocation.startLine; _object->locationOfIdProperty.column = idLocation.startColumn; @@ -1202,8 +1250,7 @@ bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, O if (import->qualifierIndex != emptyStringIndex && stringAt(import->qualifierIndex) == currentName) { qualifiedIdElement = qualifiedIdElement->next; - currentName += QLatin1Char('.'); - currentName += qualifiedIdElement->name; + currentName += QLatin1Char('.') + qualifiedIdElement->name; if (!qualifiedIdElement->name.unicode()->isUpper()) COMPILE_EXCEPTION(qualifiedIdElement->firstSourceLocation(), tr("Expected type name")); @@ -1229,6 +1276,7 @@ bool IRBuilder::resolveQualifiedId(QQmlJS::AST::UiQualifiedId **nameToResolve, O if (!binding) { binding = New<Binding>(); binding->propertyNameIndex = propertyNameIndex; + binding->offset = qualifiedIdElement->identifierToken.offset; binding->location.line = qualifiedIdElement->identifierToken.startLine; binding->location.column = qualifiedIdElement->identifierToken.startColumn; binding->valueLocation.line = qualifiedIdElement->next->identifierToken.startLine; @@ -1300,7 +1348,18 @@ bool IRBuilder::isStatementNodeScript(QQmlJS::AST::Statement *statement) return true; } -QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) +bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement) +{ + if (property->type != QV4::CompiledData::Property::Custom) + return false; + QQmlJS::AST::ExpressionStatement *exprStmt = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(statement); + if (!exprStmt) + return false; + QQmlJS::AST::ExpressionNode * const expr = exprStmt->expression; + return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr); +} + +QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, QQmlEngine *engine, const QV4::CompiledData::ResolvedTypeReferenceMap &dependentTypes) { QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit = output.javaScriptCompilationUnit; QV4::CompiledData::Unit *jsUnit = compilationUnit->createUnitData(&output); @@ -1309,12 +1368,12 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) const int importSize = sizeof(QV4::CompiledData::Import) * output.imports.count(); const int objectOffsetTableSize = output.objects.count() * sizeof(quint32); - QHash<Object*, quint32> objectOffsets; + QHash<const Object*, quint32> objectOffsets; int objectsSize = 0; foreach (Object *o, output.objects) { objectOffsets.insert(o, unitSize + importSize + objectOffsetTableSize + objectsSize); - objectsSize += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->signalCount(), o->bindingCount()); + objectsSize += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->aliasCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.count); int signalTableSize = 0; for (const Signal *s = o->firstSignal(); s; s = s->next) @@ -1333,7 +1392,6 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(data); qmlUnit->unitSize = totalSize; - qmlUnit->flags |= output.unitFlags; qmlUnit->flags |= QV4::CompiledData::Unit::IsQml; qmlUnit->offsetToImports = unitSize; qmlUnit->nImports = output.imports.count(); @@ -1343,6 +1401,20 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) qmlUnit->offsetToStringTable = totalSize - output.jsGenerator.stringTable.sizeOfTableAndData(); qmlUnit->stringTableSize = output.jsGenerator.stringTable.stringCount(); +#ifndef V4_BOOTSTRAP + if (!dependentTypes.isEmpty()) { + QCryptographicHash hash(QCryptographicHash::Md5); + if (dependentTypes.addToHash(&hash, engine)) { + QByteArray checksum = hash.result(); + Q_ASSERT(checksum.size() == sizeof(qmlUnit->dependencyMD5Checksum)); + memcpy(qmlUnit->dependencyMD5Checksum, checksum.constData(), sizeof(qmlUnit->dependencyMD5Checksum)); + } + } +#else + Q_UNUSED(dependentTypes); + Q_UNUSED(engine); +#endif + // write imports char *importPtr = data + qmlUnit->offsetToImports; foreach (const QV4::CompiledData::Import *imp, output.imports) { @@ -1354,13 +1426,17 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) // write objects quint32 *objectTable = reinterpret_cast<quint32*>(data + qmlUnit->offsetToObjects); char *objectPtr = data + qmlUnit->offsetToObjects + objectOffsetTableSize; - foreach (Object *o, output.objects) { + for (int i = 0; i < output.objects.count(); ++i) { + const Object *o = output.objects.at(i); *objectTable++ = objectOffsets.value(o); QV4::CompiledData::Object *objectToWrite = reinterpret_cast<QV4::CompiledData::Object*>(objectPtr); objectToWrite->inheritedTypeNameIndex = o->inheritedTypeNameIndex; - objectToWrite->indexOfDefaultProperty = o->indexOfDefaultProperty; - objectToWrite->idIndex = o->idIndex; + objectToWrite->indexOfDefaultPropertyOrAlias = o->indexOfDefaultPropertyOrAlias; + objectToWrite->defaultPropertyIsAlias = o->defaultPropertyIsAlias; + objectToWrite->flags = o->flags; + objectToWrite->idNameIndex = o->idNameIndex; + objectToWrite->id = o->id; objectToWrite->location = o->location; objectToWrite->locationOfIdProperty = o->locationOfIdProperty; @@ -1374,6 +1450,10 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) objectToWrite->offsetToProperties = nextOffset; nextOffset += objectToWrite->nProperties * sizeof(QV4::CompiledData::Property); + objectToWrite->nAliases = o->aliasCount(); + objectToWrite->offsetToAliases = nextOffset; + nextOffset += objectToWrite->nAliases * sizeof(QV4::CompiledData::Alias); + objectToWrite->nSignals = o->signalCount(); objectToWrite->offsetToSignals = nextOffset; nextOffset += objectToWrite->nSignals * sizeof(quint32); @@ -1382,9 +1462,13 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) objectToWrite->offsetToBindings = nextOffset; nextOffset += objectToWrite->nBindings * sizeof(QV4::CompiledData::Binding); + objectToWrite->nNamedObjectsInComponent = o->namedObjectsInComponent.count; + objectToWrite->offsetToNamedObjectsInComponent = nextOffset; + nextOffset += objectToWrite->nNamedObjectsInComponent * sizeof(quint32); + quint32 *functionsTable = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToFunctions); for (const Function *f = o->firstFunction(); f; f = f->next) - *functionsTable++ = o->runtimeFunctionIndices->at(f->index); + *functionsTable++ = o->runtimeFunctionIndices.at(f->index); char *propertiesPtr = objectPtr + objectToWrite->offsetToProperties; for (const Property *p = o->firstProperty(); p; p = p->next) { @@ -1393,6 +1477,13 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) propertiesPtr += sizeof(QV4::CompiledData::Property); } + char *aliasesPtr = objectPtr + objectToWrite->offsetToAliases; + for (const Alias *a = o->firstAlias(); a; a = a->next) { + QV4::CompiledData::Alias *aliasToWrite = reinterpret_cast<QV4::CompiledData::Alias*>(aliasesPtr); + *aliasToWrite = *a; + aliasesPtr += sizeof(QV4::CompiledData::Alias); + } + char *bindingPtr = objectPtr + objectToWrite->offsetToBindings; bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isValueBindingNoAlias); bindingPtr = writeBindings(bindingPtr, o, &QV4::CompiledData::Binding::isSignalHandler); @@ -1421,7 +1512,12 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) signalPtr += size; } - objectPtr += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->signalCount(), o->bindingCount()); + quint32 *namedObjectInComponentPtr = reinterpret_cast<quint32*>(objectPtr + objectToWrite->offsetToNamedObjectsInComponent); + for (int i = 0; i < o->namedObjectsInComponent.count; ++i) { + *namedObjectInComponentPtr++ = o->namedObjectsInComponent.at(i); + } + + objectPtr += QV4::CompiledData::Object::calculateSizeExcludingSignals(o->functionCount(), o->propertyCount(), o->aliasCount(), o->signalCount(), o->bindingCount(), o->namedObjectsInComponent.count); objectPtr += signalTableSize; } @@ -1435,10 +1531,12 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output) output.jsGenerator.stringTable.serialize(qmlUnit); + qmlUnit->generateChecksum(); + return qmlUnit; } -char *QmlUnitGenerator::writeBindings(char *bindingPtr, Object *o, BindingFilter filter) const +char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o, BindingFilter filter) const { for (const Binding *b = o->firstBinding(); b; b = b->next) { if (!(b->*(filter))()) @@ -1446,7 +1544,7 @@ char *QmlUnitGenerator::writeBindings(char *bindingPtr, Object *o, BindingFilter QV4::CompiledData::Binding *bindingToWrite = reinterpret_cast<QV4::CompiledData::Binding*>(bindingPtr); *bindingToWrite = *b; if (b->type == QV4::CompiledData::Binding::Type_Script) - bindingToWrite->value.compiledScriptIndex = o->runtimeFunctionIndices->at(b->value.compiledScriptIndex); + bindingToWrite->value.compiledScriptIndex = o->runtimeFunctionIndices.at(b->value.compiledScriptIndex); bindingPtr += sizeof(QV4::CompiledData::Binding); } return bindingPtr; @@ -1614,7 +1712,7 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, if (tdata->isComplete()) { auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, qmlEngine->propertyCacheForType(tdata->compiledData()->metaTypeId)); + initMetaObjectResolver(newResolver, qmlEngine->propertyCacheForType(tdata->compilationUnit()->metaTypeId)); newResolver->flags |= AllPropertiesAreFinal; return newResolver->resolveMember(qmlEngine, newResolver, member); } @@ -1627,7 +1725,11 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, member->kind = QV4::IR::Member::MemberOfSingletonObject; return newResolver->resolveMember(qmlEngine, newResolver, member); } - } else if (const QMetaObject *attachedMeta = type->attachedPropertiesType(qmlEngine)) { + } +#if 0 + else if (const QMetaObject *attachedMeta = type->attachedPropertiesType(qmlEngine)) { + // Right now the attached property IDs are not stable and cannot be embedded in the + // code that is cached on disk. QQmlPropertyCache *cache = qmlEngine->cache(attachedMeta); auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; @@ -1635,6 +1737,7 @@ static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, member->setAttachedPropertiesId(type->attachedPropertiesId(qmlEngine)); return newResolver->resolveMember(qmlEngine, newResolver, member); } +#endif return result; } @@ -1742,20 +1845,20 @@ static QV4::IR::DiscoveredType resolveMetaObjectProperty( if (property->isEnum()) return QV4::IR::VarType; - switch (property->propType) { + switch (property->propType()) { case QMetaType::Bool: result = QV4::IR::BoolType; break; case QMetaType::Int: result = QV4::IR::SInt32Type; break; case QMetaType::Double: result = QV4::IR::DoubleType; break; case QMetaType::QString: result = QV4::IR::StringType; break; default: if (property->isQObject()) { - if (QQmlPropertyCache *cache = qmlEngine->propertyCacheForType(property->propType)) { + if (QQmlPropertyCache *cache = qmlEngine->propertyCacheForType(property->propType())) { auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; initMetaObjectResolver(newResolver, cache); return QV4::IR::DiscoveredType(newResolver); } - } else if (const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(property->propType)) { + } else if (const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(property->propType())) { if (QQmlPropertyCache *cache = qmlEngine->cache(valueTypeMetaObject)) { auto newResolver = resolver->owner->New<QV4::IR::MemberExpressionResolver>(); newResolver->owner = resolver->owner; @@ -1822,7 +1925,9 @@ QV4::IR::Expr *JSCodeGen::fallbackNameLookup(const QString &name, int line, int // Look for IDs first. foreach (const IdMapping &mapping, _idObjects) if (name == mapping.name) { - _function->idObjectDependencies.insert(mapping.idIndex); + if (_function->isQmlBinding) + _function->idObjectDependencies.insert(mapping.idIndex); + QV4::IR::Expr *s = _block->MEMBER(_block->TEMP(_qmlContextTemp), _function->newString(name), 0, QV4::IR::Member::MemberOfIdObjectsArray, mapping.idIndex); QV4::IR::Temp *result = _block->TEMP(_block->newTemp()); _block->MOVE(result, s); @@ -1907,7 +2012,7 @@ QV4::IR::Expr *JSCodeGen::fallbackNameLookup(const QString &name, int line, int #ifndef V4_BOOTSTRAP -QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision, RevisionCheck check) +QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision, RevisionCheck check) const { if (notInRevision) *notInRevision = false; @@ -1926,7 +2031,7 @@ QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRev } -QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevision) +QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevision) const { if (notInRevision) *notInRevision = false; @@ -1948,7 +2053,7 @@ QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevis d = property(propName, notInRevision); if (d) - return cache->signal(d->notifyIndex); + return cache->signal(d->notifyIndex()); } return 0; diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 057ed1be9f..cc16dc2104 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -113,7 +113,7 @@ struct PoolList T *insertPos = 0; for (T *it = first; it; it = it->next) { - if (!(it->*sortMember < item->*sortMember)) + if (!(it->*sortMember <= item->*sortMember)) break; insertPos = it; } @@ -161,6 +161,43 @@ struct PoolList } return result; } + + struct Iterator { + T *ptr; + + explicit Iterator(T *p) : ptr(p) {} + + T *operator->() { + return ptr; + } + + const T *operator->() const { + return ptr; + } + + T &operator*() { + return *ptr; + } + + const T &operator*() const { + return *ptr; + } + + void operator++() { + ptr = ptr->next; + } + + bool operator==(const Iterator &rhs) const { + return ptr == rhs.ptr; + } + + bool operator!=(const Iterator &rhs) const { + return ptr != rhs.ptr; + } + }; + + Iterator begin() { return Iterator(first); } + Iterator end() { return Iterator(nullptr); } }; template <typename T> @@ -170,7 +207,18 @@ class FixedPoolArray public: int count; - void init(QQmlJS::MemoryPool *pool, const QVector<T> &vector) + FixedPoolArray() + : data(0) + , count(0) + {} + + void allocate(QQmlJS::MemoryPool *pool, int size) + { + count = size; + data = reinterpret_cast<T*>(pool->allocate(count * sizeof(T))); + } + + void allocate(QQmlJS::MemoryPool *pool, const QVector<T> &vector) { count = vector.count(); data = reinterpret_cast<T*>(pool->allocate(count * sizeof(T))); @@ -183,6 +231,16 @@ public: } } + template <typename Container> + void allocate(QQmlJS::MemoryPool *pool, const Container &container) + { + count = container.count(); + data = reinterpret_cast<T*>(pool->allocate(count * sizeof(T))); + typename Container::ConstIterator it = container.constBegin(); + for (int i = 0; i < count; ++i) + new (data + i) T(*it++); + } + const T &at(int index) const { Q_ASSERT(index >= 0 && index < count); return data[index]; @@ -200,6 +258,9 @@ public: return i; return -1; } + + const T *begin() const { return data; } + const T *end() const { return data + count; } }; struct Object; @@ -217,6 +278,10 @@ struct Signal QStringList parameterStringList(const QV4::Compiler::StringTableGenerator *stringPool) const; + int parameterCount() const { return parameters->count; } + PoolList<SignalParameter>::Iterator parametersBegin() const { return parameters->begin(); } + PoolList<SignalParameter>::Iterator parametersEnd() const { return parameters->end(); } + Signal *next; }; @@ -227,16 +292,32 @@ struct Property : public QV4::CompiledData::Property struct Binding : public QV4::CompiledData::Binding { + // The offset in the source file where the binding appeared. This is used for sorting to ensure + // that assignments to list properties are done in the correct order. We use the offset here instead + // of Binding::location as the latter has limited precision. + quint32 offset; // Binding's compiledScriptIndex is index in object's functionsAndExpressions Binding *next; }; +struct Alias : public QV4::CompiledData::Alias +{ + Alias *next; +}; + struct Function { QQmlJS::AST::FunctionDeclaration *functionDeclaration; QV4::CompiledData::Location location; int nameIndex; quint32 index; // index in parsedQML::functions + FixedPoolArray<int> formals; + + // --- QQmlPropertyCacheCreator interface + const int *formalsBegin() const { return formals.begin(); } + const int *formalsEnd() const { return formals.end(); } + // --- + Function *next; }; @@ -265,14 +346,19 @@ struct Q_QML_PRIVATE_EXPORT Object Q_DECLARE_TR_FUNCTIONS(Object) public: quint32 inheritedTypeNameIndex; - quint32 idIndex; - int indexOfDefaultProperty; + quint32 idNameIndex; + int id; + int indexOfDefaultPropertyOrAlias; + bool defaultPropertyIsAlias; + quint32 flags; QV4::CompiledData::Location location; QV4::CompiledData::Location locationOfIdProperty; const Property *firstProperty() const { return properties->first; } int propertyCount() const { return properties->count; } + Alias *firstAlias() const { return aliases->first; } + int aliasCount() const { return aliases->count; } const Signal *firstSignal() const { return qmlSignals->first; } int signalCount() const { return qmlSignals->count; } Binding *firstBinding() const { return bindings->first; } @@ -280,16 +366,28 @@ public: const Function *firstFunction() const { return functions->first; } int functionCount() const { return functions->count; } + PoolList<Binding>::Iterator bindingsBegin() const { return bindings->begin(); } + PoolList<Binding>::Iterator bindingsEnd() const { return bindings->end(); } + PoolList<Property>::Iterator propertiesBegin() const { return properties->begin(); } + PoolList<Property>::Iterator propertiesEnd() const { return properties->end(); } + PoolList<Alias>::Iterator aliasesBegin() const { return aliases->begin(); } + PoolList<Alias>::Iterator aliasesEnd() const { return aliases->end(); } + PoolList<Signal>::Iterator signalsBegin() const { return qmlSignals->begin(); } + PoolList<Signal>::Iterator signalsEnd() const { return qmlSignals->end(); } + PoolList<Function>::Iterator functionsBegin() const { return functions->begin(); } + PoolList<Function>::Iterator functionsEnd() const { return functions->end(); } + // If set, then declarations for this object (and init bindings for these) should go into the // specified object. Used for declarations inside group properties. Object *declarationsOverride; - void init(QQmlJS::MemoryPool *pool, int typeNameIndex, int id, const QQmlJS::AST::SourceLocation &location = QQmlJS::AST::SourceLocation()); + void init(QQmlJS::MemoryPool *pool, int typeNameIndex, int idIndex, const QQmlJS::AST::SourceLocation &location = QQmlJS::AST::SourceLocation()); QString sanityCheckFunctionNames(const QSet<QString> &illegalNames, QQmlJS::AST::SourceLocation *errorLocation); QString appendSignal(Signal *signal); QString appendProperty(Property *prop, const QString &propertyName, bool isDefaultProperty, const QQmlJS::AST::SourceLocation &defaultToken, QQmlJS::AST::SourceLocation *errorLocation); + QString appendAlias(Alias *prop, const QString &aliasName, bool isDefaultProperty, const QQmlJS::AST::SourceLocation &defaultToken, QQmlJS::AST::SourceLocation *errorLocation); void appendFunction(QmlIR::Function *f); QString appendBinding(Binding *b, bool isListBinding); @@ -299,12 +397,17 @@ public: QString bindingAsString(Document *doc, int scriptIndex) const; PoolList<CompiledFunctionOrExpression> *functionsAndExpressions; - FixedPoolArray<int> *runtimeFunctionIndices; + FixedPoolArray<int> runtimeFunctionIndices; + + FixedPoolArray<quint32> namedObjectsInComponent; + int namedObjectsInComponentCount() const { return namedObjectsInComponent.count; } + const quint32 *namedObjectsInComponentTable() const { return namedObjectsInComponent.begin(); } private: friend struct IRLoader; PoolList<Property> *properties; + PoolList<Alias> *aliases; PoolList<Signal> *qmlSignals; PoolList<Binding> *bindings; PoolList<Function> *functions; @@ -330,15 +433,10 @@ struct Q_QML_PRIVATE_EXPORT Document QList<Pragma*> pragmas; QQmlJS::AST::UiProgram *program; int indexOfRootObject; - QList<Object*> objects; + QVector<Object*> objects; QV4::Compiler::JSUnitGenerator jsGenerator; - quint32 unitFlags; QQmlRefPointer<QV4::CompiledData::CompilationUnit> javaScriptCompilationUnit; - QHash<int, QStringList> extraSignalParameters; - - QV4::CompiledData::TypeReferenceMap typeReferences; - void collectTypeReferences(); int registerString(const QString &str) { return jsGenerator.registerString(str); } QString stringAt(int index) const { return jsGenerator.stringForIndex(index); } @@ -410,6 +508,8 @@ public: void appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLocation, const QQmlJS::AST::SourceLocation &nameLocation, quint32 propertyNameIndex, QQmlJS::AST::Statement *value); void appendBinding(const QQmlJS::AST::SourceLocation &qualifiedNameLocation, const QQmlJS::AST::SourceLocation &nameLocation, quint32 propertyNameIndex, int objectIndex, bool isListItem = false, bool isOnAssignment = false); + bool appendAlias(QQmlJS::AST::UiPublicMember *node); + Object *bindingsTarget() const; bool setId(const QQmlJS::AST::SourceLocation &idLocation, QQmlJS::AST::Statement *value); @@ -426,6 +526,7 @@ public: QString stringAt(int index) const { return jsGenerator->stringForIndex(index); } static bool isStatementNodeScript(QQmlJS::AST::Statement *statement); + static bool isRedundantNullInitializerForPropertyDeclaration(Property *property, QQmlJS::AST::Statement *statement); QList<QQmlJS::DiagnosticMessage> errors; @@ -433,7 +534,7 @@ public: QList<const QV4::CompiledData::Import *> _imports; QList<Pragma*> _pragmas; - QList<Object*> _objects; + QVector<Object*> _objects; QV4::CompiledData::TypeReferenceMap _typeReferences; @@ -447,21 +548,21 @@ public: struct Q_QML_PRIVATE_EXPORT QmlUnitGenerator { - QV4::CompiledData::Unit *generate(Document &output); + QV4::CompiledData::Unit *generate(Document &output, QQmlEngine *engine, const QV4::CompiledData::ResolvedTypeReferenceMap &dependentTypes); private: typedef bool (Binding::*BindingFilter)() const; - char *writeBindings(char *bindingPtr, Object *o, BindingFilter filter) const; + char *writeBindings(char *bindingPtr, const Object *o, BindingFilter filter) const; }; #ifndef V4_BOOTSTRAP struct Q_QML_EXPORT PropertyResolver { - PropertyResolver(QQmlPropertyCache *cache) + PropertyResolver(const QQmlPropertyCache *cache) : cache(cache) {} - QQmlPropertyData *property(int index) + QQmlPropertyData *property(int index) const { return cache->property(index); } @@ -471,12 +572,12 @@ struct Q_QML_EXPORT PropertyResolver IgnoreRevision }; - QQmlPropertyData *property(const QString &name, bool *notInRevision = 0, RevisionCheck check = CheckRevision); + QQmlPropertyData *property(const QString &name, bool *notInRevision = 0, RevisionCheck check = CheckRevision) const; // This code must match the semantics of QQmlPropertyPrivate::findSignalByName - QQmlPropertyData *signal(const QString &name, bool *notInRevision); + QQmlPropertyData *signal(const QString &name, bool *notInRevision) const; - QQmlPropertyCache *cache; + const QQmlPropertyCache *cache; }; #endif diff --git a/src/qml/compiler/qqmlpropertycachecreator.cpp b/src/qml/compiler/qqmlpropertycachecreator.cpp new file mode 100644 index 0000000000..f8d63ec634 --- /dev/null +++ b/src/qml/compiler/qqmlpropertycachecreator.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications 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 "qqmlpropertycachecreator_p.h" + +#include <private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +QAtomicInt QQmlPropertyCacheCreatorBase::classIndexCounter(0); + +QQmlBindingInstantiationContext::QQmlBindingInstantiationContext() + : referencingObjectIndex(-1) + , instantiatingBinding(nullptr) + , instantiatingProperty(nullptr) +{ + +} + +QQmlBindingInstantiationContext::QQmlBindingInstantiationContext(int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding, const QString &instantiatingPropertyName, const QQmlPropertyCache *referencingObjectPropertyCache) + : referencingObjectIndex(referencingObjectIndex) + , instantiatingBinding(instantiatingBinding) + , instantiatingProperty(nullptr) +{ + if (instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty) { + Q_ASSERT(referencingObjectIndex >= 0); + Q_ASSERT(referencingObjectPropertyCache); + Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); + + bool notInRevision = false; + instantiatingProperty = QmlIR::PropertyResolver(referencingObjectPropertyCache).property(instantiatingPropertyName, ¬InRevision); + } +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h new file mode 100644 index 0000000000..10bcd1dbc1 --- /dev/null +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -0,0 +1,741 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications 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 QQMLPROPERTYCACHECREATOR_P_H +#define QQMLPROPERTYCACHECREATOR_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 "qqmltypecompiler_p.h" +#include <private/qqmlvaluetype_p.h> +#include <private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +struct QQmlBindingInstantiationContext { + QQmlBindingInstantiationContext(); + QQmlBindingInstantiationContext(int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding, const QString &instantiatingPropertyName, const QQmlPropertyCache *referencingObjectPropertyCache); + int referencingObjectIndex; + const QV4::CompiledData::Binding *instantiatingBinding; + QQmlPropertyData *instantiatingProperty; +}; + +struct QQmlPropertyCacheCreatorBase +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreatorBase) +public: + static QAtomicInt classIndexCounter; +}; + +template <typename ObjectContainer> +class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase +{ +public: + typedef typename ObjectContainer::CompiledObject CompiledObject; + + QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports); + + QQmlCompileError buildMetaObjects(); + +protected: + QQmlCompileError buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context); + QQmlPropertyCache *propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const; + QQmlCompileError createMetaObject(int objectIndex, const CompiledObject *obj, QQmlPropertyCache *baseTypeCache); + + QString stringAt(int index) const { return objectContainer->stringAt(index); } + + QQmlEnginePrivate * const enginePrivate; + const ObjectContainer * const objectContainer; + const QQmlImports * const imports; + QQmlPropertyCacheVector *propertyCaches; +}; + +template <typename ObjectContainer> +inline QQmlPropertyCacheCreator<ObjectContainer>::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports) + : enginePrivate(enginePrivate) + , objectContainer(objectContainer) + , imports(imports) + , propertyCaches(propertyCaches) +{ + propertyCaches->resize(objectContainer->objectCount()); +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjects() +{ + QQmlBindingInstantiationContext context; + return buildMetaObjectRecursively(objectContainer->rootObjectIndex(), context); +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context) +{ + const CompiledObject *obj = objectContainer->objectAt(objectIndex); + + bool needVMEMetaObject = obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0; + if (!needVMEMetaObject) { + for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) { + if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { + // If the on assignment is inside a group property, we need to distinguish between QObject based + // group properties and value type group properties. For the former the base type is derived from + // the property that references us, for the latter we only need a meta-object on the referencing object + // because interceptors can't go to the shared value type instances. + if (context.instantiatingProperty && QQmlValueTypeFactory::isValueType(context.instantiatingProperty->propType())) { + if (!propertyCaches->needsVMEMetaObject(context.referencingObjectIndex)) { + const CompiledObject *obj = objectContainer->objectAt(context.referencingObjectIndex); + auto *typeRef = objectContainer->resolvedTypes.value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + QQmlPropertyCache *baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + QQmlCompileError error = createMetaObject(context.referencingObjectIndex, obj, baseTypeCache); + if (error.isSet()) + return error; + } + } else { + // On assignments are implemented using value interceptors, which require a VME meta object. + needVMEMetaObject = true; + } + break; + } + } + } + + QQmlPropertyCache *baseTypeCache; + { + QQmlCompileError error; + baseTypeCache = propertyCacheForObject(obj, context, &error); + if (error.isSet()) + return error; + } + + if (baseTypeCache) { + if (needVMEMetaObject) { + QQmlCompileError error = createMetaObject(objectIndex, obj, baseTypeCache); + if (error.isSet()) + return error; + } else { + propertyCaches->set(objectIndex, baseTypeCache); + } + } + + if (QQmlPropertyCache *thisCache = propertyCaches->at(objectIndex)) { + for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) + if (binding->type >= QV4::CompiledData::Binding::Type_Object) { + QQmlBindingInstantiationContext context(objectIndex, &(*binding), stringAt(binding->propertyNameIndex), thisCache); + QQmlCompileError error = buildMetaObjectRecursively(binding->value.objectIndex, context); + if (error.isSet()) + return error; + } + } + + QQmlCompileError noError; + return noError; +} + +template <typename ObjectContainer> +inline QQmlPropertyCache *QQmlPropertyCacheCreator<ObjectContainer>::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlCompileError *error) const +{ + if (context.instantiatingProperty) { + if (context.instantiatingProperty->isQObject()) { + return enginePrivate->rawPropertyCacheForType(context.instantiatingProperty->propType()); + } else if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(context.instantiatingProperty->propType())) { + return enginePrivate->cache(vtmo); + } + } else if (obj->inheritedTypeNameIndex != 0) { + auto *typeRef = objectContainer->resolvedTypes.value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + + if (typeRef->isFullyDynamicType) { + if (obj->propertyCount() > 0 || obj->aliasCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new properties.")); + return nullptr; + } + if (obj->signalCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new signals.")); + return nullptr; + } + if (obj->functionCount() > 0) { + *error = QQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully Dynamic types cannot declare new functions.")); + return nullptr; + } + } + + return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { + auto *typeRef = objectContainer->resolvedTypes.value(context.instantiatingBinding->propertyNameIndex); + Q_ASSERT(typeRef); + QQmlType *qmltype = typeRef->type; + if (!qmltype) { + QString propertyName = stringAt(context.instantiatingBinding->propertyNameIndex); + if (imports->resolveType(propertyName, &qmltype, 0, 0, 0)) { + if (qmltype->isComposite()) { + QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + qmltype = QQmlMetaType::qmlType(compilationUnit->metaTypeId); + + tdata->release(); + } + } + } + + const QMetaObject *attachedMo = qmltype ? qmltype->attachedPropertiesType(enginePrivate) : 0; + if (!attachedMo) { + *error = QQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); + return nullptr; + } + return enginePrivate->cache(attachedMo); + } + return nullptr; +} + +template <typename ObjectContainer> +inline QQmlCompileError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int objectIndex, const CompiledObject *obj, QQmlPropertyCache *baseTypeCache) +{ + QQmlRefPointer<QQmlPropertyCache> cache; + cache.adopt(baseTypeCache->copyAndReserve(obj->propertyCount() + obj->aliasCount(), + obj->functionCount() + obj->propertyCount() + obj->aliasCount() + obj->signalCount(), + obj->signalCount() + obj->propertyCount() + obj->aliasCount())); + + propertyCaches->set(objectIndex, cache); + propertyCaches->setNeedsVMEMetaObject(objectIndex); + + struct TypeData { + QV4::CompiledData::Property::Type dtype; + int metaType; + } builtinTypes[] = { + { QV4::CompiledData::Property::Var, QMetaType::QVariant }, + { QV4::CompiledData::Property::Variant, QMetaType::QVariant }, + { QV4::CompiledData::Property::Int, QMetaType::Int }, + { QV4::CompiledData::Property::Bool, QMetaType::Bool }, + { QV4::CompiledData::Property::Real, QMetaType::Double }, + { QV4::CompiledData::Property::String, QMetaType::QString }, + { QV4::CompiledData::Property::Url, QMetaType::QUrl }, + { QV4::CompiledData::Property::Color, QMetaType::QColor }, + { QV4::CompiledData::Property::Font, QMetaType::QFont }, + { QV4::CompiledData::Property::Time, QMetaType::QTime }, + { QV4::CompiledData::Property::Date, QMetaType::QDate }, + { QV4::CompiledData::Property::DateTime, QMetaType::QDateTime }, + { QV4::CompiledData::Property::Rect, QMetaType::QRectF }, + { QV4::CompiledData::Property::Point, QMetaType::QPointF }, + { QV4::CompiledData::Property::Size, QMetaType::QSizeF }, + { QV4::CompiledData::Property::Vector2D, QMetaType::QVector2D }, + { QV4::CompiledData::Property::Vector3D, QMetaType::QVector3D }, + { QV4::CompiledData::Property::Vector4D, QMetaType::QVector4D }, + { QV4::CompiledData::Property::Matrix4x4, QMetaType::QMatrix4x4 }, + { QV4::CompiledData::Property::Quaternion, QMetaType::QQuaternion } +}; + static const uint builtinTypeCount = sizeof(builtinTypes) / sizeof(TypeData); + + QByteArray newClassName; + + if (objectIndex == objectContainer->rootObjectIndex()) { + const QString path = objectContainer->url().path(); + int lastSlash = path.lastIndexOf(QLatin1Char('/')); + if (lastSlash > -1) { + const QStringRef nameBase = path.midRef(lastSlash + 1, path.length() - lastSlash - 5); + if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) + newClassName = nameBase.toUtf8() + "_QMLTYPE_" + + QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1)); + } + } + if (newClassName.isEmpty()) { + newClassName = QQmlMetaObject(baseTypeCache).className(); + newClassName.append("_QML_"); + newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); + } + + cache->_dynamicClassName = newClassName; + + int varPropCount = 0; + + QmlIR::PropertyResolver resolver(baseTypeCache); + + for (auto p = obj->propertiesBegin(), end = obj->propertiesEnd(); p != end; ++p) { + if (p->type == QV4::CompiledData::Property::Var) + varPropCount++; + + bool notInRevision = false; + QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); + if (d && d->isFinal()) + return QQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); + } + + for (auto a = obj->aliasesBegin(), end = obj->aliasesEnd(); a != end; ++a) { + bool notInRevision = false; + QQmlPropertyData *d = resolver.property(stringAt(a->nameIndex), ¬InRevision); + if (d && d->isFinal()) + return QQmlCompileError(a->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); + } + + int effectivePropertyIndex = cache->propertyIndexCacheStart; + int effectiveMethodIndex = cache->methodIndexCacheStart; + + // For property change signal override detection. + // We prepopulate a set of signal names which already exist in the object, + // and throw an error if there is a signal/method defined as an override. + QSet<QString> seenSignals; + seenSignals << QStringLiteral("destroyed") << QStringLiteral("parentChanged") << QStringLiteral("objectNameChanged"); + QQmlPropertyCache *parentCache = cache; + while ((parentCache = parentCache->parent())) { + if (int pSigCount = parentCache->signalCount()) { + int pSigOffset = parentCache->signalOffset(); + for (int i = pSigOffset; i < pSigCount; ++i) { + QQmlPropertyData *currPSig = parentCache->signal(i); + // XXX TODO: find a better way to get signal name from the property data :-/ + for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); + iter != parentCache->stringCache.end(); ++iter) { + if (currPSig == (*iter).second) { + seenSignals.insert(iter.key()); + break; + } + } + } + } + } + + // Set up notify signals for properties - first normal, then alias + for (auto p = obj->propertiesBegin(), end = obj->propertiesEnd(); p != end; ++p) { + auto flags = QQmlPropertyData::defaultSignalFlags(); + + QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); + seenSignals.insert(changedSigName); + + cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); + } + + for (auto a = obj->aliasesBegin(), end = obj->aliasesEnd(); a != end; ++a) { + auto flags = QQmlPropertyData::defaultSignalFlags(); + + QString changedSigName = stringAt(a->nameIndex) + QLatin1String("Changed"); + seenSignals.insert(changedSigName); + + cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); + } + + // Dynamic signals + for (auto s = obj->signalsBegin(), end = obj->signalsEnd(); s != end; ++s) { + const int paramCount = s->parameterCount(); + + QList<QByteArray> names; + names.reserve(paramCount); + QVarLengthArray<int, 10> paramTypes(paramCount?(paramCount + 1):0); + + if (paramCount) { + paramTypes[0] = paramCount; + + int i = 0; + for (auto param = s->parametersBegin(), end = s->parametersEnd(); param != end; ++param, ++i) { + names.append(stringAt(param->nameIndex).toUtf8()); + if (param->type < builtinTypeCount) { + // built-in type + paramTypes[i + 1] = builtinTypes[param->type].metaType; + } else { + // lazily resolved type + Q_ASSERT(param->type == QV4::CompiledData::Property::Custom); + const QString customTypeName = stringAt(param->customTypeNameIndex); + QQmlType *qmltype = 0; + if (!imports->resolveType(customTypeName, &qmltype, 0, 0, 0)) + return QQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Invalid signal parameter type: %1").arg(customTypeName)); + + if (qmltype->isComposite()) { + QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + + paramTypes[i + 1] = compilationUnit->metaTypeId; + + tdata->release(); + } else { + paramTypes[i + 1] = qmltype->typeId(); + } + } + } + } + + auto flags = QQmlPropertyData::defaultSignalFlags(); + if (paramCount) + flags.hasArguments = true; + + QString signalName = stringAt(s->nameIndex); + if (seenSignals.contains(signalName)) + return QQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Duplicate signal name: invalid override of property change signal or superclass signal")); + seenSignals.insert(signalName); + + cache->appendSignal(signalName, flags, effectiveMethodIndex++, + paramCount?paramTypes.constData():0, names); + } + + + // Dynamic slots + for (auto function = objectContainer->objectFunctionsBegin(obj), end = objectContainer->objectFunctionsEnd(obj); function != end; ++function) { + auto flags = QQmlPropertyData::defaultSlotFlags(); + + const QString slotName = stringAt(function->nameIndex); + if (seenSignals.contains(slotName)) + return QQmlCompileError(function->location, QQmlPropertyCacheCreatorBase::tr("Duplicate method name: invalid override of property change signal or superclass signal")); + // Note: we don't append slotName to the seenSignals list, since we don't + // protect against overriding change signals or methods with properties. + + QList<QByteArray> parameterNames; + for (auto formal = function->formalsBegin(), end = function->formalsEnd(); formal != end; ++formal) { + flags.hasArguments = true; + parameterNames << stringAt(*formal).toUtf8(); + } + + cache->appendMethod(slotName, flags, effectiveMethodIndex++, parameterNames); + } + + + // Dynamic properties + int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; + int propertyIdx = 0; + for (auto p = obj->propertiesBegin(), end = obj->propertiesEnd(); p != end; ++p, ++propertyIdx) { + int propertyType = 0; + QQmlPropertyData::Flags propertyFlags; + + if (p->type == QV4::CompiledData::Property::Var) { + propertyType = QMetaType::QVariant; + propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType; + } else if (p->type < builtinTypeCount) { + propertyType = builtinTypes[p->type].metaType; + + if (p->type == QV4::CompiledData::Property::Variant) + propertyFlags.type = QQmlPropertyData::Flags::QVariantType; + } else { + Q_ASSERT(p->type == QV4::CompiledData::Property::CustomList || + p->type == QV4::CompiledData::Property::Custom); + + QQmlType *qmltype = 0; + if (!imports->resolveType(stringAt(p->customTypeNameIndex), &qmltype, 0, 0, 0)) { + return QQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Invalid property type")); + } + + Q_ASSERT(qmltype); + if (qmltype->isComposite()) { + QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); + Q_ASSERT(tdata); + Q_ASSERT(tdata->isComplete()); + + auto compilationUnit = tdata->compilationUnit(); + + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = compilationUnit->metaTypeId; + } else { + propertyType = compilationUnit->listMetaTypeId; + } + + tdata->release(); + } else { + if (p->type == QV4::CompiledData::Property::Custom) { + propertyType = qmltype->typeId(); + } else { + propertyType = qmltype->qListTypeId(); + } + } + + if (p->type == QV4::CompiledData::Property::Custom) + propertyFlags.type = QQmlPropertyData::Flags::QObjectDerivedType; + else + propertyFlags.type = QQmlPropertyData::Flags::QListType; + } + + if (!(p->flags & QV4::CompiledData::Property::IsReadOnly) && p->type != QV4::CompiledData::Property::CustomList) + propertyFlags.isWritable = true; + + + QString propertyName = stringAt(p->nameIndex); + if (!obj->defaultPropertyIsAlias && propertyIdx == obj->indexOfDefaultPropertyOrAlias) + cache->_defaultPropertyName = propertyName; + cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + propertyType, effectiveSignalIndex); + + effectiveSignalIndex++; + } + + QQmlCompileError noError; + return noError; +} + +template <typename ObjectContainer> +class QQmlPropertyCacheAliasCreator +{ +public: + typedef typename ObjectContainer::CompiledObject CompiledObject; + + QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); + + void appendAliasPropertiesToMetaObjects(); + + void appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex); + +private: + void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex); + void propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, QQmlPropertyRawData::Flags *propertyFlags); + + void collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const; + + int objectForId(const CompiledObject &component, int id) const; + + QQmlPropertyCacheVector *propertyCaches; + const ObjectContainer *objectContainer; +}; + +template <typename ObjectContainer> +inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) + : propertyCaches(propertyCaches) + , objectContainer(objectContainer) +{ + +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesToMetaObjects() +{ + for (int i = 0; i < objectContainer->objectCount(); ++i) { + const CompiledObject &component = *objectContainer->objectAt(i); + if (!(component.flags & QV4::CompiledData::Object::IsComponent)) + continue; + + const auto rootBinding = component.bindingsBegin(); + appendAliasPropertiesInMetaObjectsWithinComponent(component, rootBinding->value.objectIndex); + } + + const int rootObjectIndex = objectContainer->rootObjectIndex(); + appendAliasPropertiesInMetaObjectsWithinComponent(*objectContainer->objectAt(rootObjectIndex), rootObjectIndex); +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex) +{ + QVector<int> objectsWithAliases; + collectObjectsWithAliasesRecursively(firstObjectIndex, &objectsWithAliases); + if (objectsWithAliases.isEmpty()) + return; + + const auto allAliasTargetsExist = [this, &component](const CompiledObject &object) { + for (auto alias = object.aliasesBegin(), end = object.aliasesEnd(); alias != end; ++alias) { + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + + const int targetObjectIndex = objectForId(component, alias->targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + + if (alias->aliasToLocalAlias) + continue; + + if (alias->encodedMetaPropertyIndex == -1) + continue; + + const QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); + Q_ASSERT(targetCache); + + int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + QQmlPropertyData *targetProperty = targetCache->property(coreIndex); + if (!targetProperty) + return false; + } + return true; + }; + + do { + QVector<int> pendingObjects; + + for (int objectIndex: qAsConst(objectsWithAliases)) { + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + + if (allAliasTargetsExist(object)) { + appendAliasesToPropertyCache(component, objectIndex); + } else { + pendingObjects.append(objectIndex); + } + + } + qSwap(objectsWithAliases, pendingObjects); + } while (!objectsWithAliases.isEmpty()); +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const +{ + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + if (object.aliasCount() > 0) + objectsWithAliases->append(objectIndex); + + // Stop at Component boundary + if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != objectContainer->rootObjectIndex()) + return; + + for (auto binding = object.bindingsBegin(), end = object.bindingsEnd(); binding != end; ++binding) { + if (binding->type != QV4::CompiledData::Binding::Type_Object + && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty + && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) + continue; + + collectObjectsWithAliasesRecursively(binding->value.objectIndex, objectsWithAliases); + } +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, + QQmlPropertyData::Flags *propertyFlags) +{ + const int targetObjectIndex = objectForId(component, alias.targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + + const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); + + *type = 0; + bool writable = false; + bool resettable = false; + + propertyFlags->isAlias = true; + + if (alias.aliasToLocalAlias) { + auto targetAlias = targetObject.aliasesBegin(); + for (uint i = 0; i < alias.localAliasIndex; ++i) + ++targetAlias; + propertyDataForAlias(component, *targetAlias, type, propertyFlags); + return; + } else if (alias.encodedMetaPropertyIndex == -1) { + Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); + auto *typeRef = objectContainer->resolvedTypes.value(targetObject.inheritedTypeNameIndex); + Q_ASSERT(typeRef); + + if (typeRef->type) + *type = typeRef->type->typeId(); + else + *type = typeRef->compilationUnit->metaTypeId; + + propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; + } else { + int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); + int valueTypeIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).valueTypeIndex(); + + QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); + Q_ASSERT(targetCache); + QQmlPropertyData *targetProperty = targetCache->property(coreIndex); + Q_ASSERT(targetProperty); + + *type = targetProperty->propType(); + + writable = targetProperty->isWritable(); + resettable = targetProperty->isResettable(); + + if (valueTypeIndex != -1) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(*type); + if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) + *type = QVariant::Int; + else + *type = valueTypeMetaObject->property(valueTypeIndex).userType(); + } else { + if (targetProperty->isEnum()) { + *type = QVariant::Int; + } else { + // Copy type flags + propertyFlags->copyPropertyTypeFlags(targetProperty->flags()); + + if (targetProperty->isVarProperty()) + propertyFlags->type = QQmlPropertyData::Flags::QVariantType; + } + } + } + + propertyFlags->isWritable = !(alias.flags & QV4::CompiledData::Property::IsReadOnly) && writable; + propertyFlags->isResettable = resettable; +} + +template <typename ObjectContainer> +inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( + const CompiledObject &component, int objectIndex) +{ + const CompiledObject &object = *objectContainer->objectAt(objectIndex); + if (!object.aliasCount()) + return; + + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); + Q_ASSERT(propertyCache); + + int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); + int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); + + int aliasIndex = 0; + for (auto alias = object.aliasesBegin(), end = object.aliasesEnd(); alias != end; ++alias, ++aliasIndex) { + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); + + int type = 0; + QQmlPropertyData::Flags propertyFlags; + propertyDataForAlias(component, *alias, &type, &propertyFlags); + + const QString propertyName = objectContainer->stringAt(alias->nameIndex); + + if (object.defaultPropertyIsAlias && aliasIndex == object.indexOfDefaultPropertyOrAlias) + propertyCache->_defaultPropertyName = propertyName; + + propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + type, effectiveSignalIndex++); + } +} + +template <typename ObjectContainer> +inline int QQmlPropertyCacheAliasCreator<ObjectContainer>::objectForId(const CompiledObject &component, int id) const +{ + for (quint32 i = 0, count = component.namedObjectsInComponentCount(); i < count; ++i) { + const int candidateIndex = component.namedObjectsInComponentTable()[i]; + const CompiledObject &candidate = *objectContainer->objectAt(candidateIndex); + if (candidate.id == id) + return candidateIndex; + } + return -1; +} + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYCACHECREATOR_P_H diff --git a/src/qml/compiler/qqmlpropertyvalidator.cpp b/src/qml/compiler/qqmlpropertyvalidator.cpp new file mode 100644 index 0000000000..45379d5155 --- /dev/null +++ b/src/qml/compiler/qqmlpropertyvalidator.cpp @@ -0,0 +1,703 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications 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 "qqmlpropertyvalidator_p.h" + +#include <private/qqmlcustomparser_p.h> +#include <private/qqmlstringconverters_p.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_NAMESPACE + +QQmlPropertyValidator::QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, QV4::CompiledData::CompilationUnit *compilationUnit) + : enginePrivate(enginePrivate) + , imports(imports) + , qmlUnit(compilationUnit->data) + , resolvedTypes(compilationUnit->resolvedTypes) + , propertyCaches(compilationUnit->propertyCaches) + , bindingPropertyDataPerObject(&compilationUnit->bindingPropertyDataPerObject) +{ + bindingPropertyDataPerObject->resize(qmlUnit->nObjects); +} + +QVector<QQmlCompileError> QQmlPropertyValidator::validate() +{ + return validateObject(qmlUnit->indexOfRootObject, /*instantiatingBinding*/0); +} + +typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; + +struct BindingFinder +{ + bool operator()(quint32 name, const QV4::CompiledData::Binding *binding) const + { + return name < binding->propertyNameIndex; + } + bool operator()(const QV4::CompiledData::Binding *binding, quint32 name) const + { + return binding->propertyNameIndex < name; + } + bool operator()(const QV4::CompiledData::Binding *lhs, const QV4::CompiledData::Binding *rhs) const + { + return lhs->propertyNameIndex < rhs->propertyNameIndex; + } +}; + +QVector<QQmlCompileError> QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty) const +{ + const QV4::CompiledData::Object *obj = qmlUnit->objectAt(objectIndex); + + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->nBindings == 1); + const QV4::CompiledData::Binding *componentBinding = obj->bindingTable(); + Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); + return validateObject(componentBinding->value.objectIndex, componentBinding); + } + + QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); + if (!propertyCache) + return QVector<QQmlCompileError>(); + + QStringList deferredPropertyNames; + { + const QMetaObject *mo = propertyCache->firstCppMetaObject(); + const int namesIndex = mo->indexOfClassInfo("DeferredPropertyNames"); + if (namesIndex != -1) { + QMetaClassInfo classInfo = mo->classInfo(namesIndex); + deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(QLatin1Char(',')); + } + } + + QQmlCustomParser *customParser = 0; + if (auto typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { + if (typeRef->type) + customParser = typeRef->type->customParser(); + } + + QList<const QV4::CompiledData::Binding*> customBindings; + + // Collect group properties first for sanity checking + // vector values are sorted by property name string index. + GroupPropertyVector groupProperties; + const QV4::CompiledData::Binding *binding = obj->bindingTable(); + for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + if (!binding->isGroupProperty()) + continue; + + if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) + continue; + + if (populatingValueTypeGroupProperty) { + return recordError(binding->location, tr("Property assignment expected")); + } + + GroupPropertyVector::const_iterator pos = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); + groupProperties.insert(pos, binding); + } + + QmlIR::PropertyResolver propertyResolver(propertyCache); + + QString defaultPropertyName; + QQmlPropertyData *defaultProperty = 0; + if (obj->indexOfDefaultPropertyOrAlias != -1) { + QQmlPropertyCache *cache = propertyCache->parent(); + defaultPropertyName = cache->defaultPropertyName(); + defaultProperty = cache->defaultProperty(); + } else { + defaultPropertyName = propertyCache->defaultPropertyName(); + defaultProperty = propertyCache->defaultProperty(); + } + + QV4::CompiledData::BindingPropertyData collectedBindingPropertyData(obj->nBindings); + + binding = obj->bindingTable(); + for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + QString name = stringAt(binding->propertyNameIndex); + + if (customParser) { + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { + customBindings << binding; + continue; + } + } else if (QmlIR::IRBuilder::isSignalPropertyName(name) + && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { + customBindings << binding; + continue; + } + } + + bool bindingToDefaultProperty = false; + bool isGroupProperty = instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty; + + bool notInRevision = false; + QQmlPropertyData *pd = 0; + if (!name.isEmpty()) { + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + pd = propertyResolver.signal(name, ¬InRevision); + else + pd = propertyResolver.property(name, ¬InRevision, isGroupProperty ? QmlIR::PropertyResolver::IgnoreRevision : QmlIR::PropertyResolver::CheckRevision); + + if (notInRevision) { + QString typeName = stringAt(obj->inheritedTypeNameIndex); + auto *objectType = resolvedTypes.value(obj->inheritedTypeNameIndex); + if (objectType && objectType->type) { + return recordError(binding->location, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(name).arg(objectType->type->module()).arg(objectType->majorVersion).arg(objectType->minorVersion)); + } else { + return recordError(binding->location, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(name)); + } + } + } else { + if (isGroupProperty) + return recordError(binding->location, tr("Cannot assign a value directly to a grouped property")); + + pd = defaultProperty; + name = defaultPropertyName; + bindingToDefaultProperty = true; + } + + if (pd) + collectedBindingPropertyData[i] = pd; + + if (name.constData()->isUpper() && !binding->isAttachedProperty()) { + QQmlType *type = 0; + QQmlImportNamespace *typeNamespace = 0; + imports.resolveType(stringAt(binding->propertyNameIndex), &type, 0, 0, &typeNamespace); + if (typeNamespace) + return recordError(binding->location, tr("Invalid use of namespace")); + return recordError(binding->location, tr("Invalid attached object assignment")); + } + + if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { + const QVector<QQmlCompileError> subObjectValidatorErrors = validateObject(binding->value.objectIndex, binding, pd && QQmlValueTypeFactory::metaObjectForMetaType(pd->propType())); + if (!subObjectValidatorErrors.isEmpty()) + return subObjectValidatorErrors; + } + + // Signal handlers were resolved and checked earlier in the signal handler conversion pass. + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression + || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) + continue; + + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + if (instantiatingBinding && (instantiatingBinding->isAttachedProperty() || instantiatingBinding->isGroupProperty())) { + return recordError(binding->location, tr("Attached properties cannot be used here")); + } + continue; + } + + if (pd) { + GroupPropertyVector::const_iterator assignedGroupProperty = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); + const bool assigningToGroupProperty = assignedGroupProperty != groupProperties.constEnd() && !(binding->propertyNameIndex < (*assignedGroupProperty)->propertyNameIndex); + + if (!pd->isWritable() + && !pd->isQList() + && !binding->isGroupProperty() + && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration) + ) { + + if (assigningToGroupProperty && binding->type < QV4::CompiledData::Binding::Type_Object) + return recordError(binding->valueLocation, tr("Cannot assign a value directly to a grouped property")); + return recordError(binding->valueLocation, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); + } + + if (!pd->isQList() && (binding->flags & QV4::CompiledData::Binding::IsListItem)) { + QString error; + if (pd->propType() == qMetaTypeId<QQmlScriptString>()) + error = tr( "Cannot assign multiple values to a script property"); + else + error = tr( "Cannot assign multiple values to a singular property"); + return recordError(binding->valueLocation, error); + } + + if (!bindingToDefaultProperty + && !binding->isGroupProperty() + && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment) + && assigningToGroupProperty) { + QV4::CompiledData::Location loc = binding->valueLocation; + if (loc < (*assignedGroupProperty)->valueLocation) + loc = (*assignedGroupProperty)->valueLocation; + + if (pd && QQmlValueTypeFactory::isValueType(pd->propType())) + return recordError(loc, tr("Property has already been assigned a value")); + return recordError(loc, tr("Cannot assign a value directly to a grouped property")); + } + + if (binding->type < QV4::CompiledData::Binding::Type_Script) { + QQmlCompileError bindingError = validateLiteralBinding(propertyCache, pd, binding); + if (bindingError.isSet()) + return recordError(bindingError); + } else if (binding->type == QV4::CompiledData::Binding::Type_Object) { + QQmlCompileError bindingError = validateObjectBinding(pd, name, binding); + if (bindingError.isSet()) + return recordError(bindingError); + } else if (binding->isGroupProperty()) { + if (QQmlValueTypeFactory::isValueType(pd->propType())) { + if (QQmlValueTypeFactory::metaObjectForMetaType(pd->propType())) { + if (!pd->isWritable()) { + return recordError(binding->location, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); + } + } else { + return recordError(binding->location, tr("Invalid grouped property access")); + } + } else { + if (!enginePrivate->propertyCacheForType(pd->propType())) { + return recordError(binding->location, tr("Invalid grouped property access")); + } + } + } + } else { + if (customParser) { + customBindings << binding; + continue; + } + if (bindingToDefaultProperty) { + return recordError(binding->location, tr("Cannot assign to non-existent default property")); + } else { + return recordError(binding->location, tr("Cannot assign to non-existent property \"%1\"").arg(name)); + } + } + } + + if (obj->idNameIndex) { + bool notInRevision = false; + collectedBindingPropertyData << propertyResolver.property(QStringLiteral("id"), ¬InRevision); + } + + if (customParser && !customBindings.isEmpty()) { + customParser->clearErrors(); + customParser->validator = this; + customParser->engine = enginePrivate; + customParser->imports = &imports; + customParser->verifyBindings(qmlUnit, customBindings); + customParser->validator = 0; + customParser->engine = 0; + customParser->imports = (QQmlImports*)0; + QVector<QQmlCompileError> parserErrors = customParser->errors(); + if (!parserErrors.isEmpty()) + return parserErrors; + } + + (*bindingPropertyDataPerObject)[objectIndex] = collectedBindingPropertyData; + + QVector<QQmlCompileError> noError; + return noError; +} + +QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const +{ + if (property->isQList()) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign primitives to lists")); + } + + QQmlCompileError noError; + + if (property->isEnum()) { + if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) + return noError; + + QString value = binding->valueAsString(qmlUnit); + QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex()); + bool ok; + if (p.isFlagType()) { + p.enumerator().keysToValue(value.toUtf8().constData(), &ok); + } else + p.enumerator().keyToValue(value.toUtf8().constData(), &ok); + + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unknown enumeration")); + } + return noError; + } + + switch (property->propType()) { + case QMetaType::QVariant: + break; + case QVariant::String: { + if (!binding->evaluatesToString()) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: string expected")); + } + } + break; + case QVariant::StringList: { + if (!binding->evaluatesToString()) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: string or string list expected")); + } + } + break; + case QVariant::ByteArray: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: byte array expected")); + } + } + break; + case QVariant::Url: { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: url expected")); + } + } + break; + case QVariant::UInt: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = binding->valueAsNumber(); + if (double(uint(d)) == d) + return noError; + } + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unsigned int expected")); + } + break; + case QVariant::Int: { + if (binding->type == QV4::CompiledData::Binding::Type_Number) { + double d = binding->valueAsNumber(); + if (double(int(d)) == d) + return noError; + } + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: int expected")); + } + break; + case QMetaType::Float: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: number expected")); + } + } + break; + case QVariant::Double: { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: number expected")); + } + } + break; + case QVariant::Color: { + bool ok = false; + QQmlStringConverters::rgbaFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: color expected")); + } + } + break; +#ifndef QT_NO_DATESTRING + case QVariant::Date: { + bool ok = false; + QQmlStringConverters::dateFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: date expected")); + } + } + break; + case QVariant::Time: { + bool ok = false; + QQmlStringConverters::timeFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: time expected")); + } + } + break; + case QVariant::DateTime: { + bool ok = false; + QQmlStringConverters::dateTimeFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: datetime expected")); + } + } + break; +#endif // QT_NO_DATESTRING + case QVariant::Point: { + bool ok = false; + QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::PointF: { + bool ok = false; + QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::Size: { + bool ok = false; + QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: size expected")); + } + } + break; + case QVariant::SizeF: { + bool ok = false; + QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: size expected")); + } + } + break; + case QVariant::Rect: { + bool ok = false; + QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: rect expected")); + } + } + break; + case QVariant::RectF: { + bool ok = false; + QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); + if (!ok) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: point expected")); + } + } + break; + case QVariant::Bool: { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: boolean expected")); + } + } + break; + case QVariant::Vector2D: { + struct { + float xp; + float yp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector2D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: 2D vector expected")); + } + } + break; + case QVariant::Vector3D: { + struct { + float xp; + float yp; + float zy; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector3D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: 3D vector expected")); + } + } + break; + case QVariant::Vector4D: { + struct { + float xp; + float yp; + float zy; + float wp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QVector4D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: 4D vector expected")); + } + } + break; + case QVariant::Quaternion: { + struct { + float wp; + float xp; + float yp; + float zp; + } vec; + if (!QQmlStringConverters::createFromString(QMetaType::QQuaternion, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: quaternion expected")); + } + } + break; + case QVariant::RegExp: + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); + default: { + // generate single literal value assignment to a list property if required + if (property->propType() == qMetaTypeId<QList<qreal> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Number) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: number or array of numbers expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<int> >()) { + bool ok = (binding->type == QV4::CompiledData::Binding::Type_Number); + if (ok) { + double n = binding->valueAsNumber(); + if (double(int(n)) != n) + ok = false; + } + if (!ok) + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: int or array of ints expected")); + break; + } else if (property->propType() == qMetaTypeId<QList<bool> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: bool or array of bools expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<QUrl> >()) { + if (binding->type != QV4::CompiledData::Binding::Type_String) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: url or array of urls expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QList<QString> >()) { + if (!binding->evaluatesToString()) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: string or array of strings expected")); + } + break; + } else if (property->propType() == qMetaTypeId<QJSValue>()) { + break; + } else if (property->propType() == qMetaTypeId<QQmlScriptString>()) { + break; + } + + // otherwise, try a custom type assignment + QQmlMetaType::StringConverter converter = QQmlMetaType::customStringConverter(property->propType()); + if (!converter) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QMetaType::typeName(property->propType())))); + } + } + break; + } + return noError; +} + +/*! + Returns true if from can be assigned to a (QObject) property of type + to. +*/ +bool QQmlPropertyValidator::canCoerce(int to, QQmlPropertyCache *fromMo) const +{ + QQmlPropertyCache *toMo = enginePrivate->rawPropertyCacheForType(to); + + while (fromMo) { + if (fromMo == toMo) + return true; + fromMo = fromMo->parent(); + } + return false; +} + +QVector<QQmlCompileError> QQmlPropertyValidator::recordError(const QV4::CompiledData::Location &location, const QString &description) const +{ + QVector<QQmlCompileError> errors; + errors.append(QQmlCompileError(location, description)); + return errors; +} + +QVector<QQmlCompileError> QQmlPropertyValidator::recordError(const QQmlCompileError &error) const +{ + QVector<QQmlCompileError> errors; + errors.append(error); + return errors; +} + +QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const +{ + QQmlCompileError noError; + + if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) { + Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Object); + + bool isValueSource = false; + bool isPropertyInterceptor = false; + + QQmlType *qmlType = 0; + const QV4::CompiledData::Object *targetObject = qmlUnit->objectAt(binding->value.objectIndex); + if (auto *typeRef = resolvedTypes.value(targetObject->inheritedTypeNameIndex)) { + QQmlPropertyCache *cache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); + const QMetaObject *mo = cache->firstCppMetaObject(); + while (mo && !qmlType) { + qmlType = QQmlMetaType::qmlType(mo); + mo = mo->superClass(); + } + Q_ASSERT(qmlType); + } + + if (qmlType) { + isValueSource = qmlType->propertyValueSourceCast() != -1; + isPropertyInterceptor = qmlType->propertyValueInterceptorCast() != -1; + } + + if (!isValueSource && !isPropertyInterceptor) { + return QQmlCompileError(binding->valueLocation, tr("\"%1\" cannot operate on \"%2\"").arg(stringAt(targetObject->inheritedTypeNameIndex)).arg(propertyName)); + } + + return noError; + } + + if (QQmlMetaType::isInterface(property->propType())) { + // Can only check at instantiation time if the created sub-object successfully casts to the + // target interface. + return noError; + } else if (property->propType() == QMetaType::QVariant) { + // We can convert everything to QVariant :) + return noError; + } else if (property->isQList()) { + const int listType = enginePrivate->listType(property->propType()); + if (!QQmlMetaType::isInterface(listType)) { + QQmlPropertyCache *source = propertyCaches.at(binding->value.objectIndex); + if (!canCoerce(listType, source)) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign object to list property \"%1\"").arg(propertyName)); + } + } + return noError; + } else if (qmlUnit->objectAt(binding->value.objectIndex)->flags & QV4::CompiledData::Object::IsComponent) { + return noError; + } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { + return noError; + } else if (QQmlValueTypeFactory::isValueType(property->propType())) { + return QQmlCompileError(binding->location, tr("Unexpected object assignment")); + } else if (property->propType() == qMetaTypeId<QQmlScriptString>()) { + return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: script expected")); + } else { + // We want to raw metaObject here as the raw metaobject is the + // actual property type before we applied any extensions that might + // effect the properties on the type, but don't effect assignability + QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType()); + + // Will be true if the assgned type inherits propertyMetaObject + bool isAssignable = false; + // Determine isAssignable value + if (propertyMetaObject) { + QQmlPropertyCache *c = propertyCaches.at(binding->value.objectIndex); + while (c && !isAssignable) { + isAssignable |= c == propertyMetaObject; + c = c->parent(); + } + } + + if (!isAssignable) { + return QQmlCompileError(binding->valueLocation, tr("Cannot assign object to property")); + } + } + return noError; +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qqmlpropertyvalidator_p.h b/src/qml/compiler/qqmlpropertyvalidator_p.h new file mode 100644 index 0000000000..d0bd314461 --- /dev/null +++ b/src/qml/compiler/qqmlpropertyvalidator_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications 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 QQMLPROPERTYVALIDATOR_P_H +#define QQMLPROPERTYVALIDATOR_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/qqmltypecompiler_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPropertyValidator +{ + Q_DECLARE_TR_FUNCTIONS(QQmlPropertyValidator) +public: + QQmlPropertyValidator(QQmlEnginePrivate *enginePrivate, const QQmlImports &imports, QV4::CompiledData::CompilationUnit *compilationUnit); + + QVector<QQmlCompileError> validate(); + +private: + QVector<QQmlCompileError> validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty = false) const; + QQmlCompileError validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const; + QQmlCompileError validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const; + + bool canCoerce(int to, QQmlPropertyCache *fromMo) const; + + QVector<QQmlCompileError> recordError(const QV4::CompiledData::Location &location, const QString &description) const Q_REQUIRED_RESULT; + QVector<QQmlCompileError> recordError(const QQmlCompileError &error) const Q_REQUIRED_RESULT; + QString stringAt(int index) const { return qmlUnit->stringAt(index); } + + QQmlEnginePrivate *enginePrivate; + const QQmlImports &imports; + const QV4::CompiledData::Unit *qmlUnit; + const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; + const QQmlPropertyCacheVector &propertyCaches; + + QVector<QV4::CompiledData::BindingPropertyData> * const bindingPropertyDataPerObject; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYVALIDATOR_P_H diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index caa4a55d3d..2308e66609 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -44,9 +44,10 @@ #include <private/qqmlcustomparser_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qqmlstringconverters_p.h> #include <private/qv4ssa_p.h> +#include "qqmlpropertycachecreator_p.h" + #define COMPILE_EXCEPTION(token, desc) \ { \ recordError((token)->location, desc); \ @@ -55,95 +56,35 @@ QT_BEGIN_NAMESPACE -QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlCompiledData *compiledData, QQmlTypeData *typeData, QmlIR::Document *parsedQML) - : engine(engine) - , compiledData(compiledData) +QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, + QmlIR::Document *parsedQML, const QQmlRefPointer<QQmlTypeNameCache> &importCache, + const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache) + : resolvedTypes(resolvedTypeCache) + , engine(engine) , typeData(typeData) + , importCache(importCache) , document(parsedQML) { } -bool QQmlTypeCompiler::compile() +QV4::CompiledData::CompilationUnit *QQmlTypeCompiler::compile() { - compiledData->importCache = new QQmlTypeNameCache; - - foreach (const QString &ns, typeData->namespaces()) - compiledData->importCache->add(ns); - - // Add any Composite Singletons that were used to the import cache - foreach (const QQmlTypeData::TypeReference &singleton, typeData->compositeSingletons()) - compiledData->importCache->add(singleton.type->qmlTypeName(), singleton.type->sourceUrl(), singleton.prefix); - - typeData->imports().populateCache(compiledData->importCache); - - const QHash<int, QQmlTypeData::TypeReference> &resolvedTypes = typeData->resolvedTypeRefs(); - for (QHash<int, QQmlTypeData::TypeReference>::ConstIterator resolvedType = resolvedTypes.constBegin(), end = resolvedTypes.constEnd(); - resolvedType != end; ++resolvedType) { - QScopedPointer<QQmlCompiledData::TypeReference> ref(new QQmlCompiledData::TypeReference); - QQmlType *qmlType = resolvedType->type; - if (resolvedType->typeData) { - if (resolvedType->needsCreation && qmlType->isCompositeSingleton()) { - QQmlError error; - QString reason = tr("Composite Singleton Type %1 is not creatable.").arg(qmlType->qmlTypeName()); - error.setDescription(reason); - error.setColumn(resolvedType->location.column); - error.setLine(resolvedType->location.line); - recordError(error); - return false; - } - ref->component = resolvedType->typeData->compiledData(); - ref->component->addref(); - } else if (qmlType) { - ref->type = qmlType; - Q_ASSERT(ref->type); - - if (resolvedType->needsCreation && !ref->type->isCreatable()) { - QQmlError error; - QString reason = ref->type->noCreationReason(); - if (reason.isEmpty()) - reason = tr("Element is not creatable."); - error.setDescription(reason); - error.setColumn(resolvedType->location.column); - error.setLine(resolvedType->location.line); - recordError(error); - return false; - } - - if (ref->type->containsRevisionedAttributes()) { - ref->typePropertyCache = engine->cache(ref->type, - resolvedType->minorVersion); - if (!ref->typePropertyCache) { - QQmlError cacheError; - cacheError.setColumn(resolvedType->location.column); - cacheError.setLine(resolvedType->location.line); - recordError(cacheError); - return false; - } - ref->typePropertyCache->addref(); - } - } - ref->majorVersion = resolvedType->majorVersion; - ref->minorVersion = resolvedType->minorVersion; - ref->doDynamicTypeCheck(); - compiledData->resolvedTypes.insert(resolvedType.key(), ref.take()); - } - // Build property caches and VME meta object data - for (QHash<int, QQmlCompiledData::TypeReference*>::ConstIterator it = compiledData->resolvedTypes.constBegin(), end = compiledData->resolvedTypes.constEnd(); + for (auto it = resolvedTypes.constBegin(), end = resolvedTypes.constEnd(); it != end; ++it) { QQmlCustomParser *customParser = (*it)->type ? (*it)->type->customParser() : 0; if (customParser) customParsers.insert(it.key(), customParser); } - compiledData->metaObjects.reserve(document->objects.count()); - compiledData->propertyCaches.reserve(document->objects.count()); - { - QQmlPropertyCacheCreator propertyCacheBuilder(this); - if (!propertyCacheBuilder.buildMetaObjects()) - return false; + QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, engine, this, imports()); + QQmlCompileError error = propertyCacheBuilder.buildMetaObjects(); + if (error.isSet()) { + recordError(error); + return nullptr; + } } { @@ -154,13 +95,13 @@ bool QQmlTypeCompiler::compile() { SignalHandlerConverter converter(this); if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) - return false; + return nullptr; } { QQmlEnumTypeResolver enumResolver(this); if (!enumResolver.resolveEnumBindings()) - return false; + return nullptr; } { @@ -173,34 +114,19 @@ bool QQmlTypeCompiler::compile() annotator.annotateBindingsToAliases(); } - // Collect imported scripts - const QList<QQmlTypeData::ScriptReference> &scripts = typeData->resolvedScripts(); - compiledData->scripts.reserve(scripts.count()); - for (int scriptIndex = 0; scriptIndex < scripts.count(); ++scriptIndex) { - const QQmlTypeData::ScriptReference &script = scripts.at(scriptIndex); - - QStringRef qualifier(&script.qualifier); - QString enclosingNamespace; - - const int lastDotIndex = qualifier.lastIndexOf(QLatin1Char('.')); - if (lastDotIndex != -1) { - enclosingNamespace = qualifier.left(lastDotIndex).toString(); - qualifier = qualifier.mid(lastDotIndex+1); - } - - compiledData->importCache->add(qualifier.toString(), scriptIndex, enclosingNamespace); - QQmlScriptData *scriptData = script.script->scriptData(); - scriptData->addref(); - compiledData->scripts << scriptData; - } - // Resolve component boundaries and aliases { // Scan for components, determine their scopes and resolve aliases within the scope. QQmlComponentAndAliasResolver resolver(this); if (!resolver.resolve()) - return false; + return nullptr; + } + + { + QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); + if (!deferredAndCustomParserBindingScanner.scanObject()) + return nullptr; } // Compile JS binding expressions and signal handlers @@ -212,10 +138,10 @@ bool QQmlTypeCompiler::compile() sss.scan(); } - QmlIR::JSCodeGen v4CodeGenerator(typeData->finalUrlString(), document->code, &document->jsModule, &document->jsParserEngine, document->program, compiledData->importCache, &document->jsGenerator.stringTable); + QmlIR::JSCodeGen v4CodeGenerator(typeData->finalUrlString(), document->code, &document->jsModule, &document->jsParserEngine, document->program, importCache, &document->jsGenerator.stringTable); QQmlJSCodeGenerator jsCodeGen(this, &v4CodeGenerator); if (!jsCodeGen.generateCodeForComponents()) - return false; + return nullptr; QQmlJavaScriptBindingExpressionSimplificationPass pass(this); pass.reduceTranslationBindings(); @@ -230,70 +156,45 @@ bool QQmlTypeCompiler::compile() // Generate QML compiled type data structures QmlIR::QmlUnitGenerator qmlGenerator; - QV4::CompiledData::Unit *qmlUnit = qmlGenerator.generate(*document); + QV4::CompiledData::Unit *qmlUnit = qmlGenerator.generate(*document, QQmlEnginePrivate::get(engine), resolvedTypes); Q_ASSERT(document->javaScriptCompilationUnit); // The js unit owns the data and will free the qml unit. document->javaScriptCompilationUnit->data = qmlUnit; - compiledData->compilationUnit = document->javaScriptCompilationUnit; - - // Add to type registry of composites - if (compiledData->isCompositeType()) - engine->registerInternalCompositeType(compiledData); - else { - const QV4::CompiledData::Object *obj = qmlUnit->objectAt(qmlUnit->indexOfRootObject); - QQmlCompiledData::TypeReference *typeRef = compiledData->resolvedTypes.value(obj->inheritedTypeNameIndex); - Q_ASSERT(typeRef); - if (typeRef->component) { - compiledData->metaTypeId = typeRef->component->metaTypeId; - compiledData->listMetaTypeId = typeRef->component->listMetaTypeId; - } else { - compiledData->metaTypeId = typeRef->type->typeId(); - compiledData->listMetaTypeId = typeRef->type->qListTypeId(); - } - } - - // Sanity check property bindings - QQmlPropertyValidator validator(this); - if (!validator.validate()) - return false; + QV4::CompiledData::CompilationUnit *compilationUnit = document->javaScriptCompilationUnit; + compilationUnit = document->javaScriptCompilationUnit; + compilationUnit->importCache = importCache; + compilationUnit->resolvedTypes = resolvedTypes; + compilationUnit->propertyCaches = std::move(m_propertyCaches); + Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->data->nObjects)); - // Collect some data for instantiation later. - int bindingCount = 0; - int parserStatusCount = 0; - int objectCount = 0; - for (quint32 i = 0; i < qmlUnit->nObjects; ++i) { - const QV4::CompiledData::Object *obj = qmlUnit->objectAt(i); - bindingCount += obj->nBindings; - if (QQmlCompiledData::TypeReference *typeRef = compiledData->resolvedTypes.value(obj->inheritedTypeNameIndex)) { - if (QQmlType *qmlType = typeRef->type) { - if (qmlType->parserStatusCast() != -1) - ++parserStatusCount; - } - ++objectCount; - if (typeRef->component) { - bindingCount += typeRef->component->totalBindingsCount; - parserStatusCount += typeRef->component->totalParserStatusCount; - objectCount += typeRef->component->totalObjectCount; - } - } - } - compiledData->totalBindingsCount = bindingCount; - compiledData->totalParserStatusCount = parserStatusCount; - compiledData->totalObjectCount = objectCount; + if (errors.isEmpty()) + return compilationUnit; + else + return nullptr; +} - Q_ASSERT(compiledData->propertyCaches.count() == static_cast<int>(compiledData->compilationUnit->data->nObjects)); +void QQmlTypeCompiler::recordError(QQmlError error) +{ + error.setUrl(url()); + errors << error; +} - return errors.isEmpty(); +void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) +{ + QQmlError error; + error.setLine(location.line); + error.setColumn(location.column); + error.setDescription(description); + error.setUrl(url()); + errors << error; } -void QQmlTypeCompiler::recordError(const QQmlError &error) +void QQmlTypeCompiler::recordError(const QQmlCompileError &error) { - QQmlError e = error; - e.setUrl(url()); - errors << e; + recordError(error.location, error.description); } QString QQmlTypeCompiler::stringAt(int idx) const @@ -313,7 +214,7 @@ QV4::IR::Module *QQmlTypeCompiler::jsIRModule() const const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const { - return compiledData->compilationUnit->data; + return document->javaScriptCompilationUnit->data; } const QQmlImports *QQmlTypeCompiler::imports() const @@ -321,12 +222,7 @@ const QQmlImports *QQmlTypeCompiler::imports() const return &typeData->imports(); } -QHash<int, QQmlCompiledData::TypeReference*> *QQmlTypeCompiler::resolvedTypes() -{ - return &compiledData->resolvedTypes; -} - -QList<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() +QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const { return &document->objects; } @@ -336,45 +232,20 @@ int QQmlTypeCompiler::rootObjectIndex() const return document->indexOfRootObject; } -void QQmlTypeCompiler::setPropertyCaches(const QVector<QQmlPropertyCache *> &caches) +void QQmlTypeCompiler::setPropertyCaches(QQmlPropertyCacheVector &&caches) { - compiledData->propertyCaches = caches; - Q_ASSERT(caches.count() >= document->indexOfRootObject); - if (compiledData->rootPropertyCache) - compiledData->rootPropertyCache->release(); - compiledData->rootPropertyCache = caches.at(document->indexOfRootObject); - compiledData->rootPropertyCache->addref(); + m_propertyCaches = std::move(caches); + Q_ASSERT(m_propertyCaches.count() >= document->indexOfRootObject); } -const QVector<QQmlPropertyCache *> &QQmlTypeCompiler::propertyCaches() const +const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const { - return compiledData->propertyCaches; + return &m_propertyCaches; } -void QQmlTypeCompiler::setVMEMetaObjects(const QVector<QByteArray> &metaObjects) +QQmlPropertyCacheVector &&QQmlTypeCompiler::takePropertyCaches() { - Q_ASSERT(compiledData->metaObjects.isEmpty()); - compiledData->metaObjects = metaObjects; -} - -QVector<QByteArray> *QQmlTypeCompiler::vmeMetaObjects() const -{ - return &compiledData->metaObjects; -} - -QHash<int, int> *QQmlTypeCompiler::objectIndexToIdForRoot() -{ - return &compiledData->objectIndexToIdForRoot; -} - -QHash<int, QHash<int, int> > *QQmlTypeCompiler::objectIndexToIdPerComponent() -{ - return &compiledData->objectIndexToIdPerComponent; -} - -QHash<int, QBitArray> *QQmlTypeCompiler::customParserBindings() -{ - return &compiledData->customParserBindings; + return std::move(m_propertyCaches); } QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() @@ -392,14 +263,9 @@ const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const return &document->jsGenerator.stringTable; } -void QQmlTypeCompiler::setDeferredBindingsPerObject(const QHash<int, QBitArray> &deferredBindingsPerObject) -{ - compiledData->deferredBindingsPerObject = deferredBindingsPerObject; -} - void QQmlTypeCompiler::setBindingPropertyDataPerObject(const QVector<QV4::CompiledData::BindingPropertyData> &propertyData) { - compiledData->compilationUnit->bindingPropertyDataPerObject = propertyData; + document->javaScriptCompilationUnit->bindingPropertyDataPerObject = propertyData; } QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const @@ -407,499 +273,34 @@ QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scrip return object->bindingAsString(document, scriptIndex); } -QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) - : compiler(typeCompiler) -{ -} - -void QQmlCompilePass::recordError(const QV4::CompiledData::Location &location, const QString &description) const -{ - QQmlError error; - error.setLine(location.line); - error.setColumn(location.column); - error.setDescription(description); - compiler->recordError(error); -} - -static QAtomicInt classIndexCounter(0); - -QQmlPropertyCacheCreator::QQmlPropertyCacheCreator(QQmlTypeCompiler *typeCompiler) - : QQmlCompilePass(typeCompiler) - , enginePrivate(typeCompiler->enginePrivate()) - , qmlObjects(*typeCompiler->qmlObjects()) - , imports(typeCompiler->imports()) - , resolvedTypes(typeCompiler->resolvedTypes()) -{ -} - -QQmlPropertyCacheCreator::~QQmlPropertyCacheCreator() -{ - for (int i = 0; i < propertyCaches.count(); ++i) - if (QQmlPropertyCache *cache = propertyCaches.at(i)) - cache->release(); - propertyCaches.clear(); -} - -bool QQmlPropertyCacheCreator::buildMetaObjects() -{ - propertyCaches.resize(qmlObjects.count()); - vmeMetaObjects.resize(qmlObjects.count()); - - if (!buildMetaObjectRecursively(compiler->rootObjectIndex(), /*referencing object*/-1, /*instantiating binding*/0)) - return false; - - compiler->setVMEMetaObjects(vmeMetaObjects); - compiler->setPropertyCaches(propertyCaches); - propertyCaches.clear(); - - return true; -} - -bool QQmlPropertyCacheCreator::buildMetaObjectRecursively(int objectIndex, int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding) +void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion) { - const QmlIR::Object *obj = qmlObjects.at(objectIndex); - - QQmlPropertyCache *baseTypeCache = 0; - QQmlPropertyData *instantiatingProperty = 0; - if (instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty) { - Q_ASSERT(referencingObjectIndex >= 0); - QQmlPropertyCache *parentCache = propertyCaches.at(referencingObjectIndex); - Q_ASSERT(parentCache); - Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); - - bool notInRevision = false; - instantiatingProperty = QmlIR::PropertyResolver(parentCache).property(stringAt(instantiatingBinding->propertyNameIndex), ¬InRevision); - if (instantiatingProperty) { - if (instantiatingProperty->isQObject()) { - baseTypeCache = enginePrivate->rawPropertyCacheForType(instantiatingProperty->propType); - Q_ASSERT(baseTypeCache); - } else if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(instantiatingProperty->propType)) { - baseTypeCache = enginePrivate->cache(vtmo); - Q_ASSERT(baseTypeCache); - } - } - } - - bool needVMEMetaObject = obj->propertyCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0; - if (!needVMEMetaObject) { - for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { - - // On assignments are implemented using value interceptors, which require a VME meta object. - needVMEMetaObject = true; - - // If the on assignment is inside a group property, we need to distinguish between QObject based - // group properties and value type group properties. For the former the base type is derived from - // the property that references us, for the latter we only need a meta-object on the referencing object - // because interceptors can't go to the shared value type instances. - if (instantiatingProperty && QQmlValueTypeFactory::isValueType(instantiatingProperty->propType)) { - needVMEMetaObject = false; - if (!ensureMetaObject(referencingObjectIndex)) - return false; - } - break; - } - } - } - - if (obj->inheritedTypeNameIndex != 0) { - QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(obj->inheritedTypeNameIndex); - Q_ASSERT(typeRef); - - if (typeRef->isFullyDynamicType) { - if (obj->propertyCount() > 0) { - recordError(obj->location, tr("Fully dynamic types cannot declare new properties.")); - return false; - } - if (obj->signalCount() > 0) { - recordError(obj->location, tr("Fully dynamic types cannot declare new signals.")); - return false; - } - if (obj->functionCount() > 0) { - recordError(obj->location, tr("Fully Dynamic types cannot declare new functions.")); - return false; - } - } - - baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); - Q_ASSERT(baseTypeCache); - } else if (instantiatingBinding && instantiatingBinding->isAttachedProperty()) { - QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(instantiatingBinding->propertyNameIndex); - Q_ASSERT(typeRef); - QQmlType *qmltype = typeRef->type; - if (!qmltype) { - QString propertyName = stringAt(instantiatingBinding->propertyNameIndex); - if (imports->resolveType(propertyName, &qmltype, 0, 0, 0)) { - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); - Q_ASSERT(tdata); - Q_ASSERT(tdata->isComplete()); - - QQmlCompiledData *data = tdata->compiledData(); - qmltype = QQmlMetaType::qmlType(data->metaTypeId); - - tdata->release(); - } - } - } + const quint32 moduleIdx = registerString(module); + const quint32 qualifierIdx = registerString(qualifier); - const QMetaObject *attachedMo = qmltype ? qmltype->attachedPropertiesType(enginePrivate) : 0; - if (!attachedMo) { - recordError(instantiatingBinding->location, tr("Non-existent attached object")); - return false; - } - baseTypeCache = enginePrivate->cache(attachedMo); - Q_ASSERT(baseTypeCache); - } - - if (baseTypeCache) { - if (needVMEMetaObject) { - if (!createMetaObject(objectIndex, obj, baseTypeCache)) - return false; - } else { - propertyCaches[objectIndex] = baseTypeCache; - baseTypeCache->addref(); - } - } - - if (propertyCaches.at(objectIndex)) { - for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) - if (binding->type >= QV4::CompiledData::Binding::Type_Object) { - if (!buildMetaObjectRecursively(binding->value.objectIndex, objectIndex, binding)) - return false; - } + for (int i = 0, count = document->imports.count(); i < count; ++i) { + const QV4::CompiledData::Import *existingImport = document->imports.at(i); + if (existingImport->type == QV4::CompiledData::Import::ImportLibrary + && existingImport->uriIndex == moduleIdx + && existingImport->qualifierIndex == qualifierIdx) + return; } - - return true; + auto pool = memoryPool(); + QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>(); + import->type = QV4::CompiledData::Import::ImportLibrary; + import->majorVersion = majorVersion; + import->minorVersion = minorVersion; + import->uriIndex = moduleIdx; + import->qualifierIndex = qualifierIdx; + document->imports.append(import); } -bool QQmlPropertyCacheCreator::ensureMetaObject(int objectIndex) +QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) + : compiler(typeCompiler) { - if (!vmeMetaObjects.at(objectIndex).isEmpty()) - return true; - const QmlIR::Object *obj = qmlObjects.at(objectIndex); - QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(obj->inheritedTypeNameIndex); - Q_ASSERT(typeRef); - QQmlPropertyCache *baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); - return createMetaObject(objectIndex, obj, baseTypeCache); -} - -bool QQmlPropertyCacheCreator::createMetaObject(int objectIndex, const QmlIR::Object *obj, QQmlPropertyCache *baseTypeCache) -{ - QQmlPropertyCache *cache = baseTypeCache->copyAndReserve(obj->propertyCount(), - obj->functionCount() + obj->propertyCount() + obj->signalCount(), - obj->signalCount() + obj->propertyCount()); - propertyCaches[objectIndex] = cache; - - struct TypeData { - QV4::CompiledData::Property::Type dtype; - int metaType; - } builtinTypes[] = { - { QV4::CompiledData::Property::Var, QMetaType::QVariant }, - { QV4::CompiledData::Property::Variant, QMetaType::QVariant }, - { QV4::CompiledData::Property::Int, QMetaType::Int }, - { QV4::CompiledData::Property::Bool, QMetaType::Bool }, - { QV4::CompiledData::Property::Real, QMetaType::Double }, - { QV4::CompiledData::Property::String, QMetaType::QString }, - { QV4::CompiledData::Property::Url, QMetaType::QUrl }, - { QV4::CompiledData::Property::Color, QMetaType::QColor }, - { QV4::CompiledData::Property::Font, QMetaType::QFont }, - { QV4::CompiledData::Property::Time, QMetaType::QTime }, - { QV4::CompiledData::Property::Date, QMetaType::QDate }, - { QV4::CompiledData::Property::DateTime, QMetaType::QDateTime }, - { QV4::CompiledData::Property::Rect, QMetaType::QRectF }, - { QV4::CompiledData::Property::Point, QMetaType::QPointF }, - { QV4::CompiledData::Property::Size, QMetaType::QSizeF }, - { QV4::CompiledData::Property::Vector2D, QMetaType::QVector2D }, - { QV4::CompiledData::Property::Vector3D, QMetaType::QVector3D }, - { QV4::CompiledData::Property::Vector4D, QMetaType::QVector4D }, - { QV4::CompiledData::Property::Matrix4x4, QMetaType::QMatrix4x4 }, - { QV4::CompiledData::Property::Quaternion, QMetaType::QQuaternion } - }; - static const uint builtinTypeCount = sizeof(builtinTypes) / sizeof(TypeData); - - QByteArray newClassName; - - if (objectIndex == compiler->rootObjectIndex()) { - QString path = compiler->url().path(); - int lastSlash = path.lastIndexOf(QLatin1Char('/')); - if (lastSlash > -1) { - const QStringRef nameBase = path.midRef(lastSlash + 1, path.length() - lastSlash - 5); - if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) - newClassName = nameBase.toUtf8() + "_QMLTYPE_" + - QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1)); - } - } - if (newClassName.isEmpty()) { - newClassName = QQmlMetaObject(baseTypeCache).className(); - newClassName.append("_QML_"); - newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); - } - - cache->_dynamicClassName = newClassName; - - int aliasCount = 0; - int varPropCount = 0; - - QmlIR::PropertyResolver resolver(baseTypeCache); - - for (const QmlIR::Property *p = obj->firstProperty(); p; p = p->next) { - if (p->type == QV4::CompiledData::Property::Alias) - aliasCount++; - else if (p->type == QV4::CompiledData::Property::Var) - varPropCount++; - - // No point doing this for both the alias and non alias cases - bool notInRevision = false; - QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); - if (d && d->isFinal()) - COMPILE_EXCEPTION(p, tr("Cannot override FINAL property")); - } - - typedef QQmlVMEMetaData VMD; - - QByteArray &dynamicData = vmeMetaObjects[objectIndex] = QByteArray(sizeof(QQmlVMEMetaData) - + obj->propertyCount() * sizeof(VMD::PropertyData) - + obj->functionCount() * sizeof(VMD::MethodData) - + aliasCount * sizeof(VMD::AliasData), 0); - - int effectivePropertyIndex = cache->propertyIndexCacheStart; - int effectiveMethodIndex = cache->methodIndexCacheStart; - - // For property change signal override detection. - // We prepopulate a set of signal names which already exist in the object, - // and throw an error if there is a signal/method defined as an override. - QSet<QString> seenSignals; - seenSignals << QStringLiteral("destroyed") << QStringLiteral("parentChanged") << QStringLiteral("objectNameChanged"); - QQmlPropertyCache *parentCache = cache; - while ((parentCache = parentCache->parent())) { - if (int pSigCount = parentCache->signalCount()) { - int pSigOffset = parentCache->signalOffset(); - for (int i = pSigOffset; i < pSigCount; ++i) { - QQmlPropertyData *currPSig = parentCache->signal(i); - // XXX TODO: find a better way to get signal name from the property data :-/ - for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); - iter != parentCache->stringCache.end(); ++iter) { - if (currPSig == (*iter).second) { - seenSignals.insert(iter.key()); - break; - } - } - } - } - } - - // First set up notify signals for properties - first normal, then var, then alias - enum { NSS_Normal = 0, NSS_Alias = 1 }; - for (int ii = NSS_Normal; ii <= NSS_Alias; ++ii) { // 0 == normal, 1 == var, 2 == alias - - if (ii == NSS_Alias && aliasCount == 0) continue; - - for (const QmlIR::Property *p = obj->firstProperty(); p; p = p->next) { - if ((ii == NSS_Normal && p->type == QV4::CompiledData::Property::Alias) || - (ii == NSS_Alias && p->type != QV4::CompiledData::Property::Alias)) - continue; - - quint32 flags = QQmlPropertyData::IsSignal | QQmlPropertyData::IsFunction | - QQmlPropertyData::IsVMESignal; - - QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); - seenSignals.insert(changedSigName); - - cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); - } - } - - // Dynamic signals - for (const QmlIR::Signal *s = obj->firstSignal(); s; s = s->next) { - const int paramCount = s->parameters->count; - - QList<QByteArray> names; - names.reserve(paramCount); - QVarLengthArray<int, 10> paramTypes(paramCount?(paramCount + 1):0); - - if (paramCount) { - paramTypes[0] = paramCount; - - QmlIR::SignalParameter *param = s->parameters->first; - for (int i = 0; i < paramCount; ++i, param = param->next) { - names.append(stringAt(param->nameIndex).toUtf8()); - if (param->type < builtinTypeCount) { - // built-in type - paramTypes[i + 1] = builtinTypes[param->type].metaType; - } else { - // lazily resolved type - Q_ASSERT(param->type == QV4::CompiledData::Property::Custom); - const QString customTypeName = stringAt(param->customTypeNameIndex); - QQmlType *qmltype = 0; - if (!imports->resolveType(customTypeName, &qmltype, 0, 0, 0)) - COMPILE_EXCEPTION(s, tr("Invalid signal parameter type: %1").arg(customTypeName)); - - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); - Q_ASSERT(tdata); - Q_ASSERT(tdata->isComplete()); - - QQmlCompiledData *data = tdata->compiledData(); - - paramTypes[i + 1] = data->metaTypeId; - - tdata->release(); - } else { - paramTypes[i + 1] = qmltype->typeId(); - } - } - } - } - - ((QQmlVMEMetaData *)dynamicData.data())->signalCount++; - - quint32 flags = QQmlPropertyData::IsSignal | QQmlPropertyData::IsFunction | - QQmlPropertyData::IsVMESignal; - if (paramCount) - flags |= QQmlPropertyData::HasArguments; - - QString signalName = stringAt(s->nameIndex); - if (seenSignals.contains(signalName)) - COMPILE_EXCEPTION(s, tr("Duplicate signal name: invalid override of property change signal or superclass signal")); - seenSignals.insert(signalName); - - cache->appendSignal(signalName, flags, effectiveMethodIndex++, - paramCount?paramTypes.constData():0, names); - } - - - // Dynamic slots - for (const QmlIR::Function *s = obj->firstFunction(); s; s = s->next) { - QQmlJS::AST::FunctionDeclaration *astFunction = s->functionDeclaration; - - quint32 flags = QQmlPropertyData::IsFunction | QQmlPropertyData::IsVMEFunction; - - if (astFunction->formals) - flags |= QQmlPropertyData::HasArguments; - - QString slotName = astFunction->name.toString(); - if (seenSignals.contains(slotName)) - COMPILE_EXCEPTION(s, tr("Duplicate method name: invalid override of property change signal or superclass signal")); - // Note: we don't append slotName to the seenSignals list, since we don't - // protect against overriding change signals or methods with properties. - - QList<QByteArray> parameterNames; - QQmlJS::AST::FormalParameterList *param = astFunction->formals; - while (param) { - parameterNames << param->name.toUtf8(); - param = param->next; - } - - cache->appendMethod(slotName, flags, effectiveMethodIndex++, parameterNames); - } - - - // Dynamic properties (except var and aliases) - int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; - int propertyIdx = 0; - for (const QmlIR::Property *p = obj->firstProperty(); p; p = p->next, ++propertyIdx) { - - if (p->type == QV4::CompiledData::Property::Alias) - continue; - - int propertyType = 0; - int vmePropertyType = 0; - quint32 propertyFlags = 0; - - if (p->type == QV4::CompiledData::Property::Var) { - propertyType = QMetaType::QVariant; - vmePropertyType = QQmlVMEMetaData::VarPropertyType; - propertyFlags = QQmlPropertyData::IsVarProperty; - } else if (p->type < builtinTypeCount) { - propertyType = builtinTypes[p->type].metaType; - vmePropertyType = propertyType; - - if (p->type == QV4::CompiledData::Property::Variant) - propertyFlags |= QQmlPropertyData::IsQVariant; - } else { - Q_ASSERT(p->type == QV4::CompiledData::Property::CustomList || - p->type == QV4::CompiledData::Property::Custom); - - QQmlType *qmltype = 0; - if (!imports->resolveType(stringAt(p->customTypeNameIndex), &qmltype, 0, 0, 0)) { - COMPILE_EXCEPTION(p, tr("Invalid property type")); - } - - Q_ASSERT(qmltype); - if (qmltype->isComposite()) { - QQmlTypeData *tdata = enginePrivate->typeLoader.getType(qmltype->sourceUrl()); - Q_ASSERT(tdata); - Q_ASSERT(tdata->isComplete()); - - QQmlCompiledData *data = tdata->compiledData(); - - if (p->type == QV4::CompiledData::Property::Custom) { - propertyType = data->metaTypeId; - vmePropertyType = QMetaType::QObjectStar; - } else { - propertyType = data->listMetaTypeId; - vmePropertyType = qMetaTypeId<QQmlListProperty<QObject> >(); - } - - tdata->release(); - } else { - if (p->type == QV4::CompiledData::Property::Custom) { - propertyType = qmltype->typeId(); - vmePropertyType = QMetaType::QObjectStar; - } else { - propertyType = qmltype->qListTypeId(); - vmePropertyType = qMetaTypeId<QQmlListProperty<QObject> >(); - } - } - - if (p->type == QV4::CompiledData::Property::Custom) - propertyFlags |= QQmlPropertyData::IsQObjectDerived; - else - propertyFlags |= QQmlPropertyData::IsQList; - } - - if (!(p->flags & QV4::CompiledData::Property::IsReadOnly) && p->type != QV4::CompiledData::Property::CustomList) - propertyFlags |= QQmlPropertyData::IsWritable; - - - QString propertyName = stringAt(p->nameIndex); - if (propertyIdx == obj->indexOfDefaultProperty) cache->_defaultPropertyName = propertyName; - cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - propertyType, effectiveSignalIndex); - - effectiveSignalIndex++; - - VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); - (vmd->propertyData() + vmd->propertyCount)->propertyType = vmePropertyType; - vmd->propertyCount++; - } - - // Alias property count. Actual data is setup in buildDynamicMetaAliases - ((QQmlVMEMetaData *)dynamicData.data())->aliasCount = aliasCount; - - // Dynamic slot data - comes after the property data - for (const QmlIR::Function *s = obj->firstFunction(); s; s = s->next) { - QQmlJS::AST::FunctionDeclaration *astFunction = s->functionDeclaration; - int formalsCount = 0; - QQmlJS::AST::FormalParameterList *param = astFunction->formals; - while (param) { - formalsCount++; - param = param->next; - } - - VMD::MethodData methodData = { /* runtimeFunctionIndex*/ 0, // ### - formalsCount, - /* s->location.start.line */0 }; // ### +} - VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); - VMD::MethodData &md = *(vmd->methodData() + vmd->methodCount); - vmd->methodCount++; - md = methodData; - } - return true; -} SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler) : QQmlCompilePass(typeCompiler) @@ -907,7 +308,7 @@ SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler) , qmlObjects(*typeCompiler->qmlObjects()) , imports(typeCompiler->imports()) , customParsers(typeCompiler->customParserCache()) - , resolvedTypes(*typeCompiler->resolvedTypes()) + , resolvedTypes(typeCompiler->resolvedTypes) , illegalNames(QV8Engine::get(QQmlEnginePrivate::get(typeCompiler->enginePrivate()))->illegalNames()) , propertyCaches(typeCompiler->propertyCaches()) { @@ -917,7 +318,7 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio { for (int objectIndex = 0; objectIndex < qmlObjects.count(); ++objectIndex) { const QmlIR::Object * const obj = qmlObjects.at(objectIndex); - QQmlPropertyCache *cache = propertyCaches.at(objectIndex); + QQmlPropertyCache *cache = propertyCaches->at(objectIndex); if (!cache) continue; if (QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex)) { @@ -941,7 +342,7 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio // Attached property? if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex); - QQmlCompiledData::TypeReference *typeRef = resolvedTypes.value(binding->propertyNameIndex); + auto *typeRef = resolvedTypes.value(binding->propertyNameIndex); QQmlType *type = typeRef ? typeRef->type : 0; if (!type) { if (imports->resolveType(propertyName, &type, 0, 0, 0)) { @@ -950,8 +351,8 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio Q_ASSERT(tdata); Q_ASSERT(tdata->isComplete()); - QQmlCompiledData *data = tdata->compiledData(); - type = QQmlMetaType::qmlType(data->metaTypeId); + auto compilationUnit = tdata->compilationUnit(); + type = QQmlMetaType::qmlType(compilationUnit->metaTypeId); tdata->release(); } @@ -989,7 +390,7 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio bool notInRevision = false; QQmlPropertyData *signal = resolver.signal(propertyName, ¬InRevision); if (signal) { - int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex); + int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex()); sigIndex = propertyCache->originalClone(sigIndex); bool unnamedParameter = false; @@ -1014,7 +415,7 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio const QString &originalPropertyName = stringAt(binding->propertyNameIndex); - QQmlCompiledData::TypeReference *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); + auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); const QQmlType *type = typeRef ? typeRef->type : 0; if (type) { COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(originalPropertyName).arg(type->module()).arg(type->majorVersion()).arg(type->minorVersion())); @@ -1118,14 +519,14 @@ QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) , qmlObjects(*typeCompiler->qmlObjects()) , propertyCaches(typeCompiler->propertyCaches()) , imports(typeCompiler->imports()) - , resolvedTypes(typeCompiler->resolvedTypes()) + , resolvedTypes(&typeCompiler->resolvedTypes) { } bool QQmlEnumTypeResolver::resolveEnumBindings() { for (int i = 0; i < qmlObjects.count(); ++i) { - QQmlPropertyCache *propertyCache = propertyCaches.at(i); + QQmlPropertyCache *propertyCache = propertyCaches->at(i); if (!propertyCache) continue; const QmlIR::Object *obj = qmlObjects.at(i); @@ -1146,7 +547,7 @@ bool QQmlEnumTypeResolver::resolveEnumBindings() if (!pd) continue; - if (!pd->isEnum() && pd->propType != QMetaType::Int) + if (!pd->isEnum() && pd->propType() != QMetaType::Int) continue; if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding)) @@ -1169,14 +570,14 @@ bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, const QS COMPILE_EXCEPTION(binding, tr("Invalid property assignment: Enum value \"%1\" cannot start with a lowercase letter").arg(enumName.toString())); } binding->type = QV4::CompiledData::Binding::Type_Number; - binding->value.d = (double)enumValue; + binding->setNumberValueInternal((double)enumValue); binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; return true; } bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *prop, QmlIR::Binding *binding) { - bool isIntProp = (prop->propType == QMetaType::Int) && !prop->isEnum(); + bool isIntProp = (prop->propType() == QMetaType::Int) && !prop->isEnum(); if (!prop->isEnum() && !isIntProp) return true; @@ -1218,9 +619,9 @@ bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, int value = 0; bool ok = false; - QQmlCompiledData::TypeReference *tr = resolvedTypes->value(obj->inheritedTypeNameIndex); + auto *tr = resolvedTypes->value(obj->inheritedTypeNameIndex); if (type && tr && tr->type == type) { - QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex); + QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex()); // When these two match, we can short cut the search if (mprop.isFlagType()) { @@ -1311,20 +712,20 @@ QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) void QQmlAliasAnnotator::annotateBindingsToAliases() { for (int i = 0; i < qmlObjects.count(); ++i) { - QQmlPropertyCache *propertyCache = propertyCaches.at(i); + QQmlPropertyCache *propertyCache = propertyCaches->at(i); if (!propertyCache) continue; const QmlIR::Object *obj = qmlObjects.at(i); QmlIR::PropertyResolver resolver(propertyCache); - QQmlPropertyData *defaultProperty = obj->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { if (!binding->isValueBinding()) continue; bool notInRevision = false; - QQmlPropertyData *pd = binding->propertyNameIndex != 0 ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; if (pd && pd->isAlias()) binding->flags |= QV4::CompiledData::Binding::IsBindingToAlias; } @@ -1343,21 +744,21 @@ void QQmlScriptStringScanner::scan() { const int scriptStringMetaType = qMetaTypeId<QQmlScriptString>(); for (int i = 0; i < qmlObjects.count(); ++i) { - QQmlPropertyCache *propertyCache = propertyCaches.at(i); + QQmlPropertyCache *propertyCache = propertyCaches->at(i); if (!propertyCache) continue; const QmlIR::Object *obj = qmlObjects.at(i); QmlIR::PropertyResolver resolver(propertyCache); - QQmlPropertyData *defaultProperty = obj->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { if (binding->type != QV4::CompiledData::Binding::Type_Script) continue; bool notInRevision = false; - QQmlPropertyData *pd = binding->propertyNameIndex != 0 ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; - if (!pd || pd->propType != scriptStringMetaType) + QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), ¬InRevision) : defaultProperty; + if (!pd || pd->propType() != scriptStringMetaType) continue; QmlIR::CompiledFunctionOrExpression *foe = obj->functionsAndExpressions->slowAt(binding->value.compiledScriptIndex); @@ -1376,13 +777,8 @@ QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *t , pool(typeCompiler->memoryPool()) , qmlObjects(typeCompiler->qmlObjects()) , indexOfRootObject(typeCompiler->rootObjectIndex()) - , _componentIndex(-1) - , _objectIndexToIdInScope(0) - , resolvedTypes(typeCompiler->resolvedTypes()) - , propertyCaches(typeCompiler->propertyCaches()) - , vmeMetaObjectData(typeCompiler->vmeMetaObjects()) - , objectIndexToIdForRoot(typeCompiler->objectIndexToIdForRoot()) - , objectIndexToIdPerComponent(typeCompiler->objectIndexToIdPerComponent()) + , resolvedTypes(&typeCompiler->resolvedTypes) + , propertyCaches(std::move(typeCompiler->takePropertyCaches())) { } @@ -1390,7 +786,7 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI { QmlIR::PropertyResolver propertyResolver(propertyCache); - QQmlPropertyData *defaultProperty = obj->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); + QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { if (binding->type != QV4::CompiledData::Binding::Type_Object) @@ -1399,18 +795,18 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI continue; const QmlIR::Object *targetObject = qmlObjects->at(binding->value.objectIndex); - QQmlCompiledData::TypeReference *tr = resolvedTypes->value(targetObject->inheritedTypeNameIndex); + auto *tr = resolvedTypes->value(targetObject->inheritedTypeNameIndex); Q_ASSERT(tr); if (QQmlType *targetType = tr->type) { if (targetType->metaObject() == &QQmlComponent::staticMetaObject) continue; - } else if (tr->component) { - if (tr->component->rootPropertyCache->firstCppMetaObject() == &QQmlComponent::staticMetaObject) + } else if (tr->compilationUnit) { + if (tr->compilationUnit->rootPropertyCache()->firstCppMetaObject() == &QQmlComponent::staticMetaObject) continue; } QQmlPropertyData *pd = 0; - if (binding->propertyNameIndex != 0) { + if (binding->propertyNameIndex != quint32(0)) { bool notInRevision = false; pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); } else { @@ -1419,7 +815,7 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI if (!pd || !pd->isQObject()) continue; - QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType); + QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType()); const QMetaObject *mo = pc ? pc->firstCppMetaObject() : 0; while (mo) { if (mo == &QQmlComponent::staticMetaObject) @@ -1430,15 +826,20 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI if (!mo) continue; + // emulate "import Qml 2.0 as QmlInternals" and then wrap the component in "QmlInternals.Component {}" QQmlType *componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject); Q_ASSERT(componentType); + const QString qualifier = QStringLiteral("QmlInternals"); + + compiler->addImport(componentType->module(), qualifier, componentType->majorVersion(), componentType->minorVersion()); QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); - syntheticComponent->init(pool, compiler->registerString(QString::fromUtf8(componentType->typeName())), compiler->registerString(QString())); + syntheticComponent->init(pool, compiler->registerString(qualifier + QLatin1Char('.') + componentType->elementName()), compiler->registerString(QString())); syntheticComponent->location = binding->valueLocation; + syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; if (!resolvedTypes->contains(syntheticComponent->inheritedTypeNameIndex)) { - QQmlCompiledData::TypeReference *typeRef = new QQmlCompiledData::TypeReference; + auto typeRef = new QV4::CompiledData::ResolvedTypeReference; typeRef->type = componentType; typeRef->majorVersion = componentType->majorVersion(); typeRef->minorVersion = componentType->minorVersion(); @@ -1449,7 +850,6 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI const int componentIndex = qmlObjects->count() - 1; // Keep property caches symmetric QQmlPropertyCache *componentCache = enginePrivate->cache(&QQmlComponent::staticMetaObject); - componentCache->addref(); propertyCaches.append(componentCache); QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); @@ -1462,7 +862,6 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI binding->value.objectIndex = componentIndex; componentRoots.append(componentIndex); - componentBoundaries.append(syntheticBinding->value.objectIndex); } } @@ -1474,7 +873,7 @@ bool QQmlComponentAndAliasResolver::resolve() // on the left hand side is of QQmlComponent type. const int objCountWithoutSynthesizedComponents = qmlObjects->count(); for (int i = 0; i < objCountWithoutSynthesizedComponents; ++i) { - const QmlIR::Object *obj = qmlObjects->at(i); + QmlIR::Object *obj = qmlObjects->at(i); QQmlPropertyCache *cache = propertyCaches.at(i); if (obj->inheritedTypeNameIndex == 0 && !cache) continue; @@ -1482,7 +881,7 @@ bool QQmlComponentAndAliasResolver::resolve() bool isExplicitComponent = false; if (obj->inheritedTypeNameIndex) { - QQmlCompiledData::TypeReference *tref = resolvedTypes->value(obj->inheritedTypeNameIndex); + auto *tref = resolvedTypes->value(obj->inheritedTypeNameIndex); Q_ASSERT(tref); if (tref->type && tref->type->metaObject() == &QQmlComponent::staticMetaObject) isExplicitComponent = true; @@ -1493,11 +892,11 @@ bool QQmlComponentAndAliasResolver::resolve() continue; } - componentRoots.append(i); + obj->flags |= QV4::CompiledData::Object::IsComponent; if (obj->functionCount() > 0) COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new functions.")); - if (obj->propertyCount() > 0) + if (obj->propertyCount() > 0 || obj->aliasCount() > 0) COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new properties.")); if (obj->signalCount() > 0) COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new signals.")); @@ -1515,65 +914,69 @@ bool QQmlComponentAndAliasResolver::resolve() if (rootBinding->next || rootBinding->type != QV4::CompiledData::Binding::Type_Object) COMPILE_EXCEPTION(obj, tr("Invalid component body specification")); - componentBoundaries.append(rootBinding->value.objectIndex); - } + // We are going to collect ids/aliases and resolve them for the root object as a separate + // last pass. + if (i != indexOfRootObject) + componentRoots.append(i); - std::sort(componentBoundaries.begin(), componentBoundaries.end()); + } for (int i = 0; i < componentRoots.count(); ++i) { - const QmlIR::Object *component = qmlObjects->at(componentRoots.at(i)); + QmlIR::Object *component = qmlObjects->at(componentRoots.at(i)); const QmlIR::Binding *rootBinding = component->firstBinding(); - _componentIndex = i; _idToObjectIndex.clear(); - _objectIndexToIdInScope = &(*objectIndexToIdPerComponent)[componentRoots.at(i)]; - _objectsWithAliases.clear(); if (!collectIdsAndAliases(rootBinding->value.objectIndex)) return false; - if (!resolveAliases()) + component->namedObjectsInComponent.allocate(pool, _idToObjectIndex); + + if (!resolveAliases(componentRoots.at(i))) return false; } // Collect ids and aliases for root - _componentIndex = -1; _idToObjectIndex.clear(); - _objectIndexToIdInScope = objectIndexToIdForRoot; _objectsWithAliases.clear(); collectIdsAndAliases(indexOfRootObject); - resolveAliases(); + QmlIR::Object *rootComponent = qmlObjects->at(indexOfRootObject); + rootComponent->namedObjectsInComponent.allocate(pool, _idToObjectIndex); + + if (!resolveAliases(indexOfRootObject)) + return false; // Implicit component insertion may have added objects and thus we also need // to extend the symmetric propertyCaches. - compiler->setPropertyCaches(propertyCaches); + compiler->setPropertyCaches(std::move(propertyCaches)); + compiler->setComponentRoots(componentRoots); return true; } bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) { - const QmlIR::Object *obj = qmlObjects->at(objectIndex); + QmlIR::Object *obj = qmlObjects->at(objectIndex); - if (obj->idIndex != 0) { - if (_idToObjectIndex.contains(obj->idIndex)) { + if (obj->idNameIndex != 0) { + if (_idToObjectIndex.contains(obj->idNameIndex)) { recordError(obj->locationOfIdProperty, tr("id is not unique")); return false; } - _idToObjectIndex.insert(obj->idIndex, objectIndex); - _objectIndexToIdInScope->insert(objectIndex, _objectIndexToIdInScope->count()); + obj->id = _idToObjectIndex.count(); + _idToObjectIndex.insert(obj->idNameIndex, objectIndex); } - for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) { - if (property->type == QV4::CompiledData::Property::Alias) { - _objectsWithAliases.append(objectIndex); - break; - } - } + if (obj->aliasCount() > 0) + _objectsWithAliases.append(objectIndex); + + // Stop at Component boundary + if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != compiler->rootObjectIndex()) + return true; for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { if (binding->type != QV4::CompiledData::Binding::Type_Object @@ -1581,10 +984,6 @@ bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) continue; - // Stop at Component boundary - if (std::binary_search(componentBoundaries.constBegin(), componentBoundaries.constEnd(), binding->value.objectIndex)) - continue; - if (!collectIdsAndAliases(binding->value.objectIndex)) return false; } @@ -1592,257 +991,205 @@ bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) return true; } -bool QQmlComponentAndAliasResolver::resolveAliases() +bool QQmlComponentAndAliasResolver::resolveAliases(int componentIndex) { - foreach (int objectIndex, _objectsWithAliases) { - const QmlIR::Object *obj = qmlObjects->at(objectIndex); + if (_objectsWithAliases.isEmpty()) + return true; - QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); - Q_ASSERT(propertyCache); + QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> aliasCacheCreator(&propertyCaches, compiler); - int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); - int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); - int effectiveAliasIndex = 0; + bool atLeastOneAliasResolved; + do { + atLeastOneAliasResolved = false; + QVector<int> pendingObjects; - const QmlIR::Property *p = obj->firstProperty(); - for (int propertyIndex = 0; propertyIndex < obj->propertyCount(); ++propertyIndex, p = p->next) { - if (p->type != QV4::CompiledData::Property::Alias) - continue; + for (int objectIndex: qAsConst(_objectsWithAliases)) { - const int idIndex = p->aliasIdValueIndex; - const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); - if (targetObjectIndex == -1) { - recordError(p->aliasLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + QQmlCompileError error; + const auto result = resolveAliasesInObject(objectIndex, &error); + + if (error.isSet()) { + recordError(error); return false; } - const int targetId = _objectIndexToIdInScope->value(targetObjectIndex, -1); - Q_ASSERT(targetId != -1); - - const QString aliasPropertyValue = stringAt(p->aliasPropertyValueIndex); - - QStringRef property; - QStringRef subProperty; - - const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); - if (propertySeparator != -1) { - property = aliasPropertyValue.leftRef(propertySeparator); - subProperty = aliasPropertyValue.midRef(propertySeparator + 1); - } else - property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); - - int propIdx = -1; - int propType = 0; - int notifySignal = -1; - int flags = 0; - int type = 0; - bool writable = false; - bool resettable = false; - - quint32 propertyFlags = QQmlPropertyData::IsAlias; - - if (property.isEmpty()) { - const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); - QQmlCompiledData::TypeReference *typeRef = resolvedTypes->value(targetObject->inheritedTypeNameIndex); - Q_ASSERT(typeRef); - - if (typeRef->type) - type = typeRef->type->typeId(); - else - type = typeRef->component->metaTypeId; - - flags |= QML_ALIAS_FLAG_PTR; - propertyFlags |= QQmlPropertyData::IsQObjectDerived; + + if (result == AllAliasesResolved) { + aliasCacheCreator.appendAliasesToPropertyCache(*qmlObjects->at(componentIndex), objectIndex); + atLeastOneAliasResolved = true; + } else if (result == SomeAliasesResolved) { + atLeastOneAliasResolved = true; + pendingObjects.append(objectIndex); } else { - QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); - Q_ASSERT(targetCache); - QmlIR::PropertyResolver resolver(targetCache); + pendingObjects.append(objectIndex); + } + } + qSwap(_objectsWithAliases, pendingObjects); + } while (!_objectsWithAliases.isEmpty() && atLeastOneAliasResolved); - QQmlPropertyData *targetProperty = resolver.property(property.toString()); - if (!targetProperty || targetProperty->coreIndex > 0x0000FFFF) { - recordError(p->aliasLocation, tr("Invalid alias target location: %1").arg(property.toString())); - return false; - } + if (!atLeastOneAliasResolved && !_objectsWithAliases.isEmpty()) { + const QmlIR::Object *obj = qmlObjects->at(_objectsWithAliases.first()); + for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { + if (!(alias->flags & QV4::CompiledData::Alias::Resolved)) { + recordError(alias->location, tr("Circular alias reference detected")); + return false; + } + } + } - propIdx = targetProperty->coreIndex; - type = targetProperty->propType; + return true; +} - writable = targetProperty->isWritable(); - resettable = targetProperty->isResettable(); - notifySignal = targetProperty->notifyIndex; +QQmlComponentAndAliasResolver::AliasResolutionResult QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, QQmlCompileError *error) +{ + const QmlIR::Object * const obj = qmlObjects->at(objectIndex); + if (!obj->aliasCount()) + return AllAliasesResolved; - if (!subProperty.isEmpty()) { - const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(type); - if (!valueTypeMetaObject) { - recordError(p->aliasLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); - return false; - } + int numResolvedAliases = 0; + bool seenUnresolvedAlias = false; - propType = type; + for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { + if (alias->flags & QV4::CompiledData::Alias::Resolved) + continue; - int valueTypeIndex = - valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); - if (valueTypeIndex == -1) { - recordError(p->aliasLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); - return false; - } - Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + seenUnresolvedAlias = true; - propIdx = QQmlPropertyData::encodeValueTypePropertyIndex(propIdx, valueTypeIndex); - if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) - type = QVariant::Int; - else - type = valueTypeMetaObject->property(valueTypeIndex).userType(); + const int idIndex = alias->idIndex; + const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); + if (targetObjectIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + break; + } - } else { - if (targetProperty->isEnum()) { - type = QVariant::Int; - } else { - // Copy type flags - propertyFlags |= targetProperty->getFlags() & QQmlPropertyData::PropTypeFlagMask; + const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); + Q_ASSERT(targetObject->id >= 0); + alias->targetObjectId = targetObject->id; + alias->aliasToLocalAlias = false; - if (targetProperty->isVarProperty()) - propertyFlags |= QQmlPropertyData::IsQVariant; + const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); - if (targetProperty->isQObject()) - flags |= QML_ALIAS_FLAG_PTR; + QStringRef property; + QStringRef subProperty; + + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = aliasPropertyValue.leftRef(propertySeparator); + subProperty = aliasPropertyValue.midRef(propertySeparator + 1); + } else + property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); + + QQmlPropertyIndex propIdx; + + if (property.isEmpty()) { + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } else { + QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); + Q_ASSERT(targetCache); + QmlIR::PropertyResolver resolver(targetCache); + + QQmlPropertyData *targetProperty = resolver.property(property.toString()); + + // If it's an alias that we haven't resolved yet, try again later. + if (!targetProperty) { + bool aliasPointsToOtherAlias = false; + int localAliasIndex = 0; + for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { + if (stringAt(targetAlias->nameIndex) == property) { + aliasPointsToOtherAlias = true; + break; } } - } - - QQmlVMEMetaData::AliasData aliasData = { targetId, propIdx, propType, flags, notifySignal }; + if (aliasPointsToOtherAlias) { + if (targetObjectIndex == objectIndex) { + alias->localAliasIndex = localAliasIndex; + alias->aliasToLocalAlias = true; + alias->flags |= QV4::CompiledData::Alias::Resolved; + ++numResolvedAliases; + continue; + } - typedef QQmlVMEMetaData VMD; - QByteArray &dynamicData = (*vmeMetaObjectData)[objectIndex]; - Q_ASSERT(!dynamicData.isEmpty()); - VMD *vmd = (QQmlVMEMetaData *)dynamicData.data(); - *(vmd->aliasData() + effectiveAliasIndex++) = aliasData; + // Try again later and resolve the target alias first. + _objectsWithAliases.append(objectIndex); + // restore + alias->idIndex = idIndex; + break; + } + } - Q_ASSERT(dynamicData.isDetached()); + if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); + break; + } - if (!(p->flags & QV4::CompiledData::Property::IsReadOnly) && writable) - propertyFlags |= QQmlPropertyData::IsWritable; - else - propertyFlags &= ~QQmlPropertyData::IsWritable; + propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); - if (resettable) - propertyFlags |= QQmlPropertyData::IsResettable; - else - propertyFlags &= ~QQmlPropertyData::IsResettable; + if (!subProperty.isEmpty()) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(targetProperty->propType()); + if (!valueTypeMetaObject) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } - QString propertyName = stringAt(p->nameIndex); - if (propertyIndex == obj->indexOfDefaultProperty) propertyCache->_defaultPropertyName = propertyName; - propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - type, effectiveSignalIndex++); + int valueTypeIndex = + valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); + } else { + if (targetProperty->isQObject()) + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } } + + alias->encodedMetaPropertyIndex = propIdx.toEncoded(); + alias->flags |= QV4::CompiledData::Alias::Resolved; + numResolvedAliases++; } - return true; + + if (numResolvedAliases == 0) + return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; + + return SomeAliasesResolved; } -QQmlPropertyValidator::QQmlPropertyValidator(QQmlTypeCompiler *typeCompiler) +QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) : QQmlCompilePass(typeCompiler) - , enginePrivate(typeCompiler->enginePrivate()) - , qmlUnit(typeCompiler->qmlUnit()) - , resolvedTypes(*typeCompiler->resolvedTypes()) - , customParsers(typeCompiler->customParserCache()) + , qmlObjects(typeCompiler->qmlObjects()) , propertyCaches(typeCompiler->propertyCaches()) - , objectIndexToIdPerComponent(*typeCompiler->objectIndexToIdPerComponent()) - , customParserBindingsPerObject(typeCompiler->customParserBindings()) + , customParsers(typeCompiler->customParserCache()) , _seenObjectWithId(false) { } -bool QQmlPropertyValidator::validate() -{ - _bindingPropertyDataPerObject.resize(qmlUnit->nObjects); - if (!validateObject(qmlUnit->indexOfRootObject, /*instantiatingBinding*/0)) - return false; - compiler->setDeferredBindingsPerObject(_deferredBindingsPerObject); - compiler->setBindingPropertyDataPerObject(_bindingPropertyDataPerObject); - return true; -} - -const QQmlImports &QQmlPropertyValidator::imports() const +bool QQmlDeferredAndCustomParserBindingScanner::scanObject() { - return *compiler->imports(); + return scanObject(compiler->rootObjectIndex()); } -typedef QVarLengthArray<const QV4::CompiledData::Binding *, 8> GroupPropertyVector; - -struct BindingFinder -{ - bool operator()(quint32 name, const QV4::CompiledData::Binding *binding) const - { - return name < binding->propertyNameIndex; - } - bool operator()(const QV4::CompiledData::Binding *binding, quint32 name) const - { - return binding->propertyNameIndex < name; - } - bool operator()(const QV4::CompiledData::Binding *lhs, const QV4::CompiledData::Binding *rhs) const - { - return lhs->propertyNameIndex < rhs->propertyNameIndex; - } -}; - -bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty) const +bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) { - const QV4::CompiledData::Object *obj = qmlUnit->objectAt(objectIndex); - if (obj->idIndex != 0) + QmlIR::Object *obj = qmlObjects->at(objectIndex); + if (obj->idNameIndex != 0) _seenObjectWithId = true; - if (isComponent(objectIndex)) { - Q_ASSERT(obj->nBindings == 1); - const QV4::CompiledData::Binding *componentBinding = obj->bindingTable(); + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->bindingCount() == 1); + const QV4::CompiledData::Binding *componentBinding = obj->firstBinding(); Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); - return validateObject(componentBinding->value.objectIndex, componentBinding); + return scanObject(componentBinding->value.objectIndex); } - QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); if (!propertyCache) return true; - QStringList deferredPropertyNames; - { - const QMetaObject *mo = propertyCache->firstCppMetaObject(); - const int namesIndex = mo->indexOfClassInfo("DeferredPropertyNames"); - if (namesIndex != -1) { - QMetaClassInfo classInfo = mo->classInfo(namesIndex); - deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(QLatin1Char(',')); - } - } - - QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex); - QList<const QV4::CompiledData::Binding*> customBindings; - - // Collect group properties first for sanity checking - // vector values are sorted by property name string index. - GroupPropertyVector groupProperties; - const QV4::CompiledData::Binding *binding = obj->bindingTable(); - for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { - if (!binding->isGroupProperty()) - continue; - - if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) - continue; - - if (populatingValueTypeGroupProperty) { - recordError(binding->location, tr("Property assignment expected")); - return false; - } - - GroupPropertyVector::const_iterator pos = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); - groupProperties.insert(pos, binding); - } - - QBitArray customParserBindings(obj->nBindings); - QBitArray deferredBindings; - - QmlIR::PropertyResolver propertyResolver(propertyCache); - QString defaultPropertyName; QQmlPropertyData *defaultProperty = 0; - if (obj->indexOfDefaultProperty != -1) { + if (obj->indexOfDefaultPropertyOrAlias != -1) { QQmlPropertyCache *cache = propertyCache->parent(); defaultPropertyName = cache->defaultPropertyName(); defaultProperty = cache->defaultProperty(); @@ -1851,76 +1198,55 @@ bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledD defaultProperty = propertyCache->defaultProperty(); } - QV4::CompiledData::BindingPropertyData collectedBindingPropertyData(obj->nBindings); + QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex); + + QmlIR::PropertyResolver propertyResolver(propertyCache); + + QStringList deferredPropertyNames; + { + const QMetaObject *mo = propertyCache->firstCppMetaObject(); + const int namesIndex = mo->indexOfClassInfo("DeferredPropertyNames"); + if (namesIndex != -1) { + QMetaClassInfo classInfo = mo->classInfo(namesIndex); + deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(QLatin1Char(',')); + } + } - binding = obj->bindingTable(); - for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) { + for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { + QQmlPropertyData *pd = 0; QString name = stringAt(binding->propertyNameIndex); if (customParser) { if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { - customBindings << binding; - customParserBindings.setBit(i); + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; continue; } } else if (QmlIR::IRBuilder::isSignalPropertyName(name) && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { - customBindings << binding; - customParserBindings.setBit(i); + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; continue; } } - bool bindingToDefaultProperty = false; - bool isGroupProperty = instantiatingBinding && instantiatingBinding->type == QV4::CompiledData::Binding::Type_GroupProperty; - - bool notInRevision = false; - QQmlPropertyData *pd = 0; - if (!name.isEmpty()) { - if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression - || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) - pd = propertyResolver.signal(name, ¬InRevision); - else - pd = propertyResolver.property(name, ¬InRevision, isGroupProperty ? QmlIR::PropertyResolver::IgnoreRevision : QmlIR::PropertyResolver::CheckRevision); - - if (notInRevision) { - QString typeName = stringAt(obj->inheritedTypeNameIndex); - QQmlCompiledData::TypeReference *objectType = resolvedTypes.value(obj->inheritedTypeNameIndex); - if (objectType && objectType->type) { - COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(name).arg(objectType->type->module()).arg(objectType->majorVersion).arg(objectType->minorVersion)); - } else { - COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(name)); - } - } + if (name.isEmpty()) { + pd = defaultProperty; + name = defaultPropertyName; } else { - if (isGroupProperty) - COMPILE_EXCEPTION(binding, tr("Cannot assign a value directly to a grouped property")); - - pd = defaultProperty; - name = defaultPropertyName; - bindingToDefaultProperty = true; - } - - if (pd) - collectedBindingPropertyData[i] = pd; + if (name.constData()->isUpper()) + continue; - if (name.constData()->isUpper() && !binding->isAttachedProperty()) { - QQmlType *type = 0; - QQmlImportNamespace *typeNamespace = 0; - compiler->imports()->resolveType(stringAt(binding->propertyNameIndex), &type, 0, 0, &typeNamespace); - if (typeNamespace) - recordError(binding->location, tr("Invalid use of namespace")); - else - recordError(binding->location, tr("Invalid attached object assignment")); - return false; + bool notInRevision = false; + pd = propertyResolver.property(name, ¬InRevision, QmlIR::PropertyResolver::CheckRevision); } bool seenSubObjectWithId = false; if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { qSwap(_seenObjectWithId, seenSubObjectWithId); - const bool subObjectValid = validateObject(binding->value.objectIndex, binding, pd && QQmlValueTypeFactory::metaObjectForMetaType(pd->propType)); + const bool subObjectValid = scanObject(binding->value.objectIndex); qSwap(_seenObjectWithId, seenSubObjectWithId); if (!subObjectValid) return false; @@ -1930,538 +1256,28 @@ bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledD if (!seenSubObjectWithId && !deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) { - if (deferredBindings.isEmpty()) - deferredBindings.resize(obj->nBindings); - - deferredBindings.setBit(i); + binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; + obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; } - // Signal handlers were resolved and checked earlier in the signal handler conversion pass. if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) continue; - if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { - if (instantiatingBinding && (instantiatingBinding->isAttachedProperty() || instantiatingBinding->isGroupProperty())) { - recordError(binding->location, tr("Attached properties cannot be used here")); - return false; - } - continue; - } - - if (pd) { - GroupPropertyVector::const_iterator assignedGroupProperty = std::lower_bound(groupProperties.constBegin(), groupProperties.constEnd(), binding->propertyNameIndex, BindingFinder()); - const bool assigningToGroupProperty = assignedGroupProperty != groupProperties.constEnd() && !(binding->propertyNameIndex < (*assignedGroupProperty)->propertyNameIndex); - - if (!pd->isWritable() - && !pd->isQList() - && !binding->isGroupProperty() - && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration) - ) { - - if (assigningToGroupProperty && binding->type < QV4::CompiledData::Binding::Type_Object) - recordError(binding->valueLocation, tr("Cannot assign a value directly to a grouped property")); - else - recordError(binding->valueLocation, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); - return false; - } - - if (!pd->isQList() && (binding->flags & QV4::CompiledData::Binding::IsListItem)) { - QString error; - if (pd->propType == qMetaTypeId<QQmlScriptString>()) - error = tr( "Cannot assign multiple values to a script property"); - else - error = tr( "Cannot assign multiple values to a singular property"); - recordError(binding->valueLocation, error); - return false; - } - - if (!bindingToDefaultProperty - && !binding->isGroupProperty() - && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment) - && assigningToGroupProperty) { - QV4::CompiledData::Location loc = binding->valueLocation; - if (loc < (*assignedGroupProperty)->valueLocation) - loc = (*assignedGroupProperty)->valueLocation; - - if (pd && QQmlValueTypeFactory::isValueType(pd->propType)) - recordError(loc, tr("Property has already been assigned a value")); - else - recordError(loc, tr("Cannot assign a value directly to a grouped property")); - return false; - } - - if (binding->type < QV4::CompiledData::Binding::Type_Script) { - if (!validateLiteralBinding(propertyCache, pd, binding)) - return false; - } else if (binding->type == QV4::CompiledData::Binding::Type_Object) { - if (!validateObjectBinding(pd, name, binding)) - return false; - } else if (binding->isGroupProperty()) { - if (QQmlValueTypeFactory::isValueType(pd->propType)) { - if (QQmlValueTypeFactory::metaObjectForMetaType(pd->propType)) { - if (!pd->isWritable()) { - recordError(binding->location, tr("Invalid property assignment: \"%1\" is a read-only property").arg(name)); - return false; - } - } else { - recordError(binding->location, tr("Invalid grouped property access")); - return false; - } - } else { - if (!enginePrivate->propertyCacheForType(pd->propType)) { - recordError(binding->location, tr("Invalid grouped property access")); - return false; - } - } - } - } else { + if (!pd) { if (customParser) { - customBindings << binding; - customParserBindings.setBit(i); - continue; - } - if (bindingToDefaultProperty) { - COMPILE_EXCEPTION(binding, tr("Cannot assign to non-existent default property")); - } else { - COMPILE_EXCEPTION(binding, tr("Cannot assign to non-existent property \"%1\"").arg(name)); - } - } - } - - if (obj->idIndex) { - bool notInRevision = false; - collectedBindingPropertyData << propertyResolver.property(QStringLiteral("id"), ¬InRevision); - } - - if (customParser && !customBindings.isEmpty()) { - customParser->clearErrors(); - customParser->validator = this; - customParser->engine = enginePrivate; - customParser->imports = compiler->imports(); - customParser->verifyBindings(qmlUnit, customBindings); - customParser->validator = 0; - customParser->engine = 0; - customParser->imports = (QQmlImports*)0; - customParserBindingsPerObject->insert(objectIndex, customParserBindings); - const QList<QQmlError> parserErrors = customParser->errors(); - if (!parserErrors.isEmpty()) { - foreach (const QQmlError &error, parserErrors) - compiler->recordError(error); - return false; - } - } - - if (!deferredBindings.isEmpty()) - _deferredBindingsPerObject.insert(objectIndex, deferredBindings); - - _bindingPropertyDataPerObject[objectIndex] = collectedBindingPropertyData; - - return true; -} - -bool QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const -{ - if (property->isQList()) { - recordError(binding->valueLocation, tr("Cannot assign primitives to lists")); - return false; - } - - if (property->isEnum()) { - if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) - return true; - - QString value = binding->valueAsString(qmlUnit); - QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex); - bool ok; - if (p.isFlagType()) { - p.enumerator().keysToValue(value.toUtf8().constData(), &ok); - } else - p.enumerator().keyToValue(value.toUtf8().constData(), &ok); - - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: unknown enumeration")); - return false; - } - return true; - } - - switch (property->propType) { - case QMetaType::QVariant: - break; - case QVariant::String: { - if (!binding->evaluatesToString()) { - recordError(binding->valueLocation, tr("Invalid property assignment: string expected")); - return false; - } - } - break; - case QVariant::StringList: { - if (!binding->evaluatesToString()) { - recordError(binding->valueLocation, tr("Invalid property assignment: string or string list expected")); - return false; - } - } - break; - case QVariant::ByteArray: { - if (binding->type != QV4::CompiledData::Binding::Type_String) { - recordError(binding->valueLocation, tr("Invalid property assignment: byte array expected")); - return false; - } - } - break; - case QVariant::Url: { - if (binding->type != QV4::CompiledData::Binding::Type_String) { - recordError(binding->valueLocation, tr("Invalid property assignment: url expected")); - return false; - } - } - break; - case QVariant::UInt: { - if (binding->type == QV4::CompiledData::Binding::Type_Number) { - double d = binding->valueAsNumber(); - if (double(uint(d)) == d) - return true; - } - recordError(binding->valueLocation, tr("Invalid property assignment: unsigned int expected")); - return false; - } - break; - case QVariant::Int: { - if (binding->type == QV4::CompiledData::Binding::Type_Number) { - double d = binding->valueAsNumber(); - if (double(int(d)) == d) - return true; - } - recordError(binding->valueLocation, tr("Invalid property assignment: int expected")); - return false; - } - break; - case QMetaType::Float: { - if (binding->type != QV4::CompiledData::Binding::Type_Number) { - recordError(binding->valueLocation, tr("Invalid property assignment: number expected")); - return false; - } - } - break; - case QVariant::Double: { - if (binding->type != QV4::CompiledData::Binding::Type_Number) { - recordError(binding->valueLocation, tr("Invalid property assignment: number expected")); - return false; - } - } - break; - case QVariant::Color: { - bool ok = false; - QQmlStringConverters::rgbaFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: color expected")); - return false; - } - } - break; -#ifndef QT_NO_DATESTRING - case QVariant::Date: { - bool ok = false; - QQmlStringConverters::dateFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: date expected")); - return false; - } - } - break; - case QVariant::Time: { - bool ok = false; - QQmlStringConverters::timeFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: time expected")); - return false; - } - } - break; - case QVariant::DateTime: { - bool ok = false; - QQmlStringConverters::dateTimeFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: datetime expected")); - return false; - } - } - break; -#endif // QT_NO_DATESTRING - case QVariant::Point: { - bool ok = false; - QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); - return false; - } - } - break; - case QVariant::PointF: { - bool ok = false; - QQmlStringConverters::pointFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); - return false; - } - } - break; - case QVariant::Size: { - bool ok = false; - QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: size expected")); - return false; - } - } - break; - case QVariant::SizeF: { - bool ok = false; - QQmlStringConverters::sizeFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: size expected")); - return false; - } - } - break; - case QVariant::Rect: { - bool ok = false; - QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: rect expected")); - return false; - } - } - break; - case QVariant::RectF: { - bool ok = false; - QQmlStringConverters::rectFFromString(binding->valueAsString(qmlUnit), &ok); - if (!ok) { - recordError(binding->valueLocation, tr("Invalid property assignment: point expected")); - return false; - } - } - break; - case QVariant::Bool: { - if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { - recordError(binding->valueLocation, tr("Invalid property assignment: boolean expected")); - return false; - } - } - break; - case QVariant::Vector2D: { - struct { - float xp; - float yp; - } vec; - if (!QQmlStringConverters::createFromString(QMetaType::QVector2D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { - recordError(binding->valueLocation, tr("Invalid property assignment: 2D vector expected")); - return false; - } - } - break; - case QVariant::Vector3D: { - struct { - float xp; - float yp; - float zy; - } vec; - if (!QQmlStringConverters::createFromString(QMetaType::QVector3D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { - recordError(binding->valueLocation, tr("Invalid property assignment: 3D vector expected")); - return false; - } - } - break; - case QVariant::Vector4D: { - struct { - float xp; - float yp; - float zy; - float wp; - } vec; - if (!QQmlStringConverters::createFromString(QMetaType::QVector4D, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { - recordError(binding->valueLocation, tr("Invalid property assignment: 4D vector expected")); - return false; - } - } - break; - case QVariant::Quaternion: { - struct { - float wp; - float xp; - float yp; - float zp; - } vec; - if (!QQmlStringConverters::createFromString(QMetaType::QQuaternion, binding->valueAsString(qmlUnit), &vec, sizeof(vec))) { - recordError(binding->valueLocation, tr("Invalid property assignment: quaternion expected")); - return false; - } - } - break; - case QVariant::RegExp: - recordError(binding->valueLocation, tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); - return false; - default: { - // generate single literal value assignment to a list property if required - if (property->propType == qMetaTypeId<QList<qreal> >()) { - if (binding->type != QV4::CompiledData::Binding::Type_Number) { - recordError(binding->valueLocation, tr("Invalid property assignment: number or array of numbers expected")); - return false; - } - break; - } else if (property->propType == qMetaTypeId<QList<int> >()) { - bool ok = (binding->type == QV4::CompiledData::Binding::Type_Number); - if (ok) { - double n = binding->valueAsNumber(); - if (double(int(n)) != n) - ok = false; - } - if (!ok) - recordError(binding->valueLocation, tr("Invalid property assignment: int or array of ints expected")); - break; - } else if (property->propType == qMetaTypeId<QList<bool> >()) { - if (binding->type != QV4::CompiledData::Binding::Type_Boolean) { - recordError(binding->valueLocation, tr("Invalid property assignment: bool or array of bools expected")); - return false; - } - break; - } else if (property->propType == qMetaTypeId<QList<QUrl> >()) { - if (binding->type != QV4::CompiledData::Binding::Type_String) { - recordError(binding->valueLocation, tr("Invalid property assignment: url or array of urls expected")); - return false; + obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; + binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; } - break; - } else if (property->propType == qMetaTypeId<QList<QString> >()) { - if (!binding->evaluatesToString()) { - recordError(binding->valueLocation, tr("Invalid property assignment: string or array of strings expected")); - return false; - } - break; - } else if (property->propType == qMetaTypeId<QJSValue>()) { - break; - } else if (property->propType == qMetaTypeId<QQmlScriptString>()) { - break; - } - - // otherwise, try a custom type assignment - QQmlMetaType::StringConverter converter = QQmlMetaType::customStringConverter(property->propType); - if (!converter) { - recordError(binding->valueLocation, tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QMetaType::typeName(property->propType)))); - return false; } } - break; - } - return true; -} - -/*! - Returns true if from can be assigned to a (QObject) property of type - to. -*/ -bool QQmlPropertyValidator::canCoerce(int to, QQmlPropertyCache *fromMo) const -{ - QQmlPropertyCache *toMo = enginePrivate->rawPropertyCacheForType(to); - - while (fromMo) { - if (fromMo == toMo) - return true; - fromMo = fromMo->parent(); - } - return false; -} -bool QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const -{ - if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Object); - - bool isValueSource = false; - bool isPropertyInterceptor = false; - - QQmlType *qmlType = 0; - const QV4::CompiledData::Object *targetObject = qmlUnit->objectAt(binding->value.objectIndex); - QQmlCompiledData::TypeReference *typeRef = resolvedTypes.value(targetObject->inheritedTypeNameIndex); - if (typeRef) { - QQmlPropertyCache *cache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); - const QMetaObject *mo = cache->firstCppMetaObject(); - while (mo && !qmlType) { - qmlType = QQmlMetaType::qmlType(mo); - mo = mo->superClass(); - } - Q_ASSERT(qmlType); - } - - if (qmlType) { - isValueSource = qmlType->propertyValueSourceCast() != -1; - isPropertyInterceptor = qmlType->propertyValueInterceptorCast() != -1; - } - - if (!isValueSource && !isPropertyInterceptor) { - recordError(binding->valueLocation, tr("\"%1\" cannot operate on \"%2\"").arg(stringAt(targetObject->inheritedTypeNameIndex)).arg(propertyName)); - return false; - } - - return true; - } - - if (QQmlMetaType::isInterface(property->propType)) { - // Can only check at instantiation time if the created sub-object successfully casts to the - // target interface. - return true; - } else if (property->propType == QMetaType::QVariant) { - // We can convert everything to QVariant :) - return true; - } else if (property->isQList()) { - const int listType = enginePrivate->listType(property->propType); - if (!QQmlMetaType::isInterface(listType)) { - QQmlPropertyCache *source = propertyCaches.at(binding->value.objectIndex); - if (!canCoerce(listType, source)) { - recordError(binding->valueLocation, tr("Cannot assign object to list property \"%1\"").arg(propertyName)); - return false; - } - } - return true; - } else if (isComponent(binding->value.objectIndex)) { - return true; - } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { - return true; - } else if (QQmlValueTypeFactory::isValueType(property->propType)) { - recordError(binding->location, tr("Unexpected object assignment")); - return false; - } else if (property->propType == qMetaTypeId<QQmlScriptString>()) { - recordError(binding->valueLocation, tr("Invalid property assignment: script expected")); - return false; - } else { - // We want to raw metaObject here as the raw metaobject is the - // actual property type before we applied any extensions that might - // effect the properties on the type, but don't effect assignability - QQmlPropertyCache *propertyMetaObject = enginePrivate->rawPropertyCacheForType(property->propType); - - // Will be true if the assgned type inherits propertyMetaObject - bool isAssignable = false; - // Determine isAssignable value - if (propertyMetaObject) { - QQmlPropertyCache *c = propertyCaches.at(binding->value.objectIndex); - while (c && !isAssignable) { - isAssignable |= c == propertyMetaObject; - c = c->parent(); - } - } - - if (!isAssignable) { - recordError(binding->valueLocation, tr("Cannot assign object to property")); - return false; - } - } return true; } QQmlJSCodeGenerator::QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QmlIR::JSCodeGen *v4CodeGen) : QQmlCompilePass(typeCompiler) - , objectIndexToIdPerComponent(*typeCompiler->objectIndexToIdPerComponent()) - , resolvedTypes(*typeCompiler->resolvedTypes()) + , resolvedTypes(typeCompiler->resolvedTypes) , customParsers(typeCompiler->customParserCache()) , qmlObjects(*typeCompiler->qmlObjects()) , propertyCaches(typeCompiler->propertyCaches()) @@ -2471,48 +1287,42 @@ QQmlJSCodeGenerator::QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QmlIR:: bool QQmlJSCodeGenerator::generateCodeForComponents() { - const QHash<int, QHash<int, int> > &objectIndexToIdPerComponent = *compiler->objectIndexToIdPerComponent(); - for (QHash<int, QHash<int, int> >::ConstIterator component = objectIndexToIdPerComponent.constBegin(), end = objectIndexToIdPerComponent.constEnd(); - component != end; ++component) { - if (!compileComponent(component.key(), component.value())) + const QVector<quint32> &componentRoots = compiler->componentRoots(); + for (int i = 0; i < componentRoots.count(); ++i) { + if (!compileComponent(componentRoots.at(i))) return false; } - return compileComponent(compiler->rootObjectIndex(), *compiler->objectIndexToIdForRoot()); + return compileComponent(compiler->rootObjectIndex()); } -bool QQmlJSCodeGenerator::compileComponent(int contextObject, const QHash<int, int> &objectIndexToId) +bool QQmlJSCodeGenerator::compileComponent(int contextObject) { - if (isComponent(contextObject)) { - const QmlIR::Object *component = qmlObjects.at(contextObject); - Q_ASSERT(component->bindingCount() == 1); - const QV4::CompiledData::Binding *componentBinding = component->firstBinding(); + const QmlIR::Object *obj = qmlObjects.at(contextObject); + if (obj->flags & QV4::CompiledData::Object::IsComponent) { + Q_ASSERT(obj->bindingCount() == 1); + const QV4::CompiledData::Binding *componentBinding = obj->firstBinding(); Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); contextObject = componentBinding->value.objectIndex; } QmlIR::JSCodeGen::ObjectIdMapping idMapping; - if (!objectIndexToId.isEmpty()) { - idMapping.reserve(objectIndexToId.count()); + idMapping.reserve(obj->namedObjectsInComponent.count); + for (int i = 0; i < obj->namedObjectsInComponent.count; ++i) { + const int objectIndex = obj->namedObjectsInComponent.at(i); + QmlIR::JSCodeGen::IdMapping m; + const QmlIR::Object *obj = qmlObjects.at(objectIndex); + m.name = stringAt(obj->idNameIndex); + m.idIndex = obj->id; + m.type = propertyCaches->at(objectIndex); - for (QHash<int, int>::ConstIterator idIt = objectIndexToId.constBegin(), end = objectIndexToId.constEnd(); - idIt != end; ++idIt) { + auto *tref = resolvedTypes.value(obj->inheritedTypeNameIndex); + if (tref && tref->isFullyDynamicType) + m.type = 0; - const int objectIndex = idIt.key(); - QmlIR::JSCodeGen::IdMapping m; - const QmlIR::Object *obj = qmlObjects.at(objectIndex); - m.name = stringAt(obj->idIndex); - m.idIndex = idIt.value(); - m.type = propertyCaches.at(objectIndex); - - QQmlCompiledData::TypeReference *tref = resolvedTypes.value(obj->inheritedTypeNameIndex); - if (tref && tref->isFullyDynamicType) - m.type = 0; - - idMapping << m; - } + idMapping << m; } - v4CodeGen->beginContextScope(idMapping, propertyCaches.at(contextObject)); + v4CodeGen->beginContextScope(idMapping, propertyCaches->at(contextObject)); if (!compileJavaScriptCodeInObjectsRecursively(contextObject, contextObject)) return false; @@ -2522,12 +1332,12 @@ bool QQmlJSCodeGenerator::compileComponent(int contextObject, const QHash<int, i bool QQmlJSCodeGenerator::compileJavaScriptCodeInObjectsRecursively(int objectIndex, int scopeObjectIndex) { - if (isComponent(objectIndex)) + QmlIR::Object *object = qmlObjects.at(objectIndex); + if (object->flags & QV4::CompiledData::Object::IsComponent) return true; - QmlIR::Object *object = qmlObjects.at(objectIndex); if (object->functionsAndExpressions->count > 0) { - QQmlPropertyCache *scopeObject = propertyCaches.at(scopeObjectIndex); + QQmlPropertyCache *scopeObject = propertyCaches->at(scopeObjectIndex); v4CodeGen->beginObjectScope(scopeObject); QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile; @@ -2546,8 +1356,7 @@ bool QQmlJSCodeGenerator::compileJavaScriptCodeInObjectsRecursively(int objectIn } QQmlJS::MemoryPool *pool = compiler->memoryPool(); - object->runtimeFunctionIndices = pool->New<QmlIR::FixedPoolArray<int> >(); - object->runtimeFunctionIndices->init(pool, runtimeFunctionIndices); + object->runtimeFunctionIndices.allocate(pool, runtimeFunctionIndices); } for (const QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) { @@ -2580,20 +1389,20 @@ void QQmlDefaultPropertyMerger::mergeDefaultProperties() void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) { - QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); + QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); if (!propertyCache) return; QmlIR::Object *object = qmlObjects.at(objectIndex); - QString defaultProperty = object->indexOfDefaultProperty != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); + QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); QmlIR::Binding *bindingsToReinsert = 0; QmlIR::Binding *tail = 0; QmlIR::Binding *previousBinding = 0; QmlIR::Binding *binding = object->firstBinding(); while (binding) { - if (binding->propertyNameIndex == 0 || stringAt(binding->propertyNameIndex) != defaultProperty) { + if (binding->propertyNameIndex == quint32(0) || stringAt(binding->propertyNameIndex) != defaultProperty) { previousBinding = binding; binding = binding->next; continue; @@ -2646,7 +1455,7 @@ void QQmlJavaScriptBindingExpressionSimplificationPass::reduceTranslationBinding if (binding->type != QV4::CompiledData::Binding::Type_Script) continue; - const int irFunctionIndex = obj->runtimeFunctionIndices->at(binding->value.compiledScriptIndex); + const int irFunctionIndex = obj->runtimeFunctionIndices.at(binding->value.compiledScriptIndex); QV4::IR::Function *irFunction = jsModule->functions.at(irFunctionIndex); if (simplifyBinding(irFunction, binding)) { irFunctionsToRemove.append(irFunctionIndex); @@ -2757,7 +1566,7 @@ bool QQmlJavaScriptBindingExpressionSimplificationPass::simplifyBinding(QV4::IR: for (QV4::IR::BasicBlock *bb : function->basicBlocks()) { for (QV4::IR::Stmt *s : bb->statements()) { - s->accept(this); + visit(s); if (!_canSimplify) return false; } @@ -2927,22 +1736,41 @@ void QQmlIRFunctionCleanser::clean() foreach (QV4::IR::Function *function, module->functions) { for (QV4::IR::BasicBlock *block : function->basicBlocks()) { for (QV4::IR::Stmt *s : block->statements()) { - s->accept(this); + visit(s); } } } foreach (QmlIR::Object *obj, *compiler->qmlObjects()) { - if (!obj->runtimeFunctionIndices) - continue; - for (int i = 0; i < obj->runtimeFunctionIndices->count; ++i) - (*obj->runtimeFunctionIndices)[i] = newFunctionIndices[obj->runtimeFunctionIndices->at(i)]; + for (int i = 0; i < obj->runtimeFunctionIndices.count; ++i) + obj->runtimeFunctionIndices[i] = newFunctionIndices[obj->runtimeFunctionIndices.at(i)]; + } +} + +void QQmlIRFunctionCleanser::visit(QV4::IR::Stmt *s) +{ + + switch (s->stmtKind) { + case QV4::IR::Stmt::PhiStmt: + // nothing to do + break; + default: + STMT_VISIT_ALL_KINDS(s); + break; } } -void QQmlIRFunctionCleanser::visitClosure(QV4::IR::Closure *closure) +void QQmlIRFunctionCleanser::visit(QV4::IR::Expr *e) { - closure->value = newFunctionIndices.at(closure->value); + switch (e->exprKind) { + case QV4::IR::Expr::ClosureExpr: { + auto closure = e->asClosure(); + closure->value = newFunctionIndices.at(closure->value); + } break; + default: + EXPR_VISIT_ALL_KINDS(e); + break; + } } QT_END_NAMESPACE diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h index 273ba01a88..de6abb4ced 100644 --- a/src/qml/compiler/qqmltypecompiler_p.h +++ b/src/qml/compiler/qqmltypecompiler_p.h @@ -53,13 +53,12 @@ #include <qglobal.h> #include <qqmlerror.h> #include <qhash.h> -#include <private/qqmlcompiler_p.h> +#include <private/qqmltypeloader_p.h> #include <private/qqmlirbuilder_p.h> QT_BEGIN_NAMESPACE class QQmlEnginePrivate; -class QQmlCompiledData; class QQmlError; class QQmlTypeData; class QQmlImports; @@ -75,18 +74,40 @@ struct Location; } } +struct QQmlCompileError +{ + QQmlCompileError() {} + QQmlCompileError(const QV4::CompiledData::Location &location, const QString &description) + : location(location), description(description) {} + QV4::CompiledData::Location location; + QString description; + + bool isSet() const { return !description.isEmpty(); } +}; + struct QQmlTypeCompiler { Q_DECLARE_TR_FUNCTIONS(QQmlTypeCompiler) public: - QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlCompiledData *compiledData, QQmlTypeData *typeData, QmlIR::Document *document); + QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *document, const QQmlRefPointer<QQmlTypeNameCache> &importCache, const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache); + + // --- interface used by QQmlPropertyCacheCreator + typedef QmlIR::Object CompiledObject; + const QmlIR::Object *objectAt(int index) const { return document->objects.at(index); } + int objectCount() const { return document->objects.count(); } + QString stringAt(int idx) const; + QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsBegin(const QmlIR::Object *object) const { return object->functionsBegin(); } + QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsEnd(const QmlIR::Object *object) const { return object->functionsEnd(); } + QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypes; + // --- - bool compile(); + QV4::CompiledData::CompilationUnit *compile(); QList<QQmlError> compilationErrors() const { return errors; } - void recordError(const QQmlError &error); + void recordError(QQmlError error); + void recordError(const QV4::CompiledData::Location &location, const QString &description); + void recordError(const QQmlCompileError &error); - QString stringAt(int idx) const; int registerString(const QString &str); QV4::IR::Module *jsIRModule() const; @@ -96,70 +117,52 @@ public: QUrl url() const { return typeData->finalUrl(); } QQmlEnginePrivate *enginePrivate() const { return engine; } const QQmlImports *imports() const; - QHash<int, QQmlCompiledData::TypeReference *> *resolvedTypes(); - QList<QmlIR::Object*> *qmlObjects(); + QVector<QmlIR::Object *> *qmlObjects() const; int rootObjectIndex() const; - void setPropertyCaches(const QVector<QQmlPropertyCache *> &caches); - const QVector<QQmlPropertyCache *> &propertyCaches() const; - void setVMEMetaObjects(const QVector<QByteArray> &metaObjects); - QVector<QByteArray> *vmeMetaObjects() const; - QHash<int, int> *objectIndexToIdForRoot(); - QHash<int, QHash<int, int> > *objectIndexToIdPerComponent(); - QHash<int, QBitArray> *customParserBindings(); + void setPropertyCaches(QQmlPropertyCacheVector &&caches); + const QQmlPropertyCacheVector *propertyCaches() const; + QQmlPropertyCacheVector &&takePropertyCaches(); + void setComponentRoots(const QVector<quint32> &roots) { m_componentRoots = roots; } + const QVector<quint32> &componentRoots() const { return m_componentRoots; } QQmlJS::MemoryPool *memoryPool(); QStringRef newStringRef(const QString &string); const QV4::Compiler::StringTableGenerator *stringPool() const; - void setDeferredBindingsPerObject(const QHash<int, QBitArray> &deferredBindingsPerObject); void setBindingPropertyDataPerObject(const QVector<QV4::CompiledData::BindingPropertyData> &propertyData); const QHash<int, QQmlCustomParser*> &customParserCache() const { return customParsers; } QString bindingAsString(const QmlIR::Object *object, int scriptIndex) const; + void addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion); + private: QList<QQmlError> errors; QQmlEnginePrivate *engine; - QQmlCompiledData *compiledData; QQmlTypeData *typeData; + QQmlRefPointer<QQmlTypeNameCache> importCache; QmlIR::Document *document; // index is string index of type name (use obj->inheritedTypeNameIndex) QHash<int, QQmlCustomParser*> customParsers; + + // index in first hash is component index, vector inside contains object indices of objects with id property + QVector<quint32> m_componentRoots; + QQmlPropertyCacheVector m_propertyCaches; }; struct QQmlCompilePass { - virtual ~QQmlCompilePass() {} - QQmlCompilePass(QQmlTypeCompiler *typeCompiler); QString stringAt(int idx) const { return compiler->stringAt(idx); } protected: - void recordError(const QV4::CompiledData::Location &location, const QString &description) const; + void recordError(const QV4::CompiledData::Location &location, const QString &description) const + { compiler->recordError(location, description); } + void recordError(const QQmlCompileError &error) + { compiler->recordError(error); } QQmlTypeCompiler *compiler; }; -class QQmlPropertyCacheCreator : public QQmlCompilePass -{ - Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreator) -public: - QQmlPropertyCacheCreator(QQmlTypeCompiler *typeCompiler); - ~QQmlPropertyCacheCreator(); - - bool buildMetaObjects(); -protected: - bool buildMetaObjectRecursively(int objectIndex, int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding); - bool ensureMetaObject(int objectIndex); - bool createMetaObject(int objectIndex, const QmlIR::Object *obj, QQmlPropertyCache *baseTypeCache); - - QQmlEnginePrivate *enginePrivate; - const QList<QmlIR::Object*> &qmlObjects; - const QQmlImports *imports; - QHash<int, QQmlCompiledData::TypeReference*> *resolvedTypes; - QVector<QByteArray> vmeMetaObjects; - QVector<QQmlPropertyCache*> propertyCaches; -}; - // "Converts" signal expressions to full-fleged function declarations with // parameters taken from the signal declarations // It also updates the QV4::CompiledData::Binding objects to set the property name @@ -176,12 +179,12 @@ private: bool convertSignalHandlerExpressionsToFunctionDeclarations(const QmlIR::Object *obj, const QString &typeName, QQmlPropertyCache *propertyCache); QQmlEnginePrivate *enginePrivate; - const QList<QmlIR::Object*> &qmlObjects; + const QVector<QmlIR::Object*> &qmlObjects; const QQmlImports *imports; const QHash<int, QQmlCustomParser*> &customParsers; - const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes; + const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; const QSet<QString> &illegalNames; - const QVector<QQmlPropertyCache*> &propertyCaches; + const QQmlPropertyCacheVector * const propertyCaches; }; // ### This will go away when the codegen resolves all enums to constant expressions @@ -207,10 +210,10 @@ private: int evaluateEnum(const QString &scope, const QByteArray &enumValue, bool *ok) const; - const QList<QmlIR::Object*> &qmlObjects; - const QVector<QQmlPropertyCache *> propertyCaches; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; const QQmlImports *imports; - QHash<int, QQmlCompiledData::TypeReference *> *resolvedTypes; + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes; }; class QQmlCustomParserScriptIndexer: public QQmlCompilePass @@ -223,7 +226,7 @@ public: private: void scanObjectRecursively(int objectIndex, bool annotateScriptBindings = false); - const QList<QmlIR::Object*> &qmlObjects; + const QVector<QmlIR::Object*> &qmlObjects; const QHash<int, QQmlCustomParser*> &customParsers; }; @@ -235,8 +238,8 @@ public: void annotateBindingsToAliases(); private: - const QList<QmlIR::Object*> &qmlObjects; - const QVector<QQmlPropertyCache *> propertyCaches; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; }; class QQmlScriptStringScanner : public QQmlCompilePass @@ -247,8 +250,8 @@ public: void scan(); private: - const QList<QmlIR::Object*> &qmlObjects; - const QVector<QQmlPropertyCache *> propertyCaches; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; }; class QQmlComponentAndAliasResolver : public QQmlCompilePass @@ -262,64 +265,49 @@ public: protected: void findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache); bool collectIdsAndAliases(int objectIndex); - bool resolveAliases(); + bool resolveAliases(int componentIndex); + void propertyDataForAlias(QmlIR::Alias *alias, int *type, quint32 *propertyFlags); + + enum AliasResolutionResult { + NoAliasResolved, + SomeAliasesResolved, + AllAliasesResolved + }; + + AliasResolutionResult resolveAliasesInObject(int objectIndex, QQmlCompileError *error); QQmlEnginePrivate *enginePrivate; QQmlJS::MemoryPool *pool; - QList<QmlIR::Object*> *qmlObjects; + QVector<QmlIR::Object*> *qmlObjects; const int indexOfRootObject; // indices of the objects that are actually Component {} - QVector<int> componentRoots; - // indices of objects that are the beginning of a new component - // scope. This is sorted and used for binary search. - QVector<quint32> componentBoundaries; - - int _componentIndex; - QHash<int, int> _idToObjectIndex; - QHash<int, int> *_objectIndexToIdInScope; - QList<int> _objectsWithAliases; - - QHash<int, QQmlCompiledData::TypeReference*> *resolvedTypes; - QVector<QQmlPropertyCache *> propertyCaches; - QVector<QByteArray> *vmeMetaObjectData; - QHash<int, int> *objectIndexToIdForRoot; - QHash<int, QHash<int, int> > *objectIndexToIdPerComponent; + QVector<quint32> componentRoots; + + // Deliberate choice of map over hash here to ensure stable generated output. + QMap<int, int> _idToObjectIndex; + QVector<int> _objectsWithAliases; + + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypes; + QQmlPropertyCacheVector propertyCaches; }; -class QQmlPropertyValidator : public QQmlCompilePass +class QQmlDeferredAndCustomParserBindingScanner : public QQmlCompilePass { - Q_DECLARE_TR_FUNCTIONS(QQmlPropertyValidator) public: - QQmlPropertyValidator(QQmlTypeCompiler *typeCompiler); - - bool validate(); + QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler); - const QQmlImports &imports() const; - QQmlEnginePrivate *engine() const { return enginePrivate; } + bool scanObject(); private: - bool validateObject(int objectIndex, const QV4::CompiledData::Binding *instantiatingBinding, bool populatingValueTypeGroupProperty = false) const; - bool validateLiteralBinding(QQmlPropertyCache *propertyCache, QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) const; - bool validateObjectBinding(QQmlPropertyData *property, const QString &propertyName, const QV4::CompiledData::Binding *binding) const; - - bool isComponent(int objectIndex) const { return objectIndexToIdPerComponent.contains(objectIndex); } - - bool canCoerce(int to, QQmlPropertyCache *fromMo) const; + bool scanObject(int objectIndex); - QQmlEnginePrivate *enginePrivate; - const QV4::CompiledData::Unit *qmlUnit; - const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes; + QVector<QmlIR::Object*> *qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; const QHash<int, QQmlCustomParser*> &customParsers; - const QVector<QQmlPropertyCache *> &propertyCaches; - const QHash<int, QHash<int, int> > objectIndexToIdPerComponent; - QHash<int, QBitArray> *customParserBindingsPerObject; - - // collected state variables, essentially write-only - mutable QHash<int, QBitArray> _deferredBindingsPerObject; - mutable bool _seenObjectWithId; - mutable QVector<QV4::CompiledData::BindingPropertyData> _bindingPropertyDataPerObject; + + bool _seenObjectWithId; }; // ### merge with QtQml::JSCodeGen and operate directly on object->functionsAndExpressions once old compiler is gone. @@ -331,16 +319,13 @@ public: bool generateCodeForComponents(); private: - bool compileComponent(int componentRoot, const QHash<int, int> &objectIndexToId); + bool compileComponent(int componentRoot); bool compileJavaScriptCodeInObjectsRecursively(int objectIndex, int scopeObjectIndex); - bool isComponent(int objectIndex) const { return objectIndexToIdPerComponent.contains(objectIndex); } - - const QHash<int, QHash<int, int> > &objectIndexToIdPerComponent; - const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes; + const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypes; const QHash<int, QQmlCustomParser*> &customParsers; - const QList<QmlIR::Object*> &qmlObjects; - const QVector<QQmlPropertyCache *> &propertyCaches; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; QmlIR::JSCodeGen * const v4CodeGen; }; @@ -354,11 +339,11 @@ public: private: void mergeDefaultProperties(int objectIndex); - const QList<QmlIR::Object*> &qmlObjects; - const QVector<QQmlPropertyCache*> &propertyCaches; + const QVector<QmlIR::Object*> &qmlObjects; + const QQmlPropertyCacheVector * const propertyCaches; }; -class QQmlJavaScriptBindingExpressionSimplificationPass : public QQmlCompilePass, public QV4::IR::StmtVisitor +class QQmlJavaScriptBindingExpressionSimplificationPass : public QQmlCompilePass { public: QQmlJavaScriptBindingExpressionSimplificationPass(QQmlTypeCompiler *typeCompiler); @@ -368,12 +353,30 @@ public: private: void reduceTranslationBindings(int objectIndex); - virtual void visitMove(QV4::IR::Move *move); - virtual void visitJump(QV4::IR::Jump *) {} - virtual void visitCJump(QV4::IR::CJump *) { discard(); } - virtual void visitExp(QV4::IR::Exp *) { discard(); } - virtual void visitPhi(QV4::IR::Phi *) {} - virtual void visitRet(QV4::IR::Ret *ret); + void visit(QV4::IR::Stmt *s) + { + switch (s->stmtKind) { + case QV4::IR::Stmt::MoveStmt: + visitMove(s->asMove()); + break; + case QV4::IR::Stmt::RetStmt: + visitRet(s->asRet()); + break; + case QV4::IR::Stmt::CJumpStmt: + discard(); + break; + case QV4::IR::Stmt::ExpStmt: + discard(); + break; + case QV4::IR::Stmt::JumpStmt: + break; + case QV4::IR::Stmt::PhiStmt: + break; + } + } + + void visitMove(QV4::IR::Move *move); + void visitRet(QV4::IR::Ret *ret); void visitFunctionCall(const QString *name, QV4::IR::ExprList *args, QV4::IR::Temp *target); @@ -382,7 +385,7 @@ private: bool simplifyBinding(QV4::IR::Function *function, QmlIR::Binding *binding); bool detectTranslationCallAndConvertBinding(QmlIR::Binding *binding); - const QList<QmlIR::Object*> &qmlObjects; + const QVector<QmlIR::Object*> &qmlObjects; QV4::IR::Module *jsModule; bool _canSimplify; @@ -397,8 +400,7 @@ private: QVector<int> irFunctionsToRemove; }; -class QQmlIRFunctionCleanser : public QQmlCompilePass, public QV4::IR::StmtVisitor, - public QV4::IR::ExprVisitor +class QQmlIRFunctionCleanser : public QQmlCompilePass { public: QQmlIRFunctionCleanser(QQmlTypeCompiler *typeCompiler, const QVector<int> &functionsToRemove); @@ -406,51 +408,13 @@ public: void clean(); private: - virtual void visitClosure(QV4::IR::Closure *closure); - - virtual void visitTemp(QV4::IR::Temp *) {} - virtual void visitArgLocal(QV4::IR::ArgLocal *) {} - virtual void visitMove(QV4::IR::Move *s) { - s->source->accept(this); - s->target->accept(this); - } - - virtual void visitConvert(QV4::IR::Convert *e) { e->expr->accept(this); } - virtual void visitPhi(QV4::IR::Phi *) { } - - virtual void visitExp(QV4::IR::Exp *s) { s->expr->accept(this); } - - virtual void visitJump(QV4::IR::Jump *) {} - virtual void visitCJump(QV4::IR::CJump *s) { s->cond->accept(this); } - virtual void visitRet(QV4::IR::Ret *s) { s->expr->accept(this); } - - virtual void visitConst(QV4::IR::Const *) {} - virtual void visitString(QV4::IR::String *) {} - virtual void visitRegExp(QV4::IR::RegExp *) {} - virtual void visitName(QV4::IR::Name *) {} - virtual void visitUnop(QV4::IR::Unop *e) { e->expr->accept(this); } - virtual void visitBinop(QV4::IR::Binop *e) { e->left->accept(this); e->right->accept(this); } - virtual void visitCall(QV4::IR::Call *e) { - e->base->accept(this); - for (QV4::IR::ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); + visit(s->source); + visit(s->target); } - virtual void visitNew(QV4::IR::New *e) { - e->base->accept(this); - for (QV4::IR::ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - - virtual void visitSubscript(QV4::IR::Subscript *e) { - e->base->accept(this); - e->index->accept(this); - } - - virtual void visitMember(QV4::IR::Member *e) { - e->base->accept(this); - } + void visit(QV4::IR::Stmt *s); + void visit(QV4::IR::Expr *e); private: QV4::IR::Module *module; diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 461ff89550..e0def1021b 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -2026,6 +2026,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, function->maxNumberOfArguments = qMax(_env->maxNumberOfArguments, (int)QV4::Global::ReservedArgumentCount); function->isStrict = _env->isStrict; function->isNamedExpression = _env->isNamedFunctionExpression; + function->isQmlBinding = _env->compilationMode == QmlBinding; AST::SourceLocation loc = ast->firstSourceLocation(); function->line = loc.startLine; @@ -2046,7 +2047,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, } } else { if (!_env->isStrict) { - foreach (const QString &inheritedLocal, inheritedLocals) { + for (const QString &inheritedLocal : qAsConst(inheritedLocals)) { function->LOCAL(inheritedLocal); unsigned tempIndex = entryBlock->newTemp(); Environment::Member member = { Environment::UndefinedMember, @@ -2088,7 +2089,7 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, _function->RECEIVE(it->name.toString()); } - foreach (const Environment::Member &member, _env->members) { + for (const Environment::Member &member : qAsConst(_env->members)) { if (member.function) { const int function = defineFunction(member.function->name.toString(), member.function, member.function->formals, member.function->body ? member.function->body->elements : 0); @@ -2939,7 +2940,7 @@ QList<QQmlError> Codegen::qmlErrors() const qmlErrors.reserve(_errors.size()); QUrl url(_fileNameIsUrl ? QUrl(_module->fileName) : QUrl::fromLocalFile(_module->fileName)); - foreach (const QQmlJS::DiagnosticMessage &msg, _errors) { + for (const QQmlJS::DiagnosticMessage &msg: qAsConst(_errors)) { QQmlError e; e.setUrl(url); e.setLine(msg.loc.startLine); diff --git a/src/qml/compiler/qv4compilationunitmapper.cpp b/src/qml/compiler/qv4compilationunitmapper.cpp new file mode 100644 index 0000000000..2e1213464c --- /dev/null +++ b/src/qml/compiler/qv4compilationunitmapper.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qv4compilationunitmapper_p.h" + +#include "qv4compileddata_p.h" +#include <QFileInfo> +#include <QDateTime> +#include <QCoreApplication> + +QT_BEGIN_NAMESPACE + +using namespace QV4; + +CompilationUnitMapper::CompilationUnitMapper() + : dataPtr(nullptr) +{ + +} + +CompilationUnitMapper::~CompilationUnitMapper() +{ + close(); +} + +bool CompilationUnitMapper::verifyHeader(const CompiledData::Unit *header, const QString &sourcePath, QString *errorString) +{ + if (strncmp(header->magic, CompiledData::magic_str, sizeof(header->magic))) { + *errorString = QStringLiteral("Magic bytes in the header do not match"); + return false; + } + + if (header->version != quint32(QV4_DATA_STRUCTURE_VERSION)) { + *errorString = QString::fromUtf8("V4 data structure version mismatch. Found %1 expected %2").arg(header->version, 0, 16).arg(QV4_DATA_STRUCTURE_VERSION, 0, 16); + return false; + } + + if (header->qtVersion != quint32(QT_VERSION)) { + *errorString = QString::fromUtf8("Qt version mismatch. Found %1 expected %2").arg(header->qtVersion, 0, 16).arg(QT_VERSION, 0, 16); + return false; + } + + { + QFileInfo sourceCode(sourcePath); + QDateTime sourceTimeStamp; + if (sourceCode.exists()) + sourceTimeStamp = sourceCode.lastModified(); + + // Files from the resource system do not have any time stamps, so fall back to the application + // executable. + if (!sourceTimeStamp.isValid()) + sourceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); + + if (sourceTimeStamp.isValid() && sourceTimeStamp.toMSecsSinceEpoch() != header->sourceTimeStamp) { + *errorString = QStringLiteral("QML source file has a different time stamp than cached file."); + return false; + } + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4compilationunitmapper_p.h b/src/qml/compiler/qv4compilationunitmapper_p.h new file mode 100644 index 0000000000..5b6939f1cf --- /dev/null +++ b/src/qml/compiler/qv4compilationunitmapper_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 QV4COMPILATIONUNITMAPPER_H +#define QV4COMPILATIONUNITMAPPER_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 <QFile> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace CompiledData { +struct Unit; +} + +class CompilationUnitMapper +{ +public: + CompilationUnitMapper(); + ~CompilationUnitMapper(); + + CompiledData::Unit *open(const QString &cacheFilePath, const QString &sourcePath, QString *errorString); + void close(); + +private: + static bool verifyHeader(const QV4::CompiledData::Unit *header, const QString &sourcePath, QString *errorString); + +#if defined(Q_OS_UNIX) + size_t length; +#endif + void *dataPtr; +}; + +} + +QT_END_NAMESPACE + +#endif // QV4COMPILATIONUNITMAPPER_H diff --git a/src/qml/compiler/qv4compilationunitmapper_unix.cpp b/src/qml/compiler/qv4compilationunitmapper_unix.cpp new file mode 100644 index 0000000000..1aa3e05f5f --- /dev/null +++ b/src/qml/compiler/qv4compilationunitmapper_unix.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qv4compilationunitmapper_p.h" + +#include <sys/mman.h> +#include <functional> +#include <private/qcore_unix_p.h> +#include <private/qdeferredcleanup_p.h> + +#include "qv4compileddata_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QV4; + +CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, const QString &sourcePath, QString *errorString) +{ + close(); + + int fd = qt_safe_open(QFile::encodeName(cacheFileName).constData(), O_RDONLY); + if (fd == -1) { + *errorString = qt_error_string(errno); + return nullptr; + } + + QDeferredCleanup cleanup([fd]{ + qt_safe_close(fd) ; + }); + + CompiledData::Unit header; + qint64 bytesRead = qt_safe_read(fd, reinterpret_cast<char *>(&header), sizeof(header)); + + if (bytesRead != sizeof(header)) { + *errorString = QStringLiteral("File too small for the header fields"); + return nullptr; + } + + if (!verifyHeader(&header, sourcePath, errorString)) + return nullptr; + + // Data structure and qt version matched, so now we can access the rest of the file safely. + + length = static_cast<size_t>(lseek(fd, 0, SEEK_END)); + + void *ptr = mmap(nullptr, length, PROT_READ, MAP_SHARED, fd, /*offset*/0); + if (ptr == MAP_FAILED) { + *errorString = qt_error_string(errno); + return nullptr; + } + dataPtr = ptr; + + return reinterpret_cast<CompiledData::Unit*>(dataPtr); +} + +void CompilationUnitMapper::close() +{ + if (dataPtr != nullptr) + munmap(dataPtr, length); + dataPtr = nullptr; +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4compilationunitmapper_win.cpp b/src/qml/compiler/qv4compilationunitmapper_win.cpp new file mode 100644 index 0000000000..457b702ac3 --- /dev/null +++ b/src/qml/compiler/qv4compilationunitmapper_win.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qv4compilationunitmapper_p.h" + +#include "qv4compileddata_p.h" +#include <private/qdeferredcleanup_p.h> +#include <QFileInfo> +#include <QDateTime> +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +using namespace QV4; + +CompiledData::Unit *CompilationUnitMapper::open(const QString &cacheFileName, const QString &sourcePath, QString *errorString) +{ + close(); + + // ### TODO: fix up file encoding/normalization/unc handling once QFileSystemEntry + // is exported from QtCore. + HANDLE handle = +#if defined(Q_OS_WINRT) + CreateFile2(reinterpret_cast<const wchar_t*>(cacheFileName.constData()), + GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, + OPEN_EXISTING, nullptr); +#else + CreateFile(reinterpret_cast<const wchar_t*>(cacheFileName.constData()), + GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr); +#endif + if (handle == INVALID_HANDLE_VALUE) { + *errorString = qt_error_string(GetLastError()); + return nullptr; + } + + QDeferredCleanup fileHandleCleanup([handle]{ + CloseHandle(handle); + }); + +#if !defined(Q_OS_WINRT) || _MSC_VER >= 1900 + CompiledData::Unit header; + DWORD bytesRead; + if (!ReadFile(handle, reinterpret_cast<char *>(&header), sizeof(header), &bytesRead, nullptr)) { + *errorString = qt_error_string(GetLastError()); + return nullptr; + } + + if (bytesRead != sizeof(header)) { + *errorString = QStringLiteral("File too small for the header fields"); + return nullptr; + } + + if (!verifyHeader(&header, sourcePath, errorString)) + return nullptr; + + const uint mappingFlags = header.flags & QV4::CompiledData::Unit::ContainsMachineCode + ? PAGE_EXECUTE_READ : PAGE_READONLY; + const uint viewFlags = header.flags & QV4::CompiledData::Unit::ContainsMachineCode + ? (FILE_MAP_READ | FILE_MAP_EXECUTE) : FILE_MAP_READ; + + // Data structure and qt version matched, so now we can access the rest of the file safely. + + HANDLE fileMappingHandle = CreateFileMapping(handle, 0, mappingFlags, 0, 0, 0); + if (!fileMappingHandle) { + *errorString = qt_error_string(GetLastError()); + return nullptr; + } + + QDeferredCleanup mappingCleanup([fileMappingHandle]{ + CloseHandle(fileMappingHandle); + }); + + dataPtr = MapViewOfFile(fileMappingHandle, viewFlags, 0, 0, 0); + if (!dataPtr) { + *errorString = qt_error_string(GetLastError()); + return nullptr; + } + + return reinterpret_cast<CompiledData::Unit*>(dataPtr); +#else + Q_UNUSED(sourcePath); + *errorString = QStringLiteral("Compilation unit mapping not supported on WinRT 8.1"); + return nullptr; +#endif +} + +void CompilationUnitMapper::close() +{ +#if !defined(Q_OS_WINRT) || _MSC_VER >= 1900 + if (dataPtr != nullptr) + UnmapViewOfFile(dataPtr); +#endif + dataPtr = nullptr; +} + +QT_END_NAMESPACE diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index a63f35152a..e815c41a86 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -47,12 +47,30 @@ #include <private/qv4lookup_p.h> #include <private/qv4regexpobject_p.h> #include <private/qqmlpropertycache_p.h> +#include <private/qqmltypeloader_p.h> +#include <private/qqmlengine_p.h> +#include "qv4compilationunitmapper_p.h" +#include <QQmlPropertyMap> +#include <QDateTime> +#include <QSaveFile> +#include <QFile> +#include <QFileInfo> +#include <QScopedValueRollback> +#include <QStandardPaths> +#include <QDir> #endif #include <private/qqmlirbuilder_p.h> #include <QCoreApplication> +#include <QCryptographicHash> #include <algorithm> +#if defined(QT_BUILD_INTERNAL) +#if defined(Q_OS_UNIX) && !defined(QT_NO_DYNAMIC_CAST) +#include <dlfcn.h> +#endif +#endif + QT_BEGIN_NAMESPACE namespace QV4 { @@ -67,11 +85,20 @@ CompilationUnit::CompilationUnit() , runtimeLookups(0) , runtimeRegularExpressions(0) , runtimeClasses(0) + , totalBindingsCount(0) + , totalParserStatusCount(0) + , totalObjectCount(0) + , metaTypeId(-1) + , listMetaTypeId(-1) + , isRegisteredWithEngine(false) {} CompilationUnit::~CompilationUnit() { unlink(); + if (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) + free(const_cast<Unit *>(data)); + data = 0; } QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) @@ -109,7 +136,7 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) for (uint i = 0; i < data->lookupTableSize; ++i) { QV4::Lookup *l = runtimeLookups + i; - Lookup::Type type = Lookup::Type(compiledLookups[i].type_and_flags); + Lookup::Type type = Lookup::Type(uint(compiledLookups[i].type_and_flags)); if (type == CompiledData::Lookup::Type_Getter) l->getter = QV4::Lookup::getterGeneric; else if (type == CompiledData::Lookup::Type_Setter) @@ -144,14 +171,18 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) } } - linkBackendToEngine(engine); - -#if 0 - runtimeFunctionsSortedByAddress.resize(runtimeFunctions.size()); - memcpy(runtimeFunctionsSortedByAddress.data(), runtimeFunctions.data(), runtimeFunctions.size() * sizeof(QV4::Function*)); - std::sort(runtimeFunctionsSortedByAddress.begin(), runtimeFunctionsSortedByAddress.end(), functionSortHelper); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + Value *bigEndianConstants = new Value[data->constantTableSize]; + const LEUInt64 *littleEndianConstants = data->constants(); + for (uint i = 0; i < data->constantTableSize; ++i) + bigEndianConstants[i] = Value::fromReturnedValue(littleEndianConstants[i]); + constants = bigEndianConstants; +#else + constants = reinterpret_cast<const Value*>(data->constants()); #endif + linkBackendToEngine(engine); + if (data->indexOfRootFunction != -1) return runtimeFunctions[data->indexOfRootFunction]; else @@ -162,10 +193,26 @@ void CompilationUnit::unlink() { if (engine) engine->compilationUnits.erase(engine->compilationUnits.find(this)); + + if (isRegisteredWithEngine) { + Q_ASSERT(data && quint32(propertyCaches.count()) > data->indexOfRootObject && propertyCaches.at(data->indexOfRootObject)); + QQmlEnginePrivate *qmlEngine = QQmlEnginePrivate::get(propertyCaches.at(data->indexOfRootObject)->engine); + qmlEngine->unregisterInternalCompositeType(this); + isRegisteredWithEngine = false; + } + + propertyCaches.clear(); + + for (int ii = 0; ii < dependentScripts.count(); ++ii) + dependentScripts.at(ii)->release(); + dependentScripts.clear(); + + importCache = nullptr; + + qDeleteAll(resolvedTypes); + resolvedTypes.clear(); + engine = 0; - if (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) - free(data); - data = 0; free(runtimeStrings); runtimeStrings = 0; delete [] runtimeLookups; @@ -176,6 +223,9 @@ void CompilationUnit::unlink() runtimeClasses = 0; qDeleteAll(runtimeFunctions); runtimeFunctions.clear(); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + delete [] constants; +#endif } void CompilationUnit::markObjects(QV4::ExecutionEngine *e) @@ -189,6 +239,219 @@ void CompilationUnit::markObjects(QV4::ExecutionEngine *e) } } +void CompilationUnit::destroy() +{ + QQmlEngine *qmlEngine = 0; + if (engine && engine->v8Engine) + qmlEngine = engine->v8Engine->engine(); + if (qmlEngine) + QQmlEnginePrivate::deleteInEngineThread(qmlEngine, this); + else + delete this; +} + +IdentifierHash<int> CompilationUnit::namedObjectsPerComponent(int componentObjectIndex) +{ + auto it = namedObjectsPerComponentCache.find(componentObjectIndex); + if (it == namedObjectsPerComponentCache.end()) { + IdentifierHash<int> namedObjectCache(engine); + const CompiledData::Object *component = data->objectAt(componentObjectIndex); + const LEUInt32 *namedObjectIndexPtr = component->namedObjectsInComponentTable(); + for (quint32 i = 0; i < component->nNamedObjectsInComponent; ++i, ++namedObjectIndexPtr) { + const CompiledData::Object *namedObject = data->objectAt(*namedObjectIndexPtr); + namedObjectCache.add(runtimeStrings[namedObject->idNameIndex], namedObject->id); + } + it = namedObjectsPerComponentCache.insert(componentObjectIndex, namedObjectCache); + } + return *it; +} + +void CompilationUnit::finalize(QQmlEnginePrivate *engine) +{ + // Add to type registry of composites + if (propertyCaches.needsVMEMetaObject(data->indexOfRootObject)) + engine->registerInternalCompositeType(this); + else { + const QV4::CompiledData::Object *obj = objectAt(data->indexOfRootObject); + auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + if (typeRef->compilationUnit) { + metaTypeId = typeRef->compilationUnit->metaTypeId; + listMetaTypeId = typeRef->compilationUnit->listMetaTypeId; + } else { + metaTypeId = typeRef->type->typeId(); + listMetaTypeId = typeRef->type->qListTypeId(); + } + } + + // Collect some data for instantiation later. + int bindingCount = 0; + int parserStatusCount = 0; + int objectCount = 0; + for (quint32 i = 0; i < data->nObjects; ++i) { + const QV4::CompiledData::Object *obj = data->objectAt(i); + bindingCount += obj->nBindings; + if (auto *typeRef = resolvedTypes.value(obj->inheritedTypeNameIndex)) { + if (QQmlType *qmlType = typeRef->type) { + if (qmlType->parserStatusCast() != -1) + ++parserStatusCount; + } + ++objectCount; + if (typeRef->compilationUnit) { + bindingCount += typeRef->compilationUnit->totalBindingsCount; + parserStatusCount += typeRef->compilationUnit->totalParserStatusCount; + objectCount += typeRef->compilationUnit->totalObjectCount; + } + } + } + + totalBindingsCount = bindingCount; + totalParserStatusCount = parserStatusCount; + totalObjectCount = objectCount; +} + +bool CompilationUnit::verifyChecksum(QQmlEngine *engine, + const ResolvedTypeReferenceMap &dependentTypes) const +{ + if (dependentTypes.isEmpty()) { + for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { + if (data->dependencyMD5Checksum[i] != 0) + return false; + } + return true; + } + QCryptographicHash hash(QCryptographicHash::Md5); + if (!dependentTypes.addToHash(&hash, engine)) + return false; + QByteArray checksum = hash.result(); + Q_ASSERT(checksum.size() == sizeof(data->dependencyMD5Checksum)); + return memcmp(data->dependencyMD5Checksum, checksum.constData(), + sizeof(data->dependencyMD5Checksum)) == 0; +} + +static QString cacheFilePath(const QUrl &url) +{ + const QString localSourcePath = QQmlFile::urlToLocalFileOrQrc(url); + const QString localCachePath = localSourcePath + QLatin1Char('c'); + if (QFileInfo(QFileInfo(localSourcePath).dir().absolutePath()).isWritable()) + return localCachePath; + QCryptographicHash fileNameHash(QCryptographicHash::Sha1); + fileNameHash.addData(localSourcePath.toUtf8()); + QString directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/qmlcache/"); + QDir::root().mkpath(directory); + return directory + QString::fromUtf8(fileNameHash.result().toHex()) + QLatin1Char('.') + QFileInfo(localCachePath).completeSuffix(); +} + +bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) +{ + errorString->clear(); + + if (data->sourceTimeStamp == 0) { + *errorString = QStringLiteral("Missing time stamp for source file"); + return false; + } + + if (!QQmlFile::isLocalFile(unitUrl)) { + *errorString = QStringLiteral("File has to be a local file."); + return false; + } + + // Foo.qml -> Foo.qmlc + QSaveFile cacheFile(cacheFilePath(unitUrl)); + if (!cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + *errorString = cacheFile.errorString(); + return false; + } + + QByteArray modifiedUnit; + modifiedUnit.resize(data->unitSize); + memcpy(modifiedUnit.data(), data, data->unitSize); + const char *dataPtr = modifiedUnit.data(); + Unit *unitPtr; + memcpy(&unitPtr, &dataPtr, sizeof(unitPtr)); + unitPtr->flags |= Unit::StaticData; + + prepareCodeOffsetsForDiskStorage(unitPtr); + + qint64 headerWritten = cacheFile.write(modifiedUnit); + if (headerWritten != modifiedUnit.size()) { + *errorString = cacheFile.errorString(); + return false; + } + + if (!saveCodeToDisk(&cacheFile, unitPtr, errorString)) + return false; + + if (!cacheFile.commit()) { + *errorString = cacheFile.errorString(); + return false; + } + + return true; +} + +bool CompilationUnit::loadFromDisk(const QUrl &url, EvalISelFactory *iselFactory, QString *errorString) +{ + if (!QQmlFile::isLocalFile(url)) { + *errorString = QStringLiteral("File has to be a local file."); + return false; + } + + const QString sourcePath = url.toLocalFile(); + QScopedPointer<CompilationUnitMapper> cacheFile(new CompilationUnitMapper()); + + CompiledData::Unit *mappedUnit = cacheFile->open(cacheFilePath(url), sourcePath, errorString); + if (!mappedUnit) + return false; + + const Unit * const oldDataPtr = (data && !(data->flags & QV4::CompiledData::Unit::StaticData)) ? data : nullptr; + QScopedValueRollback<const Unit *> dataPtrChange(data, mappedUnit); + + { + const QString foundArchitecture = stringAt(data->architectureIndex); + const QString expectedArchitecture = QSysInfo::buildAbi(); + if (foundArchitecture != expectedArchitecture) { + *errorString = QString::fromUtf8("Architecture mismatch. Found %1 expected %2").arg(foundArchitecture).arg(expectedArchitecture); + return false; + } + } + + { + const QString foundCodeGenerator = stringAt(data->codeGeneratorIndex); + const QString expectedCodeGenerator = iselFactory->codeGeneratorName; + if (foundCodeGenerator != expectedCodeGenerator) { + *errorString = QString::fromUtf8("Code generator mismatch. Found code generated by %1 but expected %2").arg(foundCodeGenerator).arg(expectedCodeGenerator); + return false; + } + } + + if (!memoryMapCode(errorString)) + return false; + + dataPtrChange.commit(); + free(const_cast<Unit*>(oldDataPtr)); + backingFile.reset(cacheFile.take()); + return true; +} + +void CompilationUnit::prepareCodeOffsetsForDiskStorage(Unit *unit) +{ + Q_UNUSED(unit); +} + +bool CompilationUnit::saveCodeToDisk(QIODevice *device, const Unit *unit, QString *errorString) +{ + Q_UNUSED(device); + Q_UNUSED(unit); + *errorString = QStringLiteral("Saving code to disk is not supported in this configuration"); + return false; +} + +bool CompilationUnit::memoryMapCode(QString *errorString) +{ + *errorString = QStringLiteral("Missing code mapping backend"); + return false; +} #endif // V4_BOOTSTRAP Unit *CompilationUnit::createUnitData(QmlIR::Document *irDocument) @@ -205,7 +468,7 @@ QString Binding::valueAsString(const Unit *unit) const case Type_Boolean: return value.b ? QStringLiteral("true") : QStringLiteral("false"); case Type_Number: - return QString::number(value.d); + return QString::number(valueAsNumber()); case Type_Invalid: return QString(); #ifdef QT_NO_TRANSLATION @@ -287,6 +550,130 @@ QString Binding::valueAsScriptString(const Unit *unit) const return valueAsString(unit); } +#ifndef V4_BOOTSTRAP +/*! +Returns the property cache, if one alread exists. The cache is not referenced. +*/ +QQmlPropertyCache *ResolvedTypeReference::propertyCache() const +{ + if (type) + return typePropertyCache; + else + return compilationUnit->rootPropertyCache(); +} + +/*! +Returns the property cache, creating one if it doesn't already exist. The cache is not referenced. +*/ +QQmlPropertyCache *ResolvedTypeReference::createPropertyCache(QQmlEngine *engine) +{ + if (typePropertyCache) { + return typePropertyCache; + } else if (type) { + typePropertyCache = QQmlEnginePrivate::get(engine)->cache(type->metaObject()); + return typePropertyCache; + } else { + return compilationUnit->rootPropertyCache(); + } +} + +bool ResolvedTypeReference::addToHash(QCryptographicHash *hash, QQmlEngine *engine) +{ + if (type) { + bool ok = false; + hash->addData(createPropertyCache(engine)->checksum(&ok)); + return ok; + } + hash->addData(compilationUnit->data->md5Checksum, sizeof(compilationUnit->data->md5Checksum)); + return true; +} + +template <typename T> +bool qtTypeInherits(const QMetaObject *mo) { + while (mo) { + if (mo == &T::staticMetaObject) + return true; + mo = mo->superClass(); + } + return false; +} + +void ResolvedTypeReference::doDynamicTypeCheck() +{ + const QMetaObject *mo = 0; + if (typePropertyCache) + mo = typePropertyCache->firstCppMetaObject(); + else if (type) + mo = type->metaObject(); + else if (compilationUnit) + mo = compilationUnit->rootPropertyCache()->firstCppMetaObject(); + isFullyDynamicType = qtTypeInherits<QQmlPropertyMap>(mo); +} + +#if defined(QT_BUILD_INTERNAL) + +static QByteArray ownLibraryChecksum() +{ + static QByteArray libraryChecksum; + static bool checksumInitialized = false; + if (checksumInitialized) + return libraryChecksum; + checksumInitialized = true; +#if defined(Q_OS_UNIX) && !defined(QT_NO_DYNAMIC_CAST) + Dl_info libInfo; + if (dladdr(reinterpret_cast<const void *>(&ownLibraryChecksum), &libInfo) != 0) { + QFile library(QFile::decodeName(libInfo.dli_fname)); + if (library.open(QIODevice::ReadOnly)) { + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(&library); + libraryChecksum = hash.result(); + } + } +#else + // Not implemented. +#endif + return libraryChecksum; +} + +#endif + +bool ResolvedTypeReferenceMap::addToHash(QCryptographicHash *hash, QQmlEngine *engine) const +{ + for (auto it = constBegin(), end = constEnd(); it != end; ++it) { + if (!it.value()->addToHash(hash, engine)) + return false; + } + + // This is a bit of a hack to make development easier. When hacking on the code generator + // the cache files may end up being re-used. To avoid that we also add the checksum of + // the QtQml library. +#if defined(QT_BUILD_INTERNAL) + hash->addData(ownLibraryChecksum()); +#endif + + return true; +} + +#endif + +void Unit::generateChecksum() +{ +#ifndef V4_BOOTSTRAP + QCryptographicHash hash(QCryptographicHash::Md5); + + const int checksummableDataOffset = qOffsetOf(QV4::CompiledData::Unit, md5Checksum) + sizeof(md5Checksum); + + const char *dataPtr = reinterpret_cast<const char *>(this) + checksummableDataOffset; + hash.addData(dataPtr, unitSize - checksummableDataOffset); + + QByteArray checksum = hash.result(); + Q_ASSERT(checksum.size() == sizeof(md5Checksum)); + memcpy(md5Checksum, checksum.constData(), sizeof(md5Checksum)); +#else + memset(md5Checksum, 0, sizeof(md5Checksum)); +#endif +} + } } diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 8c617875e0..90cbe04505 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -60,11 +60,26 @@ #include <private/qv4executableallocator_p.h> #include <private/qqmlrefcount_p.h> #include <private/qqmlnullablevalue_p.h> +#include <private/qv4identifier_p.h> +#include <private/qflagpointer_p.h> +#include <private/qjson_p.h> +#ifndef V4_BOOTSTRAP +#include <private/qqmltypenamecache_p.h> +#include <private/qqmlpropertycache_p.h> +#endif QT_BEGIN_NAMESPACE +// Bump this whenever the compiler data structures change in an incompatible way. +#define QV4_DATA_STRUCTURE_VERSION 0x07 + +class QIODevice; class QQmlPropertyCache; class QQmlPropertyData; +class QQmlTypeNameCache; +class QQmlScriptData; +class QQmlType; +class QQmlEngine; namespace QmlIR { struct Document; @@ -76,25 +91,49 @@ struct Function; } struct Function; +class EvalISelFactory; +class CompilationUnitMapper; namespace CompiledData { +typedef QJsonPrivate::q_littleendian<qint16> LEInt16; +typedef QJsonPrivate::q_littleendian<quint16> LEUInt16; +typedef QJsonPrivate::q_littleendian<quint32> LEUInt32; +typedef QJsonPrivate::q_littleendian<qint32> LEInt32; +typedef QJsonPrivate::q_littleendian<quint64> LEUInt64; +typedef QJsonPrivate::q_littleendian<qint64> LEInt64; + struct String; struct Function; struct Lookup; struct RegExp; struct Unit; +template <typename ItemType, typename Container, const ItemType *(Container::*IndexedGetter)(int index) const> +struct TableIterator +{ + TableIterator(const Container *container, int index) : container(container), index(index) {} + const Container *container; + int index; + + const ItemType *operator->() { return (container->*IndexedGetter)(index); } + void operator++() { ++index; } + bool operator==(const TableIterator &rhs) const { return index == rhs.index; } + bool operator!=(const TableIterator &rhs) const { return index != rhs.index; } +}; + #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) #pragma pack(push, 1) #endif struct Location { - qint32 line; - qint32 column; + union { + QJsonPrivate::qle_bitfield<0, 20> line; + QJsonPrivate::qle_bitfield<20, 12> column; + }; - Location(): line(-1), column(-1) {} + Location() { line = 0; column = 0; } inline bool operator<(const Location &other) const { return line < other.line || @@ -104,20 +143,22 @@ struct Location struct RegExp { - enum Flags { + enum Flags : unsigned int { RegExp_Global = 0x01, RegExp_IgnoreCase = 0x02, RegExp_Multiline = 0x04 }; - quint32 flags; - quint32 stringIndex; + union { + QJsonPrivate::qle_bitfield<0, 4> flags; + QJsonPrivate::qle_bitfield<4, 28> stringIndex; + }; - static int calculateSize() { return sizeof(RegExp); } + RegExp() { flags = 0; stringIndex = 0; } }; struct Lookup { - enum Type { + enum Type : unsigned int { Type_Getter = 0x0, Type_Setter = 0x1, Type_GlobalGetter = 2, @@ -125,21 +166,27 @@ struct Lookup Type_IndexedSetter = 4 }; - quint32 type_and_flags; - quint32 nameIndex; + union { + QJsonPrivate::qle_bitfield<0, 4> type_and_flags; + QJsonPrivate::qle_bitfield<4, 28> nameIndex; + }; - static int calculateSize() { return sizeof(Lookup); } + Lookup() { type_and_flags = 0; nameIndex = 0; } }; struct JSClassMember { - uint nameOffset : 31; - uint isAccessor : 1; + union { + QJsonPrivate::qle_bitfield<0, 31> nameOffset; + QJsonPrivate::qle_bitfield<31, 1> isAccessor; + }; + + JSClassMember() { nameOffset = 0; isAccessor = 0; } }; struct JSClass { - uint nMembers; + LEUInt32 nMembers; // JSClassMember[nMembers] static int calculateSize(int nMembers) { return (sizeof(JSClass) + nMembers * sizeof(JSClassMember) + 7) & ~7; } @@ -147,8 +194,7 @@ struct JSClass struct String { - quint32 flags; // isArrayIndex - qint32 size; + LEInt32 size; // uint16 strdata[] static int calculateSize(const QString &str) { @@ -156,9 +202,11 @@ struct String } }; +// Function is aligned on an 8-byte boundary to make sure there are no bus errors or penalties +// for unaligned access. The ordering of the fields is also from largest to smallest. struct Function { - enum Flags { + enum Flags : unsigned int { HasDirectEval = 0x1, UsesArgumentsObject = 0x2, IsStrict = 0x4, @@ -166,24 +214,26 @@ struct Function HasCatchOrWith = 0x10 }; - quint32 index; // in CompilationUnit's function table - quint32 nameIndex; - qint64 flags; - quint32 nFormals; - quint32 formalsOffset; - quint32 nLocals; - quint32 localsOffset; - quint32 nInnerFunctions; - quint32 innerFunctionsOffset; + // Absolute offset into file where the code for this function is located. Only used when the function + // is serialized. + LEUInt64 codeOffset; + LEUInt64 codeSize; + + LEUInt32 nameIndex; + LEUInt32 nFormals; + LEUInt32 formalsOffset; + LEUInt32 nLocals; + LEUInt32 localsOffset; + LEUInt32 nInnerFunctions; Location location; // Qml Extensions Begin - quint32 nDependingIdObjects; - quint32 dependingIdObjectsOffset; // Array of resolved ID objects - quint32 nDependingContextProperties; - quint32 dependingContextPropertiesOffset; // Array of int pairs (property index and notify index) - quint32 nDependingScopeProperties; - quint32 dependingScopePropertiesOffset; // Array of int pairs (property index and notify index) + LEUInt32 nDependingIdObjects; + LEUInt32 dependingIdObjectsOffset; // Array of resolved ID objects + LEUInt32 nDependingContextProperties; + LEUInt32 dependingContextPropertiesOffset; // Array of int pairs (property index and notify index) + LEUInt32 nDependingScopeProperties; + LEUInt32 dependingScopePropertiesOffset; // Array of int pairs (property index and notify index) // Qml Extensions End // quint32 formalsIndex[nFormals] @@ -191,11 +241,19 @@ struct Function // quint32 offsetForInnerFunctions[nInnerFunctions] // Function[nInnerFunctions] - const quint32 *formalsTable() const { return reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(this) + formalsOffset); } - const quint32 *localsTable() const { return reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(this) + localsOffset); } - const quint32 *qmlIdObjectDependencyTable() const { return reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(this) + dependingIdObjectsOffset); } - const quint32 *qmlContextPropertiesDependencyTable() const { return reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(this) + dependingContextPropertiesOffset); } - const quint32 *qmlScopePropertiesDependencyTable() const { return reinterpret_cast<const quint32 *>(reinterpret_cast<const char *>(this) + dependingScopePropertiesOffset); } + // Keep all unaligned data at the end + quint8 flags; + + const LEUInt32 *formalsTable() const { return reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + formalsOffset); } + const LEUInt32 *localsTable() const { return reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + localsOffset); } + const LEUInt32 *qmlIdObjectDependencyTable() const { return reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + dependingIdObjectsOffset); } + const LEUInt32 *qmlContextPropertiesDependencyTable() const { return reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + dependingContextPropertiesOffset); } + const LEUInt32 *qmlScopePropertiesDependencyTable() const { return reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + dependingScopePropertiesOffset); } + + // --- QQmlPropertyCacheCreator interface + const LEUInt32 *formalsBegin() const { return formalsTable(); } + const LEUInt32 *formalsEnd() const { return formalsTable() + nFormals; } + // --- inline bool hasQmlDependencies() const { return nDependingIdObjects > 0 || nDependingContextProperties > 0 || nDependingScopeProperties > 0; } @@ -207,15 +265,15 @@ struct Function // Qml data structures struct Q_QML_EXPORT TranslationData { - quint32 commentIndex; - int number; + LEUInt32 commentIndex; + LEInt32 number; }; struct Q_QML_PRIVATE_EXPORT Binding { - quint32 propertyNameIndex; + LEUInt32 propertyNameIndex; - enum ValueType { + enum ValueType : unsigned int { Type_Invalid, Type_Boolean, Type_Number, @@ -228,26 +286,30 @@ struct Q_QML_PRIVATE_EXPORT Binding Type_GroupProperty }; - enum Flags { + enum Flags : unsigned int { IsSignalHandlerExpression = 0x1, IsSignalHandlerObject = 0x2, IsOnAssignment = 0x4, InitializerForReadOnlyDeclaration = 0x8, IsResolvedEnum = 0x10, IsListItem = 0x20, - IsBindingToAlias = 0x40 + IsBindingToAlias = 0x40, + IsDeferredBinding = 0x80, + IsCustomParserBinding = 0x100, }; - quint32 flags : 16; - quint32 type : 16; + union { + QJsonPrivate::qle_bitfield<0, 16> flags; + QJsonPrivate::qle_bitfield<16, 16> type; + }; union { bool b; - double d; - quint32 compiledScriptIndex; // used when Type_Script - quint32 objectIndex; + quint64 doubleValue; // do not access directly, needs endian protected access + LEUInt32 compiledScriptIndex; // used when Type_Script + LEUInt32 objectIndex; TranslationData translationData; // used when Type_Translation } value; - quint32 stringIndex; // Set for Type_String, Type_Translation and Type_Script (the latter because of script strings) + LEUInt32 stringIndex; // Set for Type_String, Type_Translation and Type_Script (the latter because of script strings) Location location; Location valueLocation; @@ -307,11 +369,20 @@ struct Q_QML_PRIVATE_EXPORT Binding QString valueAsScriptString(const Unit *unit) const; double valueAsNumber() const { - if (type == Type_Number) - return value.d; - return 0.0; - + if (type != Type_Number) + return 0.0; + quint64 intval = qFromLittleEndian<quint64>(value.doubleValue); + double d; + memcpy(&d, &intval, sizeof(double)); + return d; } + void setNumberValueInternal(double d) + { + quint64 intval; + memcpy(&intval, &d, sizeof(double)); + value.doubleValue = qToLittleEndian<quint64>(intval); + } + bool valueAsBoolean() const { if (type == Type_Boolean) @@ -323,17 +394,16 @@ struct Q_QML_PRIVATE_EXPORT Binding struct Parameter { - quint32 nameIndex; - quint32 type; - quint32 customTypeNameIndex; - quint32 reserved; + LEUInt32 nameIndex; + LEUInt32 type; + LEUInt32 customTypeNameIndex; Location location; }; struct Signal { - quint32 nameIndex; - quint32 nParameters; + LEUInt32 nameIndex; + LEUInt32 nParameters; Location location; // Parameter parameters[1]; @@ -346,47 +416,95 @@ struct Signal + nParameters * sizeof(Parameter) + 7) & ~0x7; } + + // --- QQmlPropertyCacheCceatorInterface + const Parameter *parametersBegin() const { return parameterAt(0); } + const Parameter *parametersEnd() const { return parameterAt(nParameters); } + int parameterCount() const { return nParameters; } + // --- }; struct Property { - enum Type { Var = 0, Variant, Int, Bool, Real, String, Url, Color, + enum Type : unsigned int { Var = 0, Variant, Int, Bool, Real, String, Url, Color, Font, Time, Date, DateTime, Rect, Point, Size, Vector2D, Vector3D, Vector4D, Matrix4x4, Quaternion, - Alias, Custom, CustomList }; + Custom, CustomList }; - enum Flags { + enum Flags : unsigned int { IsReadOnly = 0x1 }; - quint32 nameIndex; - quint32 type; + LEUInt32 nameIndex; union { - quint32 customTypeNameIndex; // If type >= Custom - quint32 aliasIdValueIndex; // If type == Alias + QJsonPrivate::qle_bitfield<0, 31> type; + QJsonPrivate::qle_bitfield<31, 1> flags; // readonly }; - quint32 aliasPropertyValueIndex; - quint32 flags; // readonly + LEUInt32 customTypeNameIndex; // If type >= Custom Location location; - Location aliasLocation; // If type == Alias +}; + +struct Alias { + enum Flags : unsigned int { + IsReadOnly = 0x1, + Resolved = 0x2, + AliasPointsToPointerObject = 0x4 + }; + union { + QJsonPrivate::qle_bitfield<0, 29> nameIndex; + QJsonPrivate::qle_bitfield<29, 3> flags; + }; + union { + LEUInt32 idIndex; // string index + QJsonPrivate::qle_bitfield<0, 31> targetObjectId; // object id index (in QQmlContextData::idValues) + QJsonPrivate::qle_bitfield<31, 1> aliasToLocalAlias; + }; + union { + LEUInt32 propertyNameIndex; // string index + LEInt32 encodedMetaPropertyIndex; + LEUInt32 localAliasIndex; // index in list of aliases local to the object (if targetObjectId == objectId) + }; + Location location; + Location referenceLocation; + + bool isObjectAlias() const { + Q_ASSERT(flags & Resolved); + return encodedMetaPropertyIndex == -1; + } }; struct Object { + enum Flags : unsigned int { + NoFlag = 0x0, + IsComponent = 0x1, // object was identified to be an explicit or implicit component boundary + HasDeferredBindings = 0x2, // any of the bindings are deferred + HasCustomParserBindings = 0x4 + }; + // Depending on the use, this may be the type name to instantiate before instantiating this // object. For grouped properties the type name will be empty and for attached properties // it will be the name of the attached type. - quint32 inheritedTypeNameIndex; - quint32 idIndex; - qint32 indexOfDefaultProperty; // -1 means no default property declared in this object - quint32 nFunctions; - quint32 offsetToFunctions; - quint32 nProperties; - quint32 offsetToProperties; - quint32 nSignals; - quint32 offsetToSignals; // which in turn will be a table with offsets to variable-sized Signal objects - quint32 nBindings; - quint32 offsetToBindings; + LEUInt32 inheritedTypeNameIndex; + LEUInt32 idNameIndex; + union { + QJsonPrivate::qle_bitfield<0, 15> flags; + QJsonPrivate::qle_bitfield<15, 1> defaultPropertyIsAlias; + QJsonPrivate::qle_signedbitfield<16, 16> id; + }; + LEInt32 indexOfDefaultPropertyOrAlias; // -1 means no default property declared in this object + LEUInt32 nFunctions; + LEUInt32 offsetToFunctions; + LEUInt32 nProperties; + LEUInt32 offsetToProperties; + LEUInt32 nAliases; + LEUInt32 offsetToAliases; + LEUInt32 nSignals; + LEUInt32 offsetToSignals; // which in turn will be a table with offsets to variable-sized Signal objects + LEUInt32 nBindings; + LEUInt32 offsetToBindings; + LEUInt32 nNamedObjectsInComponent; + LEUInt32 offsetToNamedObjectsInComponent; Location location; Location locationOfIdProperty; // Function[] @@ -394,20 +512,22 @@ struct Object // Signal[] // Binding[] - static int calculateSizeExcludingSignals(int nFunctions, int nProperties, int nSignals, int nBindings) + static int calculateSizeExcludingSignals(int nFunctions, int nProperties, int nAliases, int nSignals, int nBindings, int nNamedObjectsInComponent) { return ( sizeof(Object) + nFunctions * sizeof(quint32) + nProperties * sizeof(Property) + + nAliases * sizeof(Alias) + nSignals * sizeof(quint32) + nBindings * sizeof(Binding) + + nNamedObjectsInComponent * sizeof(int) + 0x7 ) & ~0x7; } - const quint32 *functionOffsetTable() const + const LEUInt32 *functionOffsetTable() const { - return reinterpret_cast<const quint32*>(reinterpret_cast<const char *>(this) + offsetToFunctions); + return reinterpret_cast<const LEUInt32*>(reinterpret_cast<const char *>(this) + offsetToFunctions); } const Property *propertyTable() const @@ -415,6 +535,11 @@ struct Object return reinterpret_cast<const Property*>(reinterpret_cast<const char *>(this) + offsetToProperties); } + const Alias *aliasTable() const + { + return reinterpret_cast<const Alias*>(reinterpret_cast<const char *>(this) + offsetToAliases); + } + const Binding *bindingTable() const { return reinterpret_cast<const Binding*>(reinterpret_cast<const char *>(this) + offsetToBindings); @@ -422,78 +547,116 @@ struct Object const Signal *signalAt(int idx) const { - const uint *offsetTable = reinterpret_cast<const uint*>((reinterpret_cast<const char *>(this)) + offsetToSignals); - const uint offset = offsetTable[idx]; + const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToSignals); + const LEUInt32 offset = offsetTable[idx]; return reinterpret_cast<const Signal*>(reinterpret_cast<const char*>(this) + offset); } + + const LEUInt32 *namedObjectsInComponentTable() const + { + return reinterpret_cast<const LEUInt32*>(reinterpret_cast<const char *>(this) + offsetToNamedObjectsInComponent); + } + + // --- QQmlPropertyCacheCreator interface + int propertyCount() const { return nProperties; } + int aliasCount() const { return nAliases; } + int signalCount() const { return nSignals; } + int functionCount() const { return nFunctions; } + + const Binding *bindingsBegin() const { return bindingTable(); } + const Binding *bindingsEnd() const { return bindingTable() + nBindings; } + + const Property *propertiesBegin() const { return propertyTable(); } + const Property *propertiesEnd() const { return propertyTable() + nProperties; } + + const Alias *aliasesBegin() const { return aliasTable(); } + const Alias *aliasesEnd() const { return aliasTable() + nAliases; } + + typedef TableIterator<Signal, Object, &Object::signalAt> SignalIterator; + SignalIterator signalsBegin() const { return SignalIterator(this, 0); } + SignalIterator signalsEnd() const { return SignalIterator(this, nSignals); } + + int namedObjectsInComponentCount() const { return nNamedObjectsInComponent; } + // --- }; struct Import { - enum ImportType { + enum ImportType : unsigned int { ImportLibrary = 0x1, ImportFile = 0x2, ImportScript = 0x3 }; - quint32 type; + LEUInt32 type; - quint32 uriIndex; - quint32 qualifierIndex; + LEUInt32 uriIndex; + LEUInt32 qualifierIndex; - qint32 majorVersion; - qint32 minorVersion; + LEInt32 majorVersion; + LEInt32 minorVersion; Location location; - Import(): type(0), uriIndex(0), qualifierIndex(0), majorVersion(0), minorVersion(0) {} + Import() { type = 0; uriIndex = 0; qualifierIndex = 0; majorVersion = 0; minorVersion = 0; } }; static const char magic_str[] = "qv4cdata"; struct Unit { + // DO NOT CHANGE THESE FIELDS EVER char magic[8]; - qint16 architecture; - qint16 version; - quint32 unitSize; // Size of the Unit and any depending data. + LEUInt32 version; + LEUInt32 qtVersion; + LEInt64 sourceTimeStamp; + LEUInt32 unitSize; // Size of the Unit and any depending data. + // END DO NOT CHANGE THESE FIELDS EVER - enum { + char md5Checksum[16]; // checksum of all bytes following this field. + void generateChecksum(); + + LEUInt32 architectureIndex; // string index to QSysInfo::buildAbi() + LEUInt32 codeGeneratorIndex; + char dependencyMD5Checksum[16]; + + enum : unsigned int { IsJavascript = 0x1, IsQml = 0x2, StaticData = 0x4, // Unit data persistent in memory? IsSingleton = 0x8, - IsSharedLibrary = 0x10 // .pragma shared? + IsSharedLibrary = 0x10, // .pragma shared? + ContainsMachineCode = 0x20 // used to determine if we need to mmap with execute permissions }; - quint32 flags; - uint stringTableSize; - uint offsetToStringTable; - uint functionTableSize; - uint offsetToFunctionTable; - uint lookupTableSize; - uint offsetToLookupTable; - uint regexpTableSize; - uint offsetToRegexpTable; - uint constantTableSize; - uint offsetToConstantTable; - uint jsClassTableSize; - uint offsetToJSClassTable; - qint32 indexOfRootFunction; - quint32 sourceFileIndex; + LEUInt32 flags; + LEUInt32 stringTableSize; + LEUInt32 offsetToStringTable; + LEUInt32 functionTableSize; + LEUInt32 offsetToFunctionTable; + LEUInt32 lookupTableSize; + LEUInt32 offsetToLookupTable; + LEUInt32 regexpTableSize; + LEUInt32 offsetToRegexpTable; + LEUInt32 constantTableSize; + LEUInt32 offsetToConstantTable; + LEUInt32 jsClassTableSize; + LEUInt32 offsetToJSClassTable; + LEInt32 indexOfRootFunction; + LEUInt32 sourceFileIndex; /* QML specific fields */ - quint32 nImports; - quint32 offsetToImports; - quint32 nObjects; - quint32 offsetToObjects; - quint32 indexOfRootObject; + LEUInt32 nImports; + LEUInt32 offsetToImports; + LEUInt32 nObjects; + LEUInt32 offsetToObjects; + LEUInt32 indexOfRootObject; const Import *importAt(int idx) const { return reinterpret_cast<const Import*>((reinterpret_cast<const char *>(this)) + offsetToImports + idx * sizeof(Import)); } const Object *objectAt(int idx) const { - const uint *offsetTable = reinterpret_cast<const uint*>((reinterpret_cast<const char *>(this)) + offsetToObjects); - const uint offset = offsetTable[idx]; + const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToObjects); + const LEUInt32 offset = offsetTable[idx]; return reinterpret_cast<const Object*>(reinterpret_cast<const char*>(this) + offset); } @@ -503,22 +666,33 @@ struct Unit /* end QML specific fields*/ QString stringAt(int idx) const { - const uint *offsetTable = reinterpret_cast<const uint*>((reinterpret_cast<const char *>(this)) + offsetToStringTable); - const uint offset = offsetTable[idx]; + const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToStringTable); + const LEUInt32 offset = offsetTable[idx]; const String *str = reinterpret_cast<const String*>(reinterpret_cast<const char *>(this) + offset); if (str->size == 0) return QString(); +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const QChar *characters = reinterpret_cast<const QChar *>(str + 1); - if (flags & StaticData) - return QString::fromRawData(characters, str->size); + // Too risky to do this while we unmap disk backed compilation but keep pointers to string + // data in the identifier tables. + // if (flags & StaticData) + // return QString::fromRawData(characters, str->size); return QString(characters, str->size); +#else + const LEUInt16 *characters = reinterpret_cast<const LEUInt16 *>(str + 1); + QString qstr(str->size, Qt::Uninitialized); + QChar *ch = qstr.data(); + for (int i = 0; i < str->size; ++i) + ch[i] = QChar(characters[i]); + return qstr; +#endif } - const uint *functionOffsetTable() const { return reinterpret_cast<const uint*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); } + const LEUInt32 *functionOffsetTable() const { return reinterpret_cast<const LEUInt32*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); } const Function *functionAt(int idx) const { - const uint *offsetTable = functionOffsetTable(); - const uint offset = offsetTable[idx]; + const LEUInt32 *offsetTable = functionOffsetTable(); + const LEUInt32 offset = offsetTable[idx]; return reinterpret_cast<const Function*>(reinterpret_cast<const char *>(this) + offset); } @@ -526,27 +700,18 @@ struct Unit const RegExp *regexpAt(int index) const { return reinterpret_cast<const RegExp*>(reinterpret_cast<const char *>(this) + offsetToRegexpTable + index * sizeof(RegExp)); } - const QV4::Value *constants() const { - return reinterpret_cast<const QV4::Value*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); + const LEUInt64 *constants() const { + return reinterpret_cast<const LEUInt64*>(reinterpret_cast<const char *>(this) + offsetToConstantTable); } const JSClassMember *jsClassAt(int idx, int *nMembers) const { - const uint *offsetTable = reinterpret_cast<const uint *>(reinterpret_cast<const char *>(this) + offsetToJSClassTable); - const uint offset = offsetTable[idx]; + const LEUInt32 *offsetTable = reinterpret_cast<const LEUInt32 *>(reinterpret_cast<const char *>(this) + offsetToJSClassTable); + const LEUInt32 offset = offsetTable[idx]; const char *ptr = reinterpret_cast<const char *>(this) + offset; const JSClass *klass = reinterpret_cast<const JSClass *>(ptr); *nMembers = klass->nMembers; return reinterpret_cast<const JSClassMember*>(ptr + sizeof(JSClass)); } - - static int calculateSize(uint nFunctions, uint nRegExps, uint nConstants, - uint nLookups, uint nClasses) { - return (sizeof(Unit) - + (nFunctions + nClasses) * sizeof(uint) - + nRegExps * RegExp::calculateSize() - + nConstants * sizeof(QV4::ReturnedValue) - + nLookups * Lookup::calculateSize() - + 7) & ~7; } }; #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) @@ -565,7 +730,7 @@ struct TypeReference bool errorWhenNotFound: 1; }; -// map from name index to location of first use +// Map from name index to location of first use. struct TypeReferenceMap : QHash<int, TypeReference> { TypeReference &add(int nameIndex, const Location &loc) { @@ -574,7 +739,75 @@ struct TypeReferenceMap : QHash<int, TypeReference> return *it; return *insert(nameIndex, loc); } + + template <typename CompiledObject> + void collectFromObject(const CompiledObject *obj) + { + if (obj->inheritedTypeNameIndex != 0) { + TypeReference &r = this->add(obj->inheritedTypeNameIndex, obj->location); + r.needsCreation = true; + r.errorWhenNotFound = true; + } + + for (auto prop = obj->propertiesBegin(), propEnd = obj->propertiesEnd(); prop != propEnd; ++prop) { + if (prop->type >= QV4::CompiledData::Property::Custom) { + // ### FIXME: We could report the more accurate location here by using prop->location, but the old + // compiler can't and the tests expect it to be the object location right now. + TypeReference &r = this->add(prop->customTypeNameIndex, obj->location); + r.errorWhenNotFound = true; + } + } + + for (auto binding = obj->bindingsBegin(), bindingEnd = obj->bindingsEnd(); binding != bindingEnd; ++binding) { + if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) + this->add(binding->propertyNameIndex, binding->location); + } + } + + template <typename Iterator> + void collectFromObjects(Iterator it, Iterator end) + { + for (; it != end; ++it) + collectFromObject(*it); + } +}; + +#ifndef V4_BOOTSTRAP +struct ResolvedTypeReference +{ + ResolvedTypeReference() + : type(0) + , majorVersion(0) + , minorVersion(0) + , isFullyDynamicType(false) + {} + + QQmlType *type; + QQmlRefPointer<QQmlPropertyCache> typePropertyCache; + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit; + + int majorVersion; + int minorVersion; + // Types such as QQmlPropertyMap can add properties dynamically at run-time and + // therefore cannot have a property cache installed when instantiated. + bool isFullyDynamicType; + + QQmlPropertyCache *propertyCache() const; + QQmlPropertyCache *createPropertyCache(QQmlEngine *); + bool addToHash(QCryptographicHash *hash, QQmlEngine *engine); + + void doDynamicTypeCheck(); }; +// map from name index +// While this could be a hash, a map is chosen here to provide a stable +// order, which is used to calculating a check-sum on dependent meta-objects. +struct ResolvedTypeReferenceMap: public QMap<int, ResolvedTypeReference*> +{ + bool addToHash(QCryptographicHash *hash, QQmlEngine *engine) const; +}; +#else +struct ResolvedTypeReferenceMap {}; +#endif // index is per-object binding index typedef QVector<QQmlPropertyData*> BindingPropertyData; @@ -597,7 +830,7 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit : public QQmlRefCount virtual ~CompilationUnit(); #endif - Unit *data; + const Unit *data; // Called only when building QML, when we build the header for JS first and append QML data virtual QV4::CompiledData::Unit *createUnitData(QmlIR::Document *irDocument); @@ -614,20 +847,81 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit : public QQmlRefCount QVector<QV4::Function *> runtimeFunctions; mutable QQmlNullableValue<QUrl> m_url; + // QML specific fields + QQmlPropertyCacheVector propertyCaches; + QQmlPropertyCache *rootPropertyCache() const { return propertyCaches.at(data->indexOfRootObject); } + + QQmlRefPointer<QQmlTypeNameCache> importCache; + // index is object index. This allows fast access to the // property data when initializing bindings, avoiding expensive // lookups by string (property name). QVector<BindingPropertyData> bindingPropertyDataPerObject; + // mapping from component object index (CompiledData::Unit object index that points to component) to identifier hash of named objects + // this is initialized on-demand by QQmlContextData + QHash<int, IdentifierHash<int>> namedObjectsPerComponentCache; + IdentifierHash<int> namedObjectsPerComponent(int componentObjectIndex); + + // pointers either to data->constants() or little-endian memory copy. + const Value* constants; + + void finalize(QQmlEnginePrivate *engine); + + int totalBindingsCount; // Number of bindings used in this type + int totalParserStatusCount; // Number of instantiated types that are QQmlParserStatus subclasses + int totalObjectCount; // Number of objects explicitly instantiated + + QVector<QQmlScriptData *> dependentScripts; + ResolvedTypeReferenceMap resolvedTypes; + + bool verifyChecksum(QQmlEngine *engine, + const ResolvedTypeReferenceMap &dependentTypes) const; + + int metaTypeId; + int listMetaTypeId; + bool isRegisteredWithEngine; + + QScopedPointer<CompilationUnitMapper> backingFile; + + // --- interface for QQmlPropertyCacheCreator + typedef Object CompiledObject; + int objectCount() const { return data->nObjects; } + int rootObjectIndex() const { return data->indexOfRootObject; } + const Object *objectAt(int index) const { return data->objectAt(index); } + QString stringAt(int index) const { return data->stringAt(index); } + + struct FunctionIterator + { + FunctionIterator(const Unit *unit, const Object *object, int index) : unit(unit), object(object), index(index) {} + const Unit *unit; + const Object *object; + int index; + + const Function *operator->() const { return unit->functionAt(object->functionOffsetTable()[index]); } + void operator++() { ++index; } + bool operator==(const FunctionIterator &rhs) const { return index == rhs.index; } + bool operator!=(const FunctionIterator &rhs) const { return index != rhs.index; } + }; + FunctionIterator objectFunctionsBegin(const Object *object) const { return FunctionIterator(data, object, 0); } + FunctionIterator objectFunctionsEnd(const Object *object) const { return FunctionIterator(data, object, object->nFunctions); } + // --- + QV4::Function *linkToEngine(QV4::ExecutionEngine *engine); void unlink(); - virtual QV4::ExecutableAllocator::ChunkOfPages *chunkForFunction(int /*functionIndex*/) { return 0; } - void markObjects(QV4::ExecutionEngine *e); + void destroy() Q_DECL_OVERRIDE; + + bool saveToDisk(const QUrl &unitUrl, QString *errorString); + bool loadFromDisk(const QUrl &url, EvalISelFactory *iselFactory, QString *errorString); + protected: virtual void linkBackendToEngine(QV4::ExecutionEngine *engine) = 0; + virtual void prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit); + virtual bool saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString); + virtual bool memoryMapCode(QString *errorString); #endif // V4_BOOTSTRAP }; diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 3943642146..e1ea3a9b88 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -42,6 +42,9 @@ #include <qv4isel_p.h> #include <private/qv4string_p.h> #include <private/qv4value_p.h> +#include <private/qv4alloca_p.h> +#include <wtf/MathExtras.h> +#include <QCryptographicHash> QV4::Compiler::StringTableGenerator::StringTableGenerator() { @@ -75,16 +78,21 @@ void QV4::Compiler::StringTableGenerator::clear() void QV4::Compiler::StringTableGenerator::serialize(CompiledData::Unit *unit) { char *dataStart = reinterpret_cast<char *>(unit); - uint *stringTable = reinterpret_cast<uint *>(dataStart + unit->offsetToStringTable); + CompiledData::LEUInt32 *stringTable = reinterpret_cast<CompiledData::LEUInt32 *>(dataStart + unit->offsetToStringTable); char *stringData = dataStart + unit->offsetToStringTable + unit->stringTableSize * sizeof(uint); for (int i = 0; i < strings.size(); ++i) { stringTable[i] = stringData - dataStart; const QString &qstr = strings.at(i); - QV4::CompiledData::String *s = (QV4::CompiledData::String*)(stringData); - s->flags = 0; // ### + QV4::CompiledData::String *s = reinterpret_cast<QV4::CompiledData::String *>(stringData); s->size = qstr.length(); +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN memcpy(s + 1, qstr.constData(), qstr.length()*sizeof(ushort)); +#else + ushort *uc = reinterpret_cast<ushort *>(s + 1); + for (int i = 0; i < qstr.length(); ++i) + uc[i] = qToLittleEndian<ushort>(qstr.at(i).unicode()); +#endif stringData += QV4::CompiledData::String::calculateSize(qstr); } @@ -92,7 +100,6 @@ void QV4::Compiler::StringTableGenerator::serialize(CompiledData::Unit *unit) QV4::Compiler::JSUnitGenerator::JSUnitGenerator(QV4::IR::Module *module) : irModule(module) - , jsClassDataSize(0) { // Make sure the empty string always gets index 0 registerString(QString()); @@ -174,30 +181,32 @@ int QV4::Compiler::JSUnitGenerator::registerJSClass(int count, IR::ExprList *arg { // ### re-use existing class definitions. - QList<CompiledData::JSClassMember> members; - members.reserve(count); + const int size = CompiledData::JSClass::calculateSize(count); + jsClassOffsets.append(jsClassData.size()); + const int oldSize = jsClassData.size(); + jsClassData.resize(jsClassData.size() + size); + memset(jsClassData.data() + oldSize, 0, size); - IR::ExprList *it = args; - for (int i = 0; i < count; ++i, it = it->next) { - CompiledData::JSClassMember member; + CompiledData::JSClass *jsClass = reinterpret_cast<CompiledData::JSClass*>(jsClassData.data() + oldSize); + jsClass->nMembers = count; + CompiledData::JSClassMember *member = reinterpret_cast<CompiledData::JSClassMember*>(jsClass + 1); + IR::ExprList *it = args; + for (int i = 0; i < count; ++i, it = it->next, ++member) { QV4::IR::Name *name = it->expr->asName(); it = it->next; const bool isData = it->expr->asConst()->value; it = it->next; - member.nameOffset = registerString(*name->id); - member.isAccessor = !isData; - members << member; + member->nameOffset = registerString(*name->id); + member->isAccessor = !isData; if (!isData) it = it->next; } - jsClasses << members; - jsClassDataSize += CompiledData::JSClass::calculateSize(members.count()); - return jsClasses.size() - 1; + return jsClassOffsets.size() - 1; } QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorOption option) @@ -211,110 +220,70 @@ QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorO registerString(*f->locals.at(i)); } - int unitSize = QV4::CompiledData::Unit::calculateSize(irModule->functions.size(), regexps.size(), - constants.size(), lookups.size(), jsClasses.count()); - - uint functionDataSize = 0; - for (int i = 0; i < irModule->functions.size(); ++i) { - QV4::IR::Function *f = irModule->functions.at(i); - functionOffsets.insert(f, functionDataSize + unitSize); - - const int qmlIdDepsCount = f->idObjectDependencies.count(); - const int qmlPropertyDepsCount = f->scopeObjectPropertyDependencies.count() + f->contextObjectPropertyDependencies.count(); - functionDataSize += QV4::CompiledData::Function::calculateSize(f->formals.size(), f->locals.size(), f->nestedFunctions.size(), qmlIdDepsCount, qmlPropertyDepsCount); + CompiledData::LEUInt32 *functionOffsets = reinterpret_cast<CompiledData::LEUInt32*>(alloca(irModule->functions.size() * sizeof(CompiledData::LEUInt32))); + uint jsClassDataOffset = 0; + + char *dataPtr; + CompiledData::Unit *unit; + { + QV4::CompiledData::Unit tempHeader = generateHeader(option, functionOffsets, &jsClassDataOffset); + dataPtr = reinterpret_cast<char *>(malloc(tempHeader.unitSize)); + memset(dataPtr, 0, tempHeader.unitSize); + memcpy(&unit, &dataPtr, sizeof(CompiledData::Unit*)); + memcpy(unit, &tempHeader, sizeof(tempHeader)); } - const int totalSize = unitSize + functionDataSize + jsClassDataSize + (option == GenerateWithStringTable ? stringTable.sizeOfTableAndData() : 0); - char *data = (char *)malloc(totalSize); - memset(data, 0, totalSize); - QV4::CompiledData::Unit *unit = (QV4::CompiledData::Unit*)data; - - memcpy(unit->magic, QV4::CompiledData::magic_str, sizeof(unit->magic)); - unit->architecture = 0; // ### - unit->flags = QV4::CompiledData::Unit::IsJavascript; - unit->version = 1; - unit->unitSize = totalSize; - unit->functionTableSize = irModule->functions.size(); - unit->offsetToFunctionTable = sizeof(*unit); - unit->lookupTableSize = lookups.count(); - unit->offsetToLookupTable = unit->offsetToFunctionTable + unit->functionTableSize * sizeof(uint); - unit->regexpTableSize = regexps.size(); - unit->offsetToRegexpTable = unit->offsetToLookupTable + unit->lookupTableSize * CompiledData::Lookup::calculateSize(); - unit->constantTableSize = constants.size(); - unit->offsetToConstantTable = unit->offsetToRegexpTable + unit->regexpTableSize * CompiledData::RegExp::calculateSize(); - unit->jsClassTableSize = jsClasses.count(); - unit->offsetToJSClassTable = unit->offsetToConstantTable + unit->constantTableSize * sizeof(ReturnedValue); - if (option == GenerateWithStringTable) { - unit->stringTableSize = stringTable.stringCount(); - unit->offsetToStringTable = unitSize + functionDataSize + jsClassDataSize; - } else { - unit->stringTableSize = 0; - unit->offsetToStringTable = 0; - } - unit->indexOfRootFunction = -1; - unit->sourceFileIndex = getStringId(irModule->fileName); - unit->nImports = 0; - unit->offsetToImports = 0; - unit->nObjects = 0; - unit->offsetToObjects = 0; - unit->indexOfRootObject = 0; - - uint *functionTable = (uint *)(data + unit->offsetToFunctionTable); - for (int i = 0; i < irModule->functions.size(); ++i) - functionTable[i] = functionOffsets.value(irModule->functions.at(i)); - - char *f = data + unitSize; + memcpy(dataPtr + unit->offsetToFunctionTable, functionOffsets, unit->functionTableSize * sizeof(CompiledData::LEUInt32)); + for (int i = 0; i < irModule->functions.size(); ++i) { QV4::IR::Function *function = irModule->functions.at(i); if (function == irModule->rootFunction) unit->indexOfRootFunction = i; - const int bytes = writeFunction(f, i, function); - f += bytes; + writeFunction(dataPtr + functionOffsets[i], function); } - CompiledData::Lookup *lookupsToWrite = (CompiledData::Lookup*)(data + unit->offsetToLookupTable); + CompiledData::Lookup *lookupsToWrite = reinterpret_cast<CompiledData::Lookup*>(dataPtr + unit->offsetToLookupTable); foreach (const CompiledData::Lookup &l, lookups) *lookupsToWrite++ = l; - CompiledData::RegExp *regexpTable = (CompiledData::RegExp *)(data + unit->offsetToRegexpTable); + CompiledData::RegExp *regexpTable = reinterpret_cast<CompiledData::RegExp *>(dataPtr + unit->offsetToRegexpTable); memcpy(regexpTable, regexps.constData(), regexps.size() * sizeof(*regexpTable)); - ReturnedValue *constantTable = (ReturnedValue *)(data + unit->offsetToConstantTable); +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + ReturnedValue *constantTable = reinterpret_cast<ReturnedValue *>(dataPtr + unit->offsetToConstantTable); memcpy(constantTable, constants.constData(), constants.size() * sizeof(ReturnedValue)); - - // write js classes and js class lookup table - uint *jsClassTable = (uint*)(data + unit->offsetToJSClassTable); - char *jsClass = data + unitSize + functionDataSize; - for (int i = 0; i < jsClasses.count(); ++i) { - jsClassTable[i] = jsClass - data; - - const QList<CompiledData::JSClassMember> members = jsClasses.at(i); - - CompiledData::JSClass *c = reinterpret_cast<CompiledData::JSClass*>(jsClass); - c->nMembers = members.count(); - - CompiledData::JSClassMember *memberToWrite = reinterpret_cast<CompiledData::JSClassMember*>(jsClass + sizeof(CompiledData::JSClass)); - foreach (const CompiledData::JSClassMember &member, members) - *memberToWrite++ = member; - - jsClass += CompiledData::JSClass::calculateSize(members.count()); +#else + CompiledData::LEUInt64 *constantTable = reinterpret_cast<CompiledData::LEUInt64 *>(dataPtr + unit->offsetToConstantTable); + for (int i = 0; i < constants.count(); ++i) + constantTable[i] = constants.at(i); +#endif + + { + memcpy(dataPtr + jsClassDataOffset, jsClassData.constData(), jsClassData.size()); + + // write js classes and js class lookup table + CompiledData::LEUInt32 *jsClassOffsetTable = reinterpret_cast<CompiledData::LEUInt32 *>(dataPtr + unit->offsetToJSClassTable); + for (int i = 0; i < jsClassOffsets.count(); ++i) + jsClassOffsetTable[i] = jsClassDataOffset + jsClassOffsets.at(i); } // write strings and string table if (option == GenerateWithStringTable) stringTable.serialize(unit); + unit->generateChecksum(); + return unit; } -int QV4::Compiler::JSUnitGenerator::writeFunction(char *f, int index, QV4::IR::Function *irFunction) +void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::IR::Function *irFunction) const { QV4::CompiledData::Function *function = (QV4::CompiledData::Function *)f; quint32 currentOffset = sizeof(QV4::CompiledData::Function); + currentOffset = (currentOffset + 7) & ~quint32(0x7); - function->index = index; function->nameIndex = getStringId(*irFunction->name); function->flags = 0; if (irFunction->hasDirectEval) @@ -336,8 +305,6 @@ int QV4::Compiler::JSUnitGenerator::writeFunction(char *f, int index, QV4::IR::F currentOffset += function->nLocals * sizeof(quint32); function->nInnerFunctions = irFunction->nestedFunctions.size(); - function->innerFunctionsOffset = currentOffset; - currentOffset += function->nInnerFunctions * sizeof(quint32); function->nDependingIdObjects = 0; function->nDependingContextProperties = 0; @@ -364,6 +331,9 @@ int QV4::Compiler::JSUnitGenerator::writeFunction(char *f, int index, QV4::IR::F function->location.line = irFunction->line; function->location.column = irFunction->column; + function->codeOffset = 0; + function->codeSize = 0; + // write formals quint32 *formals = (quint32 *)(f + function->formalsOffset); for (int i = 0; i < irFunction->formals.size(); ++i) @@ -374,15 +344,12 @@ int QV4::Compiler::JSUnitGenerator::writeFunction(char *f, int index, QV4::IR::F for (int i = 0; i < irFunction->locals.size(); ++i) locals[i] = getStringId(*irFunction->locals.at(i)); - // write inner functions - quint32 *innerFunctions = (quint32 *)(f + function->innerFunctionsOffset); - for (int i = 0; i < irFunction->nestedFunctions.size(); ++i) - innerFunctions[i] = functionOffsets.value(irFunction->nestedFunctions.at(i)); - // write QML dependencies quint32 *writtenDeps = (quint32 *)(f + function->dependingIdObjectsOffset); - foreach (int id, irFunction->idObjectDependencies) - *writtenDeps++ = id; + for (int id : irFunction->idObjectDependencies) { + Q_ASSERT(id >= 0); + *writtenDeps++ = static_cast<quint32>(id); + } writtenDeps = (quint32 *)(f + function->dependingContextPropertiesOffset); for (auto property : irFunction->contextObjectPropertyDependencies) { @@ -395,7 +362,77 @@ int QV4::Compiler::JSUnitGenerator::writeFunction(char *f, int index, QV4::IR::F *writtenDeps++ = property.key(); // property index *writtenDeps++ = property.value(); // notify index } +} + +QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Compiler::JSUnitGenerator::GeneratorOption option, QJsonPrivate::q_littleendian<quint32> *functionOffsets, uint *jsClassDataOffset) +{ + CompiledData::Unit unit; + memset(&unit, 0, sizeof(unit)); + memcpy(unit.magic, CompiledData::magic_str, sizeof(unit.magic)); + unit.flags = QV4::CompiledData::Unit::IsJavascript; + unit.flags |= irModule->unitFlags; + unit.version = QV4_DATA_STRUCTURE_VERSION; + unit.qtVersion = QT_VERSION; + memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum)); + unit.architectureIndex = registerString(QSysInfo::buildAbi()); + unit.codeGeneratorIndex = registerString(codeGeneratorName); + memset(unit.dependencyMD5Checksum, 0, sizeof(unit.dependencyMD5Checksum)); + + quint32 nextOffset = sizeof(CompiledData::Unit); + + unit.functionTableSize = irModule->functions.size(); + unit.offsetToFunctionTable = nextOffset; + nextOffset += unit.functionTableSize * sizeof(uint); + + unit.lookupTableSize = lookups.count(); + unit.offsetToLookupTable = nextOffset; + nextOffset += unit.lookupTableSize * sizeof(CompiledData::Lookup); + + unit.regexpTableSize = regexps.size(); + unit.offsetToRegexpTable = nextOffset; + nextOffset += unit.regexpTableSize * sizeof(CompiledData::RegExp); + + unit.constantTableSize = constants.size(); + + // Ensure we load constants from well-aligned addresses into for example SSE registers. + nextOffset = static_cast<quint32>(WTF::roundUpToMultipleOf(16, nextOffset)); + unit.offsetToConstantTable = nextOffset; + nextOffset += unit.constantTableSize * sizeof(ReturnedValue); + + unit.jsClassTableSize = jsClassOffsets.count(); + unit.offsetToJSClassTable = nextOffset; + nextOffset += unit.jsClassTableSize * sizeof(uint); + + *jsClassDataOffset = nextOffset; + nextOffset += jsClassData.size(); + + for (int i = 0; i < irModule->functions.size(); ++i) { + QV4::IR::Function *f = irModule->functions.at(i); + functionOffsets[i] = nextOffset; + + const int qmlIdDepsCount = f->idObjectDependencies.count(); + const int qmlPropertyDepsCount = f->scopeObjectPropertyDependencies.count() + f->contextObjectPropertyDependencies.count(); + nextOffset += QV4::CompiledData::Function::calculateSize(f->formals.size(), f->locals.size(), f->nestedFunctions.size(), qmlIdDepsCount, qmlPropertyDepsCount); + } - return CompiledData::Function::calculateSize(function->nFormals, function->nLocals, function->nInnerFunctions, - function->nDependingIdObjects, function->nDependingContextProperties + function->nDependingScopeProperties); + if (option == GenerateWithStringTable) { + unit.stringTableSize = stringTable.stringCount(); + unit.offsetToStringTable = nextOffset; + nextOffset += stringTable.sizeOfTableAndData(); + } else { + unit.stringTableSize = 0; + unit.offsetToStringTable = 0; + } + unit.indexOfRootFunction = -1; + unit.sourceFileIndex = getStringId(irModule->fileName); + unit.sourceTimeStamp = irModule->sourceTimeStamp; + unit.nImports = 0; + unit.offsetToImports = 0; + unit.nObjects = 0; + unit.offsetToObjects = 0; + unit.indexOfRootObject = 0; + + unit.unitSize = nextOffset; + + return unit; } diff --git a/src/qml/compiler/qv4compiler_p.h b/src/qml/compiler/qv4compiler_p.h index 0321a83b4f..49b8664513 100644 --- a/src/qml/compiler/qv4compiler_p.h +++ b/src/qml/compiler/qv4compiler_p.h @@ -52,6 +52,7 @@ #include <QtCore/qstring.h> #include "qv4jsir_p.h" +#include <private/qjson_p.h> QT_BEGIN_NAMESPACE @@ -114,18 +115,20 @@ struct Q_QML_PRIVATE_EXPORT JSUnitGenerator { QV4::CompiledData::Unit *generateUnit(GeneratorOption option = GenerateWithStringTable); // Returns bytes written - int writeFunction(char *f, int index, IR::Function *irFunction); + void writeFunction(char *f, IR::Function *irFunction) const; StringTableGenerator stringTable; + QString codeGeneratorName; private: + CompiledData::Unit generateHeader(GeneratorOption option, QJsonPrivate::q_littleendian<quint32> *functionOffsets, uint *jsClassDataOffset); + IR::Module *irModule; - QHash<IR::Function *, uint> functionOffsets; QList<CompiledData::Lookup> lookups; QVector<CompiledData::RegExp> regexps; QVector<ReturnedValue> constants; - QList<QList<CompiledData::JSClassMember> > jsClasses; - uint jsClassDataSize; + QByteArray jsClassData; + QVector<int> jsClassOffsets; }; } diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 90010ccf52..ca4e0b73d4 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -50,18 +50,26 @@ // // We mean it. // - -#include <QtCore/qglobal.h> +#include <private/qv4global_p.h> #include <private/qv4value_p.h> #include <private/qv4function_p.h> #include <private/qv4runtime_p.h> +QT_REQUIRE_CONFIG(qml_interpreter); + QT_BEGIN_NAMESPACE +#ifdef QT_NO_QML_DEBUGGER +#define MOTH_DEBUG_INSTR(F) +#else +#define MOTH_DEBUG_INSTR(F) \ + F(Line, line) \ + F(Debug, debug) +#endif + #define FOR_EACH_MOTH_INSTR(F) \ F(Ret, ret) \ - F(Line, line) \ - F(Debug, debug) \ + MOTH_DEBUG_INSTR(F) \ F(LoadRuntimeString, loadRuntimeString) \ F(LoadRegExp, loadRegExp) \ F(LoadClosure, loadClosure) \ @@ -162,7 +170,7 @@ QT_BEGIN_NAMESPACE #define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QV4::Moth::Instr) - 1) #ifdef MOTH_THREADED_INTERPRETER -# define MOTH_INSTR_HEADER void *code; +# define MOTH_INSTR_HEADER union { quint32 instructionType; void *code; }; #else # define MOTH_INSTR_HEADER quint32 instructionType; #endif @@ -174,6 +182,8 @@ QT_BEGIN_NAMESPACE namespace QV4 { namespace Moth { + // When making changes to the instructions, make sure to bump QV4_DATA_STRUCTURE_VERSION in qv4compileddata_p.h + struct Param { // Params are looked up as follows: // Constant: 0 @@ -243,6 +253,7 @@ union Instr { enum Type { FOR_EACH_MOTH_INSTR(MOTH_INSTR_ENUM) + LastInstruction }; struct instr_common { @@ -252,6 +263,8 @@ union Instr MOTH_INSTR_HEADER Param result; }; + +#ifndef QT_NO_QML_DEBUGGING struct instr_line { MOTH_INSTR_HEADER qint32 lineNumber; @@ -260,6 +273,8 @@ union Instr MOTH_INSTR_HEADER qint32 lineNumber; }; +#endif // QT_NO_QML_DEBUGGING + struct instr_loadRuntimeString { MOTH_INSTR_HEADER int stringId; @@ -322,12 +337,14 @@ union Instr int propertyIndex; Param base; Param result; + bool captureRequired; }; struct instr_loadContextObjectProperty { MOTH_INSTR_HEADER int propertyIndex; Param base; Param result; + bool captureRequired; }; struct instr_loadIdObject { MOTH_INSTR_HEADER @@ -672,7 +689,7 @@ union Instr }; struct instr_binop { MOTH_INSTR_HEADER - QV4::Runtime::BinaryOperation alu; + uint alu; // offset inside the runtime methods Param lhs; Param rhs; Param result; @@ -757,7 +774,7 @@ union Instr }; struct instr_binopContext { MOTH_INSTR_HEADER - QV4::Runtime::BinaryOperationContext alu; + uint alu; // offset inside the runtime methods Param lhs; Param rhs; Param result; diff --git a/src/qml/compiler/qv4isel_moth.cpp b/src/qml/compiler/qv4isel_moth.cpp index e84b9c6ec9..ca6319ef3c 100644 --- a/src/qml/compiler/qv4isel_moth.cpp +++ b/src/qml/compiler/qv4isel_moth.cpp @@ -46,6 +46,7 @@ #include <private/qv4regexpobject_p.h> #include <private/qv4compileddata_p.h> #include <private/qqmlengine_p.h> +#include <wtf/MathExtras.h> #undef USE_TYPE_INFO @@ -54,7 +55,7 @@ using namespace QV4::Moth; namespace { -inline QV4::Runtime::BinaryOperation aluOpFunction(IR::AluOp op) +inline uint aluOpFunction(IR::AluOp op) { switch (op) { case IR::OpInvalid: @@ -70,43 +71,43 @@ inline QV4::Runtime::BinaryOperation aluOpFunction(IR::AluOp op) case IR::OpCompl: return 0; case IR::OpBitAnd: - return QV4::Runtime::bitAnd; + return offsetof(QV4::Runtime, bitAnd); case IR::OpBitOr: - return QV4::Runtime::bitOr; + return offsetof(QV4::Runtime, bitOr); case IR::OpBitXor: - return QV4::Runtime::bitXor; + return offsetof(QV4::Runtime, bitXor); case IR::OpAdd: return 0; case IR::OpSub: - return QV4::Runtime::sub; + return offsetof(QV4::Runtime, sub); case IR::OpMul: - return QV4::Runtime::mul; + return offsetof(QV4::Runtime, mul); case IR::OpDiv: - return QV4::Runtime::div; + return offsetof(QV4::Runtime, div); case IR::OpMod: - return QV4::Runtime::mod; + return offsetof(QV4::Runtime, mod); case IR::OpLShift: - return QV4::Runtime::shl; + return offsetof(QV4::Runtime, shl); case IR::OpRShift: - return QV4::Runtime::shr; + return offsetof(QV4::Runtime, shr); case IR::OpURShift: - return QV4::Runtime::ushr; + return offsetof(QV4::Runtime, ushr); case IR::OpGt: - return QV4::Runtime::greaterThan; + return offsetof(QV4::Runtime, greaterThan); case IR::OpLt: - return QV4::Runtime::lessThan; + return offsetof(QV4::Runtime, lessThan); case IR::OpGe: - return QV4::Runtime::greaterEqual; + return offsetof(QV4::Runtime, greaterEqual); case IR::OpLe: - return QV4::Runtime::lessEqual; + return offsetof(QV4::Runtime, lessEqual); case IR::OpEqual: - return QV4::Runtime::equal; + return offsetof(QV4::Runtime, equal); case IR::OpNotEqual: - return QV4::Runtime::notEqual; + return offsetof(QV4::Runtime, notEqual); case IR::OpStrictEqual: - return QV4::Runtime::strictEqual; + return offsetof(QV4::Runtime, strictEqual); case IR::OpStrictNotEqual: - return QV4::Runtime::strictNotEqual; + return offsetof(QV4::Runtime, strictNotEqual); case IR::OpInstanceof: return 0; case IR::OpIn: @@ -151,8 +152,8 @@ inline bool isBoolType(IR::Expr *e) } // anonymous namespace -InstructionSelection::InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) - : EvalInstructionSelection(execAllocator, module, jsGenerator) +InstructionSelection::InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory) + : EvalInstructionSelection(execAllocator, module, jsGenerator, iselFactory) , qmlEngine(qmlEngine) , _block(0) , _codeStart(0) @@ -250,6 +251,7 @@ void InstructionSelection::run(int functionIndex) if (s->location.startLine != currentLine) { blockNeedsDebugInstruction = false; currentLine = s->location.startLine; +#ifndef QT_NO_QML_DEBUGGER if (irModule->debugMode) { Instruction::Debug debug; debug.lineNumber = currentLine; @@ -259,10 +261,11 @@ void InstructionSelection::run(int functionIndex) line.lineNumber = currentLine; addInstruction(line); } +#endif } } - s->accept(this); + visit(s); } } @@ -576,18 +579,20 @@ void InstructionSelection::setQObjectProperty(IR::Expr *source, IR::Expr *target addInstruction(store); } -void InstructionSelection::getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, IR::Expr *target) +void InstructionSelection::getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, bool captureRequired, IR::Expr *target) { if (kind == IR::Member::MemberOfQmlScopeObject) { Instruction::LoadScopeObjectProperty load; load.base = getParam(source); load.propertyIndex = index; + load.captureRequired = captureRequired; load.result = getResultParam(target); addInstruction(load); } else if (kind == IR::Member::MemberOfQmlContextObject) { Instruction::LoadContextObjectProperty load; load.base = getParam(source); load.propertyIndex = index; + load.captureRequired = captureRequired; load.result = getResultParam(target); addInstruction(load); } else if (kind == IR::Member::MemberOfIdObjectsArray) { @@ -879,11 +884,11 @@ Param InstructionSelection::binopHelper(IR::AluOp oper, IR::Expr *leftSource, IR if (oper == IR::OpInstanceof || oper == IR::OpIn || oper == IR::OpAdd) { Instruction::BinopContext binop; if (oper == IR::OpInstanceof) - binop.alu = QV4::Runtime::instanceof; + binop.alu = offsetof(QV4::Runtime, instanceof); else if (oper == IR::OpIn) - binop.alu = QV4::Runtime::in; + binop.alu = offsetof(QV4::Runtime, in); else - binop.alu = QV4::Runtime::add; + binop.alu = offsetof(QV4::Runtime, add); binop.lhs = getParam(leftSource); binop.rhs = getParam(rightSource); binop.result = getResultParam(target); @@ -930,6 +935,17 @@ void InstructionSelection::prepareCallArgs(IR::ExprList *e, quint32 &argc, quint } } +void InstructionSelection::addDebugInstruction() +{ +#ifndef QT_NO_QML_DEBUGGER + if (blockNeedsDebugInstruction) { + Instruction::Debug debug; + debug.lineNumber = -int(currentLine); + addInstruction(debug); + } +#endif +} + void InstructionSelection::visitJump(IR::Jump *s) { if (s->target == _nextBlock) @@ -937,11 +953,7 @@ void InstructionSelection::visitJump(IR::Jump *s) if (_removableJumps.contains(s)) return; - if (blockNeedsDebugInstruction) { - Instruction::Debug debug; - debug.lineNumber = -int(currentLine); - addInstruction(debug); - } + addDebugInstruction(); Instruction::Jump jump; jump.offset = 0; @@ -952,11 +964,7 @@ void InstructionSelection::visitJump(IR::Jump *s) void InstructionSelection::visitCJump(IR::CJump *s) { - if (blockNeedsDebugInstruction) { - Instruction::Debug debug; - debug.lineNumber = -int(currentLine); - addInstruction(debug); - } + addDebugInstruction(); Param condition; if (IR::Temp *t = s->cond->asTemp()) { @@ -991,12 +999,8 @@ void InstructionSelection::visitCJump(IR::CJump *s) void InstructionSelection::visitRet(IR::Ret *s) { - if (blockNeedsDebugInstruction) { - // this is required so stepOut will always be guaranteed to stop in every stack frame - Instruction::Debug debug; - debug.lineNumber = -int(currentLine); - addInstruction(debug); - } + // this is required so stepOut will always be guaranteed to stop in every stack frame + addDebugInstruction(); Instruction::Ret ret; ret.result = getParam(s->expr); @@ -1332,12 +1336,7 @@ void QV4::Moth::InstructionSelection::callBuiltinConvertThisToObject() ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &instr) { - -#ifdef MOTH_THREADED_INTERPRETER - instr.common.code = VME::instructionJumpTable()[static_cast<int>(type)]; -#else instr.common.instructionType = type; -#endif int instructionSize = Instr::size(type); if (_codeEnd - _codeNext < instructionSize) { @@ -1423,6 +1422,29 @@ CompilationUnit::~CompilationUnit() void CompilationUnit::linkBackendToEngine(QV4::ExecutionEngine *engine) { +#ifdef MOTH_THREADED_INTERPRETER + // link byte code against addresses of instructions + for (int i = 0; i < codeRefs.count(); ++i) { + QByteArray &codeRef = codeRefs[i]; + char *code = codeRef.data(); + int index = 0; + while (index < codeRef.size()) { + Instr *genericInstr = reinterpret_cast<Instr *>(code + index); + + switch (genericInstr->common.instructionType) { +#define LINK_INSTRUCTION(InstructionType, Member) \ + case Instr::InstructionType: \ + genericInstr->common.code = VME::instructionJumpTable()[static_cast<int>(genericInstr->common.instructionType)]; \ + index += InstrMeta<(int)Instr::InstructionType>::Size; \ + break; + + FOR_EACH_MOTH_INSTR(LINK_INSTRUCTION) + + } + } + } +#endif + runtimeFunctions.resize(data->functionTableSize); runtimeFunctions.fill(0); for (int i = 0 ;i < runtimeFunctions.size(); ++i) { @@ -1433,3 +1455,113 @@ void CompilationUnit::linkBackendToEngine(QV4::ExecutionEngine *engine) runtimeFunctions[i] = runtimeFunction; } } + +void CompilationUnit::prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit) +{ + const int codeAlignment = 16; + quint64 offset = WTF::roundUpToMultipleOf(codeAlignment, unit->unitSize); + Q_ASSERT(int(unit->functionTableSize) == codeRefs.size()); + for (int i = 0; i < codeRefs.size(); ++i) { + CompiledData::Function *compiledFunction = const_cast<CompiledData::Function *>(unit->functionAt(i)); + compiledFunction->codeOffset = offset; + compiledFunction->codeSize = codeRefs.at(i).size(); + offset = WTF::roundUpToMultipleOf(codeAlignment, offset + compiledFunction->codeSize); + } +} + +bool CompilationUnit::saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString) +{ + Q_ASSERT(device->pos() == unit->unitSize); + Q_ASSERT(device->atEnd()); + Q_ASSERT(int(unit->functionTableSize) == codeRefs.size()); + + QByteArray padding; + +#ifdef MOTH_THREADED_INTERPRETER + // Map from instruction label back to instruction type. Only needed when persisting + // already linked compilation units; + QHash<void*, int> reverseInstructionMapping; + if (engine) { + void **instructions = VME::instructionJumpTable(); + for (int i = 0; i < Instr::LastInstruction; ++i) + reverseInstructionMapping.insert(instructions[i], i); + } +#endif + + for (int i = 0; i < codeRefs.size(); ++i) { + const CompiledData::Function *compiledFunction = unit->functionAt(i); + + if (device->pos() > qint64(compiledFunction->codeOffset)) { + *errorString = QStringLiteral("Invalid state of cache file to write."); + return false; + } + + const quint64 paddingSize = compiledFunction->codeOffset - device->pos(); + padding.fill(0, paddingSize); + qint64 written = device->write(padding); + if (written != padding.size()) { + *errorString = device->errorString(); + return false; + } + + QByteArray code = codeRefs.at(i); + +#ifdef MOTH_THREADED_INTERPRETER + if (!reverseInstructionMapping.isEmpty()) { + char *codePtr = code.data(); // detaches + int index = 0; + while (index < code.size()) { + Instr *genericInstr = reinterpret_cast<Instr *>(codePtr + index); + + genericInstr->common.instructionType = reverseInstructionMapping.value(genericInstr->common.code); + + switch (genericInstr->common.instructionType) { + #define REVERSE_INSTRUCTION(InstructionType, Member) \ + case Instr::InstructionType: \ + index += InstrMeta<(int)Instr::InstructionType>::Size; \ + break; + + FOR_EACH_MOTH_INSTR(REVERSE_INSTRUCTION) + } + } + } +#endif + + written = device->write(code.constData(), compiledFunction->codeSize); + if (written != qint64(compiledFunction->codeSize)) { + *errorString = device->errorString(); + return false; + } + } + return true; +} + +bool CompilationUnit::memoryMapCode(QString *errorString) +{ + Q_UNUSED(errorString); + codeRefs.resize(data->functionTableSize); + + const char *basePtr = reinterpret_cast<const char *>(data); + + for (uint i = 0; i < data->functionTableSize; ++i) { + const CompiledData::Function *compiledFunction = data->functionAt(i); + const char *codePtr = const_cast<const char *>(reinterpret_cast<const char *>(basePtr + compiledFunction->codeOffset)); +#ifdef MOTH_THREADED_INTERPRETER + // for the threaded interpreter we need to make a copy of the data because it needs to be + // modified for the instruction handler addresses. + QByteArray code(codePtr, compiledFunction->codeSize); +#else + QByteArray code = QByteArray::fromRawData(codePtr, compiledFunction->codeSize); +#endif + codeRefs[i] = code; + } + + return true; +} + +QQmlRefPointer<CompiledData::CompilationUnit> ISelFactory::createUnitForLoading() +{ + QQmlRefPointer<CompiledData::CompilationUnit> result; + result.adopt(new Moth::CompilationUnit); + return result; +} diff --git a/src/qml/compiler/qv4isel_moth_p.h b/src/qml/compiler/qv4isel_moth_p.h index 29d117af38..74323a2912 100644 --- a/src/qml/compiler/qv4isel_moth_p.h +++ b/src/qml/compiler/qv4isel_moth_p.h @@ -58,6 +58,8 @@ #include <private/qv4value_p.h> #include "qv4instr_moth_p.h" +QT_REQUIRE_CONFIG(qml_interpreter); + QT_BEGIN_NAMESPACE namespace QV4 { @@ -66,7 +68,10 @@ namespace Moth { struct CompilationUnit : public QV4::CompiledData::CompilationUnit { virtual ~CompilationUnit(); - virtual void linkBackendToEngine(QV4::ExecutionEngine *engine); + void linkBackendToEngine(QV4::ExecutionEngine *engine) Q_DECL_OVERRIDE; + void prepareCodeOffsetsForDiskStorage(CompiledData::Unit *unit) Q_DECL_OVERRIDE; + bool saveCodeToDisk(QIODevice *device, const CompiledData::Unit *unit, QString *errorString) Q_DECL_OVERRIDE; + bool memoryMapCode(QString *errorString) Q_DECL_OVERRIDE; QVector<QByteArray> codeRefs; @@ -77,7 +82,7 @@ class Q_QML_EXPORT InstructionSelection: public EvalInstructionSelection { public: - InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator); + InstructionSelection(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory); ~InstructionSelection(); virtual void run(int functionIndex); @@ -134,7 +139,7 @@ protected: virtual void setProperty(IR::Expr *source, IR::Expr *targetBase, const QString &targetName); virtual void setQmlContextProperty(IR::Expr *source, IR::Expr *targetBase, IR::Member::MemberKind kind, int propertyIndex); virtual void setQObjectProperty(IR::Expr *source, IR::Expr *targetBase, int propertyIndex); - virtual void getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, IR::Expr *target); + virtual void getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, bool captureRequired, IR::Expr *target); virtual void getQObjectProperty(IR::Expr *base, int propertyIndex, bool captureRequired, bool isSingleton, int attachedPropertiesId, IR::Expr *target); virtual void getElement(IR::Expr *base, IR::Expr *index, IR::Expr *target); virtual void setElement(IR::Expr *source, IR::Expr *targetBase, IR::Expr *targetIndex); @@ -174,6 +179,8 @@ private: template <int Instr> inline ptrdiff_t addInstruction(const InstrData<Instr> &data); + inline void addDebugInstruction(); + ptrdiff_t addInstructionHelper(Instr::Type type, Instr &instr); void patchJumpAddresses(); QByteArray squeezeCode() const; @@ -202,11 +209,14 @@ private: class Q_QML_EXPORT ISelFactory: public EvalISelFactory { public: + ISelFactory() : EvalISelFactory(QStringLiteral("moth")) {} virtual ~ISelFactory() {} - virtual EvalInstructionSelection *create(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) - { return new InstructionSelection(qmlEngine, execAllocator, module, jsGenerator); } - virtual bool jitCompileRegexps() const + EvalInstructionSelection *create(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) Q_DECL_OVERRIDE Q_DECL_FINAL + { return new InstructionSelection(qmlEngine, execAllocator, module, jsGenerator, this); } + bool jitCompileRegexps() const Q_DECL_OVERRIDE Q_DECL_FINAL { return false; } + QQmlRefPointer<QV4::CompiledData::CompilationUnit> createUnitForLoading() Q_DECL_OVERRIDE; + }; template<int InstrT> diff --git a/src/qml/compiler/qv4isel_p.cpp b/src/qml/compiler/qv4isel_p.cpp index 0ae08160ab..efcfb9bd77 100644 --- a/src/qml/compiler/qv4isel_p.cpp +++ b/src/qml/compiler/qv4isel_p.cpp @@ -52,7 +52,7 @@ using namespace QV4; using namespace QV4::IR; -EvalInstructionSelection::EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) +EvalInstructionSelection::EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory) : useFastLookups(true) , useTypeInference(true) , executableAllocator(execAllocator) @@ -67,6 +67,7 @@ EvalInstructionSelection::EvalInstructionSelection(QV4::ExecutableAllocator *exe Q_ASSERT(execAllocator); #endif Q_ASSERT(module); + jsGenerator->codeGeneratorName = iselFactory->codeGeneratorName; } EvalInstructionSelection::~EvalInstructionSelection() @@ -146,24 +147,24 @@ void IRDecoder::visitMove(IR::Move *s) const int attachedPropertiesId = m->attachedPropertiesId; const bool isSingletonProperty = m->kind == IR::Member::MemberOfSingletonObject; - if (_function && attachedPropertiesId == 0 && !m->property->isConstant()) { + if (_function && attachedPropertiesId == 0 && !m->property->isConstant() && _function->isQmlBinding) { if (m->kind == IR::Member::MemberOfQmlContextObject) { - _function->contextObjectPropertyDependencies.insert(m->property->coreIndex, m->property->notifyIndex); + _function->contextObjectPropertyDependencies.insert(m->property->coreIndex(), m->property->notifyIndex()); captureRequired = false; } else if (m->kind == IR::Member::MemberOfQmlScopeObject) { - _function->scopeObjectPropertyDependencies.insert(m->property->coreIndex, m->property->notifyIndex); + _function->scopeObjectPropertyDependencies.insert(m->property->coreIndex(), m->property->notifyIndex()); captureRequired = false; } } if (m->kind == IR::Member::MemberOfQmlScopeObject || m->kind == IR::Member::MemberOfQmlContextObject) { - getQmlContextProperty(m->base, (IR::Member::MemberKind)m->kind, m->property->coreIndex, s->target); + getQmlContextProperty(m->base, (IR::Member::MemberKind)m->kind, m->property->coreIndex(), captureRequired, s->target); return; } - getQObjectProperty(m->base, m->property->coreIndex, captureRequired, isSingletonProperty, attachedPropertiesId, s->target); + getQObjectProperty(m->base, m->property->coreIndex(), captureRequired, isSingletonProperty, attachedPropertiesId, s->target); #endif // V4_BOOTSTRAP return; } else if (m->kind == IR::Member::MemberOfIdObjectsArray) { - getQmlContextProperty(m->base, (IR::Member::MemberKind)m->kind, m->idIndex, s->target); + getQmlContextProperty(m->base, (IR::Member::MemberKind)m->kind, m->idIndex, /*captureRequired*/false, s->target); return; } else if (m->base->asTemp() || m->base->asConst() || m->base->asArgLocal()) { getProperty(m->base, *m->name, s->target); @@ -186,7 +187,7 @@ void IRDecoder::visitMove(IR::Move *s) #ifndef V4_BOOTSTRAP Q_ASSERT(member->kind != IR::Member::MemberOfIdObjectsArray); if (member->kind == IR::Member::MemberOfQmlScopeObject || member->kind == IR::Member::MemberOfQmlContextObject) { - callQmlContextProperty(member->base, (IR::Member::MemberKind)member->kind, member->property->coreIndex, c->args, s->target); + callQmlContextProperty(member->base, (IR::Member::MemberKind)member->kind, member->property->coreIndex(), c->args, s->target); return; } #endif @@ -215,10 +216,10 @@ void IRDecoder::visitMove(IR::Move *s) Q_UNIMPLEMENTED(); #else if (m->kind == IR::Member::MemberOfQmlScopeObject || m->kind == IR::Member::MemberOfQmlContextObject) { - setQmlContextProperty(s->source, m->base, (IR::Member::MemberKind)m->kind, m->property->coreIndex); + setQmlContextProperty(s->source, m->base, (IR::Member::MemberKind)m->kind, m->property->coreIndex()); return; } - setQObjectProperty(s->source, m->base, m->property->coreIndex); + setQObjectProperty(s->source, m->base, m->property->coreIndex()); #endif return; } else { @@ -262,7 +263,7 @@ void IRDecoder::visitExp(IR::Exp *s) #ifndef V4_BOOTSTRAP Q_ASSERT(member->kind != IR::Member::MemberOfIdObjectsArray); if (member->kind == IR::Member::MemberOfQmlScopeObject || member->kind == IR::Member::MemberOfQmlContextObject) { - callQmlContextProperty(member->base, (IR::Member::MemberKind)member->kind, member->property->coreIndex, c->args, 0); + callQmlContextProperty(member->base, (IR::Member::MemberKind)member->kind, member->property->coreIndex(), c->args, 0); return; } #endif @@ -294,7 +295,7 @@ void IRDecoder::callBuiltin(IR::Call *call, Expr *result) if (member->kind == IR::Member::MemberOfQmlScopeObject || member->kind == IR::Member::MemberOfQmlContextObject) { callBuiltinTypeofQmlContextProperty(member->base, IR::Member::MemberKind(member->kind), - member->property->coreIndex, result); + member->property->coreIndex(), result); return; } #endif diff --git a/src/qml/compiler/qv4isel_p.h b/src/qml/compiler/qv4isel_p.h index 88d2071c52..037c02e5ea 100644 --- a/src/qml/compiler/qv4isel_p.h +++ b/src/qml/compiler/qv4isel_p.h @@ -65,13 +65,14 @@ class QQmlEnginePrivate; namespace QV4 { +class EvalISelFactory; class ExecutableAllocator; struct Function; class Q_QML_PRIVATE_EXPORT EvalInstructionSelection { public: - EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator); + EvalInstructionSelection(QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator, EvalISelFactory *iselFactory); virtual ~EvalInstructionSelection() = 0; QQmlRefPointer<QV4::CompiledData::CompilationUnit> compile(bool generateUnitData = true); @@ -104,23 +105,44 @@ protected: class Q_QML_PRIVATE_EXPORT EvalISelFactory { public: + EvalISelFactory(const QString &codeGeneratorName) : codeGeneratorName(codeGeneratorName) {} virtual ~EvalISelFactory() = 0; virtual EvalInstructionSelection *create(QQmlEnginePrivate *qmlEngine, QV4::ExecutableAllocator *execAllocator, IR::Module *module, QV4::Compiler::JSUnitGenerator *jsGenerator) = 0; virtual bool jitCompileRegexps() const = 0; + virtual QQmlRefPointer<QV4::CompiledData::CompilationUnit> createUnitForLoading() = 0; + + const QString codeGeneratorName; }; namespace IR { -class Q_QML_PRIVATE_EXPORT IRDecoder: protected IR::StmtVisitor +class Q_QML_PRIVATE_EXPORT IRDecoder { public: IRDecoder() : _function(0) {} virtual ~IRDecoder() = 0; - virtual void visitPhi(IR::Phi *) {} - -public: // visitor methods for StmtVisitor: - virtual void visitMove(IR::Move *s); - virtual void visitExp(IR::Exp *s); + void visit(Stmt *s) + { + if (auto e = s->asExp()) { + visitExp(e); + } else if (auto m = s->asMove()) { + visitMove(m); + } else if (auto j = s->asJump()) { + visitJump(j); + } else if (auto c = s->asCJump()) { + visitCJump(c); + } else if (auto r = s->asRet()) { + visitRet(r); + } else if (auto p = s->asPhi()) { + visitPhi(p); + } else { + Q_UNREACHABLE(); + } + } + +private: // visitor methods for StmtVisitor: + void visitMove(IR::Move *s); + void visitExp(IR::Exp *s); public: // to implement by subclasses: virtual void callBuiltinInvalid(IR::Name *func, IR::ExprList *args, IR::Expr *result) = 0; @@ -166,7 +188,7 @@ public: // to implement by subclasses: virtual void initClosure(IR::Closure *closure, IR::Expr *target) = 0; virtual void getProperty(IR::Expr *base, const QString &name, IR::Expr *target) = 0; virtual void getQObjectProperty(IR::Expr *base, int propertyIndex, bool captureRequired, bool isSingletonProperty, int attachedPropertiesId, IR::Expr *target) = 0; - virtual void getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, IR::Expr *target) = 0; + virtual void getQmlContextProperty(IR::Expr *source, IR::Member::MemberKind kind, int index, bool captureRequired, IR::Expr *target) = 0; virtual void setProperty(IR::Expr *source, IR::Expr *targetBase, const QString &targetName) = 0; virtual void setQmlContextProperty(IR::Expr *source, IR::Expr *targetBase, IR::Member::MemberKind kind, int propertyIndex) = 0; virtual void setQObjectProperty(IR::Expr *source, IR::Expr *targetBase, int propertyIndex) = 0; @@ -178,6 +200,11 @@ public: // to implement by subclasses: virtual void binop(IR::AluOp oper, IR::Expr *leftSource, IR::Expr *rightSource, IR::Expr *target) = 0; protected: + virtual void visitJump(IR::Jump *) = 0; + virtual void visitCJump(IR::CJump *) = 0; + virtual void visitRet(IR::Ret *) = 0; + virtual void visitPhi(IR::Phi *) {} + virtual void callBuiltin(IR::Call *c, IR::Expr *result); IR::Function *_function; // subclass needs to set diff --git a/src/qml/compiler/qv4isel_util_p.h b/src/qml/compiler/qv4isel_util_p.h index 674fc01623..1755193d32 100644 --- a/src/qml/compiler/qv4isel_util_p.h +++ b/src/qml/compiler/qv4isel_util_p.h @@ -104,7 +104,7 @@ inline Primitive convertToValue(IR::Const *c) return Primitive::undefinedValue(); } -class ConvertTemps: protected IR::StmtVisitor, protected IR::ExprVisitor +class ConvertTemps { void renumber(IR::Temp *t) { @@ -132,7 +132,7 @@ protected: virtual void process(IR::Stmt *s) { - s->accept(this); + visit(s); } public: @@ -157,34 +157,28 @@ public: } protected: - virtual void visitConst(IR::Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(IR::Name *) {} - virtual void visitTemp(IR::Temp *e) { renumber(e); } - virtual void visitArgLocal(IR::ArgLocal *) {} - virtual void visitClosure(IR::Closure *) {} - virtual void visitConvert(IR::Convert *e) { e->expr->accept(this); } - virtual void visitUnop(IR::Unop *e) { e->expr->accept(this); } - virtual void visitBinop(IR::Binop *e) { e->left->accept(this); e->right->accept(this); } - virtual void visitCall(IR::Call *e) { - e->base->accept(this); - for (IR::ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); + void visit(IR::Stmt *s) { + switch (s->stmtKind) { + case IR::Stmt::PhiStmt: + visitPhi(s->asPhi()); + break; + default: + STMT_VISIT_ALL_KINDS(s); + break; + } } - virtual void visitNew(IR::New *e) { - e->base->accept(this); - for (IR::ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); + + virtual void visitPhi(IR::Phi *) + { Q_UNREACHABLE(); } + +private: + void visit(IR::Expr *e) { + if (auto temp = e->asTemp()) { + renumber(temp); + } else { + EXPR_VISIT_ALL_KINDS(e); + } } - virtual void visitSubscript(IR::Subscript *e) { e->base->accept(this); e->index->accept(this); } - virtual void visitMember(IR::Member *e) { e->base->accept(this); } - virtual void visitExp(IR::Exp *s) { s->expr->accept(this); } - virtual void visitMove(IR::Move *s) { s->target->accept(this); s->source->accept(this); } - virtual void visitJump(IR::Jump *) {} - virtual void visitCJump(IR::CJump *s) { s->cond->accept(this); } - virtual void visitRet(IR::Ret *s) { s->expr->accept(this); } - virtual void visitPhi(IR::Phi *) { Q_UNREACHABLE(); } }; } // namespace QV4 diff --git a/src/qml/compiler/qv4jsir.cpp b/src/qml/compiler/qv4jsir.cpp index b28db59190..5687834b00 100644 --- a/src/qml/compiler/qv4jsir.cpp +++ b/src/qml/compiler/qv4jsir.cpp @@ -157,12 +157,13 @@ AluOp binaryOperator(int op) } } -struct RemoveSharedExpressions: IR::StmtVisitor, IR::ExprVisitor +class RemoveSharedExpressions { CloneExpr clone; std::vector<Expr *> subexpressions; // contains all the non-cloned subexpressions in the given function. sorted using std::lower_bound. Expr *uniqueExpr; +public: RemoveSharedExpressions(): uniqueExpr(0) {} void operator()(IR::Function *function) @@ -176,11 +177,12 @@ struct RemoveSharedExpressions: IR::StmtVisitor, IR::ExprVisitor clone.setBasicBlock(block); for (Stmt *s : block->statements()) { - s->accept(this); + visit(s); } } } +private: template <typename Expr_> Expr_ *cleanup(Expr_ *expr) { @@ -189,7 +191,7 @@ struct RemoveSharedExpressions: IR::StmtVisitor, IR::ExprVisitor subexpressions.insert(it, expr); IR::Expr *e = expr; qSwap(uniqueExpr, e); - expr->accept(this); + visit(expr); qSwap(uniqueExpr, e); return static_cast<Expr_ *>(e); } @@ -199,83 +201,45 @@ struct RemoveSharedExpressions: IR::StmtVisitor, IR::ExprVisitor return clone(expr); } - // statements - virtual void visitExp(Exp *s) + void visit(Stmt *s) { - s->expr = cleanup(s->expr); - } - - virtual void visitMove(Move *s) - { - s->target = cleanup(s->target); - s->source = cleanup(s->source); - } - - virtual void visitJump(Jump *) - { - // nothing to do for Jump statements - } - - virtual void visitCJump(CJump *s) - { - s->cond = cleanup(s->cond); - } - - virtual void visitRet(Ret *s) - { - s->expr = cleanup(s->expr); - } - - virtual void visitPhi(IR::Phi *) { Q_UNIMPLEMENTED(); } - - // expressions - virtual void visitConst(Const *) {} - virtual void visitString(String *) {} - virtual void visitRegExp(RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - - virtual void visitConvert(Convert *e) - { - e->expr = cleanup(e->expr); - } - - virtual void visitUnop(Unop *e) - { - e->expr = cleanup(e->expr); - } - - virtual void visitBinop(Binop *e) - { - e->left = cleanup(e->left); - e->right = cleanup(e->right); - } - - virtual void visitCall(Call *e) - { - e->base = cleanup(e->base); - for (IR::ExprList *it = e->args; it; it = it->next) - it->expr = cleanup(it->expr); - } - - virtual void visitNew(New *e) - { - e->base = cleanup(e->base); - for (IR::ExprList *it = e->args; it; it = it->next) - it->expr = cleanup(it->expr); - } - - virtual void visitSubscript(Subscript *e) - { - e->base = cleanup(e->base); - e->index = cleanup(e->index); + if (auto e = s->asExp()) { + e->expr = cleanup(e->expr); + } else if (auto m = s->asMove()) { + m->target = cleanup(m->target); + m->source = cleanup(m->source); + } else if (auto c = s->asCJump()) { + c->cond = cleanup(c->cond); + } else if (auto r = s->asRet()) { + r->expr = cleanup(r->expr); + } } - virtual void visitMember(Member *e) + void visit(Expr *e) { - e->base = cleanup(e->base); + if (auto c = e->asConvert()) { + c->expr = cleanup(c->expr); + } else if (auto u = e->asUnop()) { + u->expr = cleanup(u->expr); + } else if (auto b = e->asBinop()) { + b->left = cleanup(b->left); + b->right = cleanup(b->right); + } else if (auto c = e->asCall()) { + c->base = cleanup(c->base); + for (IR::ExprList *it = c->args; it; it = it->next) { + it->expr = cleanup(it->expr); + } + } else if (auto n = e->asNew()) { + n->base = cleanup(n->base); + for (IR::ExprList *it = n->args; it; it = it->next) { + it->expr = cleanup(it->expr); + } + } else if (auto s = e->asSubscript()) { + s->base = cleanup(s->base); + s->index = cleanup(s->index); + } else if (auto m = e->asMember()) { + m->base = cleanup(m->base); + } } }; @@ -404,9 +368,10 @@ Function::Function(Module *module, Function *outer, const QString &name) , isNamedExpression(false) , hasTry(false) , hasWith(false) + , isQmlBinding(false) , unused(0) - , line(-1) - , column(-1) + , line(0) + , column(0) , _allBasicBlocks(0) , _statementCount(0) { @@ -548,75 +513,39 @@ ExprList *CloneExpr::clone(ExprList *list) return clonedList; } -void CloneExpr::visitConst(Const *e) -{ - cloned = cloneConst(e, block->function); -} - -void CloneExpr::visitString(String *e) -{ - cloned = block->STRING(e->value); -} - -void CloneExpr::visitRegExp(RegExp *e) -{ - cloned = block->REGEXP(e->value, e->flags); -} - -void CloneExpr::visitName(Name *e) -{ - cloned = cloneName(e, block->function); -} - -void CloneExpr::visitTemp(Temp *e) -{ - cloned = cloneTemp(e, block->function); -} - -void CloneExpr::visitArgLocal(ArgLocal *e) -{ - cloned = cloneArgLocal(e, block->function); -} - -void CloneExpr::visitClosure(Closure *e) -{ - cloned = block->CLOSURE(e->value); -} - -void CloneExpr::visitConvert(Convert *e) -{ - cloned = block->CONVERT(clone(e->expr), e->type); -} - -void CloneExpr::visitUnop(Unop *e) -{ - cloned = block->UNOP(e->op, clone(e->expr)); -} - -void CloneExpr::visitBinop(Binop *e) -{ - cloned = block->BINOP(e->op, clone(e->left), clone(e->right)); -} - -void CloneExpr::visitCall(Call *e) -{ - cloned = block->CALL(clone(e->base), clone(e->args)); -} - -void CloneExpr::visitNew(New *e) -{ - cloned = block->NEW(clone(e->base), clone(e->args)); -} - -void CloneExpr::visitSubscript(Subscript *e) -{ - cloned = block->SUBSCRIPT(clone(e->base), clone(e->index)); -} - -void CloneExpr::visitMember(Member *e) -{ - Expr *clonedBase = clone(e->base); - cloned = block->MEMBER(clonedBase, e->name, e->property, e->kind, e->idIndex); +void CloneExpr::visit(Expr *e) +{ + if (auto c = e->asConst()) { + cloned = cloneConst(c, block->function); + } else if (auto s = e->asString()) { + cloned = block->STRING(s->value); + } else if (auto r = e->asRegExp()) { + cloned = block->REGEXP(r->value, r->flags); + } else if (auto n = e->asName()) { + cloned = cloneName(n, block->function); + } else if (auto t = e->asTemp()) { + cloned = cloneTemp(t, block->function); + } else if (auto a = e->asArgLocal()) { + cloned = cloneArgLocal(a, block->function); + } else if (auto c = e->asClosure()) { + cloned = block->CLOSURE(c->value); + } else if (auto c = e->asConvert()) { + cloned = block->CONVERT(clone(c->expr), c->type); + } else if (auto u = e->asUnop()) { + cloned = block->UNOP(u->op, clone(u->expr)); + } else if (auto b = e->asBinop()) { + cloned = block->BINOP(b->op, clone(b->left), clone(b->right)); + } else if (auto c = e->asCall()) { + cloned = block->CALL(clone(c->base), clone(c->args)); + } else if (auto n = e->asNew()) { + cloned = block->NEW(clone(n->base), clone(n->args)); + } else if (auto s = e->asSubscript()) { + cloned = block->SUBSCRIPT(clone(s->base), clone(s->index)); + } else if (auto m = e->asMember()) { + cloned = block->MEMBER(clone(m->base), m->name, m->property, m->kind, m->idIndex); + } else { + Q_UNREACHABLE(); + } } IRPrinter::IRPrinter(QTextStream *out) @@ -632,17 +561,17 @@ IRPrinter::~IRPrinter() void IRPrinter::print(Stmt *s) { - s->accept(this); + visit(s); } void IRPrinter::print(const Expr &e) { - const_cast<Expr *>(&e)->accept(this); + visit(const_cast<Expr *>(&e)); } void IRPrinter::print(Expr *e) { - e->accept(this); + visit(e); } void IRPrinter::print(Function *f) @@ -696,7 +625,7 @@ void IRPrinter::print(BasicBlock *bb) QTextStream *prevOut = &os; std::swap(out, prevOut); addStmtNr(s); - s->accept(this); + visit(s); if (s->location.startLine) { out->flush(); for (int i = 58 - str.length(); i > 0; --i) @@ -713,10 +642,29 @@ void IRPrinter::print(BasicBlock *bb) std::swap(currentBB, bb); } +void IRPrinter::visit(Stmt *s) +{ + if (auto e = s->asExp()) { + visitExp(e); + } else if (auto m = s->asMove()) { + visitMove(m); + } else if (auto j = s->asJump()) { + visitJump(j); + } else if (auto c = s->asCJump()) { + visitCJump(c); + } else if (auto r = s->asRet()) { + visitRet(r); + } else if (auto p = s->asPhi()) { + visitPhi(p); + } else { + Q_UNREACHABLE(); + } +} + void IRPrinter::visitExp(Exp *s) { *out << "void "; - s->expr->accept(this); + visit(s->expr); } void IRPrinter::visitMove(Move *s) @@ -725,13 +673,13 @@ void IRPrinter::visitMove(Move *s) if (!s->swap && targetTemp->type != UnknownType) *out << typeName(targetTemp->type) << ' '; - s->target->accept(this); + visit(s->target); *out << ' '; if (s->swap) *out << "<=> "; else *out << "= "; - s->source->accept(this); + visit(s->source); } void IRPrinter::visitJump(Jump *s) @@ -742,7 +690,7 @@ void IRPrinter::visitJump(Jump *s) void IRPrinter::visitCJump(CJump *s) { *out << "if "; - s->cond->accept(this); + visit(s->cond); *out << " goto L" << s->iftrue->index() << " else goto L" << s->iffalse->index(); } @@ -752,7 +700,7 @@ void IRPrinter::visitRet(Ret *s) *out << "return"; if (s->expr) { *out << ' '; - s->expr->accept(this); + visit(s->expr); } } @@ -761,7 +709,7 @@ void IRPrinter::visitPhi(Phi *s) if (s->targetTemp->type != UnknownType) *out << typeName(s->targetTemp->type) << ' '; - s->targetTemp->accept(this); + visit(s->targetTemp); *out << " = phi "; for (int i = 0, ei = s->incoming.size(); i < ei; ++i) { if (i > 0) @@ -769,7 +717,42 @@ void IRPrinter::visitPhi(Phi *s) if (currentBB) *out << 'L' << currentBB->in.at(i)->index() << ": "; if (s->incoming[i]) - s->incoming[i]->accept(this); + visit(s->incoming[i]); + } +} + +void IRPrinter::visit(Expr *e) +{ + if (auto c = e->asConst()) { + visitConst(c); + } else if (auto s = e->asString()) { + visitString(s); + } else if (auto r = e->asRegExp()) { + visitRegExp(r); + } else if (auto n = e->asName()) { + visitName(n); + } else if (auto t = e->asTemp()) { + visitTemp(t); + } else if (auto a = e->asArgLocal()) { + visitArgLocal(a); + } else if (auto c = e->asClosure()) { + visitClosure(c); + } else if (auto c = e->asConvert()) { + visitConvert(c); + } else if (auto u = e->asUnop()) { + visitUnop(u); + } else if (auto b = e->asBinop()) { + visitBinop(b); + } else if (auto c = e->asCall()) { + visitCall(c); + } else if (auto n = e->asNew()) { + visitNew(n); + } else if (auto s = e->asSubscript()) { + visitSubscript(s); + } else if (auto m = e->asMember()) { + visitMember(m); + } else { + Q_UNREACHABLE(); } } @@ -867,32 +850,32 @@ void IRPrinter::visitClosure(Closure *e) void IRPrinter::visitConvert(Convert *e) { *out << "convert " << typeName(e->expr->type) << " to " << typeName(e->type) << ' '; - e->expr->accept(this); + visit(e->expr); } void IRPrinter::visitUnop(Unop *e) { *out << opname(e->op) << ' '; - e->expr->accept(this); + visit(e->expr); } void IRPrinter::visitBinop(Binop *e) { *out << opname(e->op) << ' '; - e->left->accept(this); + visit(e->left); *out << ", "; - e->right->accept(this); + visit(e->right); } void IRPrinter::visitCall(Call *e) { *out << "call "; - e->base->accept(this); + visit(e->base); *out << '('; for (ExprList *it = e->args; it; it = it->next) { if (it != e->args) *out << ", "; - it->expr->accept(this); + visit(it->expr); } *out << ')'; } @@ -900,21 +883,21 @@ void IRPrinter::visitCall(Call *e) void IRPrinter::visitNew(New *e) { *out << "new "; - e->base->accept(this); + visit(e->base); *out << '('; for (ExprList *it = e->args; it; it = it->next) { if (it != e->args) *out << ", "; - it->expr->accept(this); + visit(it->expr); } *out << ')'; } void IRPrinter::visitSubscript(Subscript *e) { - e->base->accept(this); + visit(e->base); *out << '['; - e->index->accept(this); + visit(e->index); *out << ']'; } @@ -924,12 +907,12 @@ void IRPrinter::visitMember(Member *e) && e->attachedPropertiesId != 0 && !e->base->asTemp()) *out << "[[attached property from " << e->attachedPropertiesId << "]]"; else - e->base->accept(this); + visit(e->base); *out << '.' << *e->name; #ifndef V4_BOOTSTRAP if (e->property) - *out << " (meta-property " << e->property->coreIndex - << " <" << QMetaType::typeName(e->property->propType) + *out << " (meta-property " << e->property->coreIndex() + << " <" << QMetaType::typeName(e->property->propType()) << ">)"; else if (e->kind == Member::MemberOfIdObjectsArray) *out << "(id object " << e->idIndex << ")"; @@ -942,15 +925,15 @@ QString IRPrinter::escape(const QString &s) for (int i = 0; i < s.length(); ++i) { const QChar ch = s.at(i); if (ch == QLatin1Char('\n')) - r += QStringLiteral("\\n"); + r += QLatin1String("\\n"); else if (ch == QLatin1Char('\r')) - r += QStringLiteral("\\r"); + r += QLatin1String("\\r"); else if (ch == QLatin1Char('\\')) - r += QStringLiteral("\\\\"); + r += QLatin1String("\\\\"); else if (ch == QLatin1Char('"')) - r += QStringLiteral("\\\""); + r += QLatin1String("\\\""); else if (ch == QLatin1Char('\'')) - r += QStringLiteral("\\'"); + r += QLatin1String("\\'"); else r += ch; } diff --git a/src/qml/compiler/qv4jsir_p.h b/src/qml/compiler/qv4jsir_p.h index 94fa65cf71..73aa6c4975 100644 --- a/src/qml/compiler/qv4jsir_p.h +++ b/src/qml/compiler/qv4jsir_p.h @@ -192,7 +192,7 @@ enum AluOp { AluOp binaryOperator(int op); const char *opname(IR::AluOp op); -enum Type { +enum Type : quint16 { UnknownType = 0, MissingType = 1 << 0, @@ -217,34 +217,6 @@ inline bool strictlyEqualTypes(Type t1, Type t2) QString typeName(Type t); -struct ExprVisitor { - virtual ~ExprVisitor() {} - virtual void visitConst(Const *) = 0; - virtual void visitString(String *) = 0; - virtual void visitRegExp(RegExp *) = 0; - virtual void visitName(Name *) = 0; - virtual void visitTemp(Temp *) = 0; - virtual void visitArgLocal(ArgLocal *) = 0; - virtual void visitClosure(Closure *) = 0; - virtual void visitConvert(Convert *) = 0; - virtual void visitUnop(Unop *) = 0; - virtual void visitBinop(Binop *) = 0; - virtual void visitCall(Call *) = 0; - virtual void visitNew(New *) = 0; - virtual void visitSubscript(Subscript *) = 0; - virtual void visitMember(Member *) = 0; -}; - -struct StmtVisitor { - virtual ~StmtVisitor() {} - virtual void visitExp(Exp *) = 0; - virtual void visitMove(Move *) = 0; - virtual void visitJump(Jump *) = 0; - virtual void visitCJump(CJump *) = 0; - virtual void visitRet(Ret *) = 0; - virtual void visitPhi(Phi *) = 0; -}; - struct MemberExpressionResolver; struct DiscoveredType { @@ -288,28 +260,129 @@ struct MemberExpressionResolver }; struct Q_AUTOTEST_EXPORT Expr { + enum ExprKind : quint8 { + NameExpr, + TempExpr, + ArgLocalExpr, + SubscriptExpr, + MemberExpr, + + LastLValue = MemberExpr, + + ConstExpr, + StringExpr, + RegExpExpr, + ClosureExpr, + ConvertExpr, + UnopExpr, + BinopExpr, + CallExpr, + NewExpr + }; + Type type; + const ExprKind exprKind; + + Expr &operator=(const Expr &other) { + Q_ASSERT(exprKind == other.exprKind); + type = other.type; + return *this; + } + + template <typename To> + inline bool isa() const { + return To::classof(this); + } - Expr(): type(UnknownType) {} - virtual ~Expr() {} - virtual void accept(ExprVisitor *) = 0; - virtual bool isLValue() { return false; } - virtual Const *asConst() { return 0; } - virtual String *asString() { return 0; } - virtual RegExp *asRegExp() { return 0; } - virtual Name *asName() { return 0; } - virtual Temp *asTemp() { return 0; } - virtual ArgLocal *asArgLocal() { return 0; } - virtual Closure *asClosure() { return 0; } - virtual Convert *asConvert() { return 0; } - virtual Unop *asUnop() { return 0; } - virtual Binop *asBinop() { return 0; } - virtual Call *asCall() { return 0; } - virtual New *asNew() { return 0; } - virtual Subscript *asSubscript() { return 0; } - virtual Member *asMember() { return 0; } + template <typename To> + inline To *as() { + if (isa<To>()) { + return static_cast<To *>(this); + } else { + return nullptr; + } + } + + template <typename To> + inline const To *as() const { + if (isa<To>()) { + return static_cast<const To *>(this); + } else { + return nullptr; + } + } + + Expr(ExprKind exprKind): type(UnknownType), exprKind(exprKind) {} + bool isLValue() const; + + Const *asConst() { return as<Const>(); } + String *asString() { return as<String>(); } + RegExp *asRegExp() { return as<RegExp>(); } + Name *asName() { return as<Name>(); } + Temp *asTemp() { return as<Temp>(); } + ArgLocal *asArgLocal() { return as<ArgLocal>(); } + Closure *asClosure() { return as<Closure>(); } + Convert *asConvert() { return as<Convert>(); } + Unop *asUnop() { return as<Unop>(); } + Binop *asBinop() { return as<Binop>(); } + Call *asCall() { return as<Call>(); } + New *asNew() { return as<New>(); } + Subscript *asSubscript() { return as<Subscript>(); } + Member *asMember() { return as<Member>(); } }; +#define EXPR_VISIT_ALL_KINDS(e) \ + switch (e->exprKind) { \ + case QV4::IR::Expr::ConstExpr: \ + break; \ + case QV4::IR::Expr::StringExpr: \ + break; \ + case QV4::IR::Expr::RegExpExpr: \ + break; \ + case QV4::IR::Expr::NameExpr: \ + break; \ + case QV4::IR::Expr::TempExpr: \ + break; \ + case QV4::IR::Expr::ArgLocalExpr: \ + break; \ + case QV4::IR::Expr::ClosureExpr: \ + break; \ + case QV4::IR::Expr::ConvertExpr: { \ + auto casted = e->asConvert(); \ + visit(casted->expr); \ + } break; \ + case QV4::IR::Expr::UnopExpr: { \ + auto casted = e->asUnop(); \ + visit(casted->expr); \ + } break; \ + case QV4::IR::Expr::BinopExpr: { \ + auto casted = e->asBinop(); \ + visit(casted->left); \ + visit(casted->right); \ + } break; \ + case QV4::IR::Expr::CallExpr: { \ + auto casted = e->asCall(); \ + visit(casted->base); \ + for (QV4::IR::ExprList *it = casted->args; it; it = it->next) \ + visit(it->expr); \ + } break; \ + case QV4::IR::Expr::NewExpr: { \ + auto casted = e->asNew(); \ + visit(casted->base); \ + for (QV4::IR::ExprList *it = casted->args; it; it = it->next) \ + visit(it->expr); \ + } break; \ + case QV4::IR::Expr::SubscriptExpr: { \ + auto casted = e->asSubscript(); \ + visit(casted->base); \ + visit(casted->index); \ + } break; \ + case QV4::IR::Expr::MemberExpr: { \ + auto casted = e->asMember(); \ + visit(casted->base); \ + } break; \ + } + struct ExprList { Expr *expr; ExprList *next; @@ -326,26 +399,28 @@ struct ExprList { struct Const: Expr { double value; + Const(): Expr(ConstExpr) {} + void init(Type type, double value) { this->type = type; this->value = value; } - virtual void accept(ExprVisitor *v) { v->visitConst(this); } - virtual Const *asConst() { return this; } + static bool classof(const Expr *c) { return c->exprKind == ConstExpr; } }; struct String: Expr { const QString *value; + String(): Expr(StringExpr) {} + void init(const QString *value) { this->value = value; } - virtual void accept(ExprVisitor *v) { v->visitString(this); } - virtual String *asString() { return this; } + static bool classof(const Expr *c) { return c->exprKind == StringExpr; } }; struct RegExp: Expr { @@ -359,14 +434,15 @@ struct RegExp: Expr { const QString *value; int flags; + RegExp(): Expr(RegExpExpr) {} + void init(const QString *value, int flags) { this->value = value; this->flags = flags; } - virtual void accept(ExprVisitor *v) { v->visitRegExp(this); } - virtual RegExp *asRegExp() { return this; } + static bool classof(const Expr *c) { return c->exprKind == RegExpExpr; } }; struct Name: Expr { @@ -399,13 +475,13 @@ struct Name: Expr { quint32 line; quint32 column; + Name(): Expr(NameExpr) {} + void initGlobal(const QString *id, quint32 line, quint32 column); void init(const QString *id, quint32 line, quint32 column); void init(Builtin builtin, quint32 line, quint32 column); - virtual void accept(ExprVisitor *v) { v->visitName(this); } - virtual bool isLValue() { return true; } - virtual Name *asName() { return this; } + static bool classof(const Expr *c) { return c->exprKind == NameExpr; } }; struct Q_AUTOTEST_EXPORT Temp: Expr { @@ -424,7 +500,8 @@ struct Q_AUTOTEST_EXPORT Temp: Expr { MemberExpressionResolver *memberResolver; Temp() - : index((1 << 28) - 1) + : Expr(TempExpr) + , index((1 << 28) - 1) , isReadOnly(0) , kind(Invalid) , memberResolver(0) @@ -438,9 +515,8 @@ struct Q_AUTOTEST_EXPORT Temp: Expr { } bool isInvalid() const { return kind == Invalid; } - virtual void accept(ExprVisitor *v) { v->visitTemp(this); } - virtual bool isLValue() { return !isReadOnly; } - virtual Temp *asTemp() { return this; } + + static bool classof(const Expr *c) { return c->exprKind == TempExpr; } }; inline bool operator==(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW @@ -479,44 +555,48 @@ struct Q_AUTOTEST_EXPORT ArgLocal: Expr { this->isArgumentsOrEval = false; } - virtual void accept(ExprVisitor *v) { v->visitArgLocal(this); } - virtual bool isLValue() { return true; } - virtual ArgLocal *asArgLocal() { return this; } + ArgLocal(): Expr(ArgLocalExpr) {} bool operator==(const ArgLocal &other) const { return index == other.index && scope == other.scope && kind == other.kind; } + + static bool classof(const Expr *c) { return c->exprKind == ArgLocalExpr; } }; struct Closure: Expr { int value; // index in _module->functions const QString *functionName; + Closure(): Expr(ClosureExpr) {} + void init(int functionInModule, const QString *functionName) { this->value = functionInModule; this->functionName = functionName; } - virtual void accept(ExprVisitor *v) { v->visitClosure(this); } - virtual Closure *asClosure() { return this; } + static bool classof(const Expr *c) { return c->exprKind == ClosureExpr; } }; struct Convert: Expr { Expr *expr; + Convert(): Expr(ConvertExpr) {} + void init(Expr *expr, Type type) { this->expr = expr; this->type = type; } - virtual void accept(ExprVisitor *v) { v->visitConvert(this); } - virtual Convert *asConvert() { return this; } + static bool classof(const Expr *c) { return c->exprKind == ConvertExpr; } }; struct Unop: Expr { - AluOp op; Expr *expr; + AluOp op; + + Unop(): Expr(UnopExpr) {} void init(AluOp op, Expr *expr) { @@ -524,14 +604,15 @@ struct Unop: Expr { this->expr = expr; } - virtual void accept(ExprVisitor *v) { v->visitUnop(this); } - virtual Unop *asUnop() { return this; } + static bool classof(const Expr *c) { return c->exprKind == UnopExpr; } }; struct Binop: Expr { - AluOp op; Expr *left; // Temp or Const Expr *right; // Temp or Const + AluOp op; + + Binop(): Expr(BinopExpr) {} void init(AluOp op, Expr *left, Expr *right) { @@ -540,14 +621,15 @@ struct Binop: Expr { this->right = right; } - virtual void accept(ExprVisitor *v) { v->visitBinop(this); } - virtual Binop *asBinop() { return this; } + static bool classof(const Expr *c) { return c->exprKind == BinopExpr; } }; struct Call: Expr { Expr *base; // Name, Member, Temp ExprList *args; // List of Temps + Call(): Expr(CallExpr) {} + void init(Expr *base, ExprList *args) { this->base = base; @@ -560,14 +642,15 @@ struct Call: Expr { return 0; } - virtual void accept(ExprVisitor *v) { v->visitCall(this); } - virtual Call *asCall() { return this; } + static bool classof(const Expr *c) { return c->exprKind == CallExpr; } }; struct New: Expr { Expr *base; // Name, Member, Temp ExprList *args; // List of Temps + New(): Expr(NewExpr) {} + void init(Expr *base, ExprList *args) { this->base = base; @@ -580,23 +663,22 @@ struct New: Expr { return 0; } - virtual void accept(ExprVisitor *v) { v->visitNew(this); } - virtual New *asNew() { return this; } + static bool classof(const Expr *c) { return c->exprKind == NewExpr; } }; struct Subscript: Expr { Expr *base; Expr *index; + Subscript(): Expr(SubscriptExpr) {} + void init(Expr *base, Expr *index) { this->base = base; this->index = index; } - virtual void accept(ExprVisitor *v) { v->visitSubscript(this); } - virtual bool isLValue() { return true; } - virtual Subscript *asSubscript() { return this; } + static bool classof(const Expr *c) { return c->exprKind == SubscriptExpr; } }; struct Member: Expr { @@ -628,6 +710,8 @@ struct Member: Expr { uchar kind: 3; // MemberKind + Member(): Expr(MemberExpr) {} + void setEnumValue(int value) { kind = MemberOfEnum; enumValue = value; @@ -649,35 +733,52 @@ struct Member: Expr { this->kind = kind; } - virtual void accept(ExprVisitor *v) { v->visitMember(this); } - virtual bool isLValue() { return true; } - virtual Member *asMember() { return this; } + static bool classof(const Expr *c) { return c->exprKind == MemberExpr; } }; +inline bool Expr::isLValue() const { + if (auto t = as<Temp>()) + return !t->isReadOnly; + return exprKind <= LastLValue; +} + struct Stmt { + enum StmtKind: quint8 { + MoveStmt, + ExpStmt, + JumpStmt, + CJumpStmt, + RetStmt, + PhiStmt + }; + + template <typename To> + inline bool isa() const { + return To::classof(this); + } + + template <typename To> + inline To *as() { + if (isa<To>()) + return static_cast<To *>(this); + else + return nullptr; + } + enum { InvalidId = -1 }; QQmlJS::AST::SourceLocation location; - explicit Stmt(int id): _id(id) {} + explicit Stmt(int id, StmtKind stmtKind): _id(id), stmtKind(stmtKind) {} - virtual ~Stmt() - { -#ifdef Q_CC_MSVC - // MSVC complains about potential memory leaks if a destructor never returns. -#else - Q_UNREACHABLE(); -#endif - } - virtual Stmt *asTerminator() { return 0; } + Stmt *asTerminator(); - virtual void accept(StmtVisitor *) = 0; - virtual Exp *asExp() { return 0; } - virtual Move *asMove() { return 0; } - virtual Jump *asJump() { return 0; } - virtual CJump *asCJump() { return 0; } - virtual Ret *asRet() { return 0; } - virtual Phi *asPhi() { return 0; } + Exp *asExp() { return as<Exp>(); } + Move *asMove() { return as<Move>(); } + Jump *asJump() { return as<Jump>(); } + CJump *asCJump() { return as<CJump>(); } + Ret *asRet() { return as<Ret>(); } + Phi *asPhi() { return as<Phi>(); } int id() const { return _id; } @@ -687,21 +788,52 @@ private: // For memory management in BasicBlock private: friend struct Function; int _id; + +public: + const StmtKind stmtKind; }; +#define STMT_VISIT_ALL_KINDS(s) \ + switch (s->stmtKind) { \ + case QV4::IR::Stmt::MoveStmt: { \ + auto casted = s->asMove(); \ + visit(casted->target); \ + visit(casted->source); \ + } break; \ + case QV4::IR::Stmt::ExpStmt: { \ + auto casted = s->asExp(); \ + visit(casted->expr); \ + } break; \ + case QV4::IR::Stmt::JumpStmt: \ + break; \ + case QV4::IR::Stmt::CJumpStmt: { \ + auto casted = s->asCJump(); \ + visit(casted->cond); \ + } break; \ + case QV4::IR::Stmt::RetStmt: { \ + auto casted = s->asRet(); \ + visit(casted->expr); \ + } break; \ + case QV4::IR::Stmt::PhiStmt: { \ + auto casted = s->asPhi(); \ + visit(casted->targetTemp); \ + for (auto *e : casted->incoming) { \ + visit(e); \ + } \ + } break; \ + } + struct Exp: Stmt { Expr *expr; - Exp(int id): Stmt(id) {} + Exp(int id): Stmt(id, ExpStmt) {} void init(Expr *expr) { this->expr = expr; } - virtual void accept(StmtVisitor *v) { v->visitExp(this); } - virtual Exp *asExp() { return this; } - + static bool classof(const Stmt *c) { return c->stmtKind == ExpStmt; } }; struct Move: Stmt { @@ -709,7 +841,7 @@ struct Move: Stmt { Expr *source; bool swap; - Move(int id): Stmt(id) {} + Move(int id): Stmt(id, MoveStmt) {} void init(Expr *target, Expr *source) { @@ -718,25 +850,20 @@ struct Move: Stmt { this->swap = false; } - virtual void accept(StmtVisitor *v) { v->visitMove(this); } - virtual Move *asMove() { return this; } - + static bool classof(const Stmt *c) { return c->stmtKind == MoveStmt; } }; struct Jump: Stmt { BasicBlock *target; - Jump(int id): Stmt(id) {} + Jump(int id): Stmt(id, JumpStmt) {} void init(BasicBlock *target) { this->target = target; } - virtual Stmt *asTerminator() { return this; } - - virtual void accept(StmtVisitor *v) { v->visitJump(this); } - virtual Jump *asJump() { return this; } + static bool classof(const Stmt *c) { return c->stmtKind == JumpStmt; } }; struct CJump: Stmt { @@ -745,7 +872,7 @@ struct CJump: Stmt { BasicBlock *iffalse; BasicBlock *parent; - CJump(int id): Stmt(id) {} + CJump(int id): Stmt(id, CJumpStmt) {} void init(Expr *cond, BasicBlock *iftrue, BasicBlock *iffalse, BasicBlock *parent) { @@ -755,26 +882,20 @@ struct CJump: Stmt { this->parent = parent; } - virtual Stmt *asTerminator() { return this; } - - virtual void accept(StmtVisitor *v) { v->visitCJump(this); } - virtual CJump *asCJump() { return this; } + static bool classof(const Stmt *c) { return c->stmtKind == CJumpStmt; } }; struct Ret: Stmt { Expr *expr; - Ret(int id): Stmt(id) {} + Ret(int id): Stmt(id, RetStmt) {} void init(Expr *expr) { this->expr = expr; } - virtual Stmt *asTerminator() { return this; } - - virtual void accept(StmtVisitor *v) { v->visitRet(this); } - virtual Ret *asRet() { return this; } + static bool classof(const Stmt *c) { return c->stmtKind == RetStmt; } }; // Phi nodes can only occur at the start of a basic block. If there are any, they need to be @@ -785,30 +906,54 @@ struct Phi: Stmt { Temp *targetTemp; VarLengthArray<Expr *, 4> incoming; - Phi(int id): Stmt(id) {} + Phi(int id): Stmt(id, PhiStmt) {} - virtual void accept(StmtVisitor *v) { v->visitPhi(this); } - virtual Phi *asPhi() { return this; } + static bool classof(const Stmt *c) { return c->stmtKind == PhiStmt; } void destroyData() { incoming.~VarLengthArray(); } }; +inline Stmt *Stmt::asTerminator() +{ + if (auto s = asJump()) { + return s; + } else if (auto s = asCJump()) { + return s; + } else if (auto s = asRet()) { + return s; + } else { + return nullptr; + } +} + struct Q_QML_PRIVATE_EXPORT Module { QQmlJS::MemoryPool pool; QVector<Function *> functions; Function *rootFunction; QString fileName; + qint64 sourceTimeStamp; bool isQmlModule; // implies rootFunction is always 0 + uint unitFlags; // flags merged into CompiledData::Unit::flags +#ifdef QT_NO_QML_DEBUGGER + static const bool debugMode = false; +#else bool debugMode; +#endif Function *newFunction(const QString &name, Function *outer); Module(bool debugMode) : rootFunction(0) + , sourceTimeStamp(0) , isQmlModule(false) + , unitFlags(0) +#ifndef QT_NO_QML_DEBUGGER , debugMode(debugMode) {} +#else + { Q_UNUSED(debugMode); } +#endif ~Module(); void setFileName(const QString &name); @@ -1060,6 +1205,20 @@ private: unsigned _isRemoved : 1; }; +template <typename T> +class SmallSet: public QVarLengthArray<T, 8> +{ +public: + void insert(int value) + { + for (auto it : *this) { + if (it == value) + return; + } + this->append(value); + } +}; + // Map from meta property index (existence implies dependency) to notify signal index struct KeyValuePair { @@ -1125,14 +1284,15 @@ struct Function { uint isNamedExpression : 1; uint hasTry: 1; uint hasWith: 1; - uint unused : 25; + uint isQmlBinding: 1; + uint unused : 24; - // Location of declaration in source code (-1 if not specified) - int line; - int column; + // Location of declaration in source code (0 if not specified) + uint line; + uint column; // Qml extension: - QSet<int> idObjectDependencies; + SmallSet<int> idObjectDependencies; PropertyDependencyMap contextObjectPropertyDependencies; PropertyDependencyMap scopeObjectPropertyDependencies; @@ -1192,7 +1352,7 @@ private: int _statementCount; }; -class CloneExpr: protected IR::ExprVisitor +class CloneExpr { public: explicit CloneExpr(IR::BasicBlock *block = 0); @@ -1210,7 +1370,7 @@ public: { Expr *c = expr; qSwap(cloned, c); - expr->accept(this); + visit(expr); qSwap(cloned, c); return static_cast<ExprSubclass *>(c); } @@ -1253,23 +1413,10 @@ public: return newArgLocal; } -protected: +private: IR::ExprList *clone(IR::ExprList *list); - virtual void visitConst(Const *); - virtual void visitString(String *); - virtual void visitRegExp(RegExp *); - virtual void visitName(Name *); - virtual void visitTemp(Temp *); - virtual void visitArgLocal(ArgLocal *); - virtual void visitClosure(Closure *); - virtual void visitConvert(Convert *); - virtual void visitUnop(Unop *); - virtual void visitBinop(Binop *); - virtual void visitCall(Call *); - virtual void visitNew(New *); - virtual void visitSubscript(Subscript *); - virtual void visitMember(Member *); + void visit(Expr *e); protected: IR::BasicBlock *block; @@ -1278,7 +1425,7 @@ private: IR::Expr *cloned; }; -class Q_AUTOTEST_EXPORT IRPrinter: public StmtVisitor, public ExprVisitor +class Q_AUTOTEST_EXPORT IRPrinter { public: IRPrinter(QTextStream *out); @@ -1291,6 +1438,7 @@ public: virtual void print(Function *f); virtual void print(BasicBlock *bb); + void visit(Stmt *s); virtual void visitExp(Exp *s); virtual void visitMove(Move *s); virtual void visitJump(Jump *s); @@ -1298,6 +1446,7 @@ public: virtual void visitRet(Ret *s); virtual void visitPhi(Phi *s); + void visit(Expr *e); virtual void visitConst(Const *e); virtual void visitString(String *e); virtual void visitRegExp(RegExp *e); diff --git a/src/qml/compiler/qv4ssa.cpp b/src/qml/compiler/qv4ssa.cpp index f021e1f760..4111cc77db 100644 --- a/src/qml/compiler/qv4ssa.cpp +++ b/src/qml/compiler/qv4ssa.cpp @@ -642,7 +642,7 @@ public: qout << from; else qout << "(none)"; - qout << " -> " << to->index() << endl; + qout << " dominates " << to->index() << endl; } qDebug("%s", buf.data().constData()); } @@ -740,6 +740,21 @@ public: return order; } + void mergeIntoPredecessor(BasicBlock *successor) + { + int succIdx = successor->index(); + if (succIdx == InvalidBasicBlockIndex) { + return; + } + + int succDom = idom[unsigned(succIdx)]; + for (BasicBlockIndex &idx : idom) { + if (idx == succIdx) { + idx = succDom; + } + } + } + private: bool dominates(BasicBlockIndex dominator, BasicBlockIndex dominated) const { // dominator can be Invalid when the dominated block has no dominator (i.e. the start node) @@ -898,7 +913,7 @@ private: } }; -class VariableCollector: public StmtVisitor, ExprVisitor { +class VariableCollector { std::vector<Temp> _allTemps; std::vector<BasicBlockSet> _defsites; std::vector<std::vector<int> > A_orig; @@ -946,7 +961,7 @@ public: currentBB = bb; killed.assign(function->tempCount, false); for (Stmt *s : bb->statements()) - s->accept(this); + visit(s); } } @@ -971,62 +986,45 @@ public: return nonLocals.at(var.index); } -protected: - virtual void visitPhi(Phi *) {} - virtual void visitConvert(Convert *e) { e->expr->accept(this); } - - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitUnop(Unop *e) { e->expr->accept(this); } - virtual void visitBinop(Binop *e) { e->left->accept(this); e->right->accept(this); } - virtual void visitSubscript(Subscript *e) { e->base->accept(this); e->index->accept(this); } - virtual void visitMember(Member *e) { e->base->accept(this); } - virtual void visitExp(Exp *s) { s->expr->accept(this); } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { s->cond->accept(this); } - virtual void visitRet(Ret *s) { s->expr->accept(this); } - - virtual void visitCall(Call *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - - virtual void visitNew(New *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - - virtual void visitMove(Move *s) { - s->source->accept(this); +private: + void visit(Stmt *s) + { + if (s->asPhi()) { + // nothing to do + } else if (auto move = s->asMove()) { + visit(move->source); - if (Temp *t = s->target->asTemp()) { - addTemp(t); + if (Temp *t = move->target->asTemp()) { + addTemp(t); - if (isCollectable(t)) { - _defsites[t->index].insert(currentBB); - addDefInCurrentBlock(t); + if (isCollectable(t)) { + _defsites[t->index].insert(currentBB); + addDefInCurrentBlock(t); - // For semi-pruned SSA: - killed.setBit(t->index); + // For semi-pruned SSA: + killed.setBit(t->index); + } + } else { + visit(move->target); } } else { - s->target->accept(this); + STMT_VISIT_ALL_KINDS(s) } } - virtual void visitTemp(Temp *t) + void visit(Expr *e) { - addTemp(t); + if (auto t = e->asTemp()) { + addTemp(t); - if (isCollectable(t)) - if (!killed.at(t->index)) - nonLocals.setBit(t->index); + if (isCollectable(t)) { + if (!killed.at(t->index)) { + nonLocals.setBit(t->index); + } + } + } else { + EXPR_VISIT_ALL_KINDS(e); + } } }; @@ -1192,6 +1190,15 @@ public: return _defUses[variable.index].blockOfStatement; } + void replaceBasicBlock(BasicBlock *from, BasicBlock *to) + { + for (auto &du : _defUses) { + if (du.blockOfStatement == from) { + du.blockOfStatement = to; + } + } + } + void removeUse(Stmt *usingStmt, const Temp &var) { Q_ASSERT(static_cast<unsigned>(var.index) < _defUses.size()); @@ -1345,7 +1352,7 @@ void insertPhiNode(const Temp &a, BasicBlock *y, IR::Function *f) { // // Undo(t, c) = // mapping[t] = c -class VariableRenamer: public StmtVisitor, public ExprVisitor +class VariableRenamer { Q_DISABLE_COPY(VariableRenamer) @@ -1466,7 +1473,7 @@ private: for (Stmt *s : bb->statements()) { currentStmt = s; - s->accept(this); + visit(s); } for (BasicBlock *Y : bb->out) { @@ -1532,23 +1539,35 @@ private: return newIndex; } -protected: - virtual void visitTemp(Temp *e) { // only called for uses, not defs -// qDebug()<<"I: replacing use of"<<e->index<<"with"<<stack[e->index].top(); - e->index = currentNumber(*e); - e->kind = Temp::VirtualRegister; - defUses.addUse(*e, currentStmt); - } +private: + void visit(Stmt *s) + { + if (auto move = s->asMove()) { + // uses: + visit(move->source); - virtual void visitMove(Move *s) { - // uses: - s->source->accept(this); + // defs: + if (Temp *t = move->target->asTemp()) { + renameTemp(t); + } else { + visit(move->target); + } + } else if (auto phi = s->asPhi()) { + renameTemp(phi->targetTemp); + } else { + STMT_VISIT_ALL_KINDS(s); + } + } - // defs: - if (Temp *t = s->target->asTemp()) - renameTemp(t); - else - s->target->accept(this); + void visit(Expr *e) + { + if (auto temp = e->asTemp()) { + temp->index = currentNumber(*temp); + temp->kind = Temp::VirtualRegister; + defUses.addUse(*temp, currentStmt); + } else { + EXPR_VISIT_ALL_KINDS(e); + } } void renameTemp(Temp *t) { // only called for defs, not uses @@ -1558,44 +1577,6 @@ protected: t->index = newIdx; defUses.addDef(t, currentStmt, currentBB); } - - virtual void visitConvert(Convert *e) { e->expr->accept(this); } - virtual void visitPhi(Phi *s) { renameTemp(s->targetTemp); } - - virtual void visitExp(Exp *s) { s->expr->accept(this); } - - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { s->cond->accept(this); } - virtual void visitRet(Ret *s) { s->expr->accept(this); } - - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitUnop(Unop *e) { e->expr->accept(this); } - virtual void visitBinop(Binop *e) { e->left->accept(this); e->right->accept(this); } - virtual void visitCall(Call *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - - virtual void visitNew(New *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - - virtual void visitSubscript(Subscript *e) { - e->base->accept(this); - e->index->accept(this); - } - - virtual void visitMember(Member *e) { - e->base->accept(this); - } }; // This function converts the IR to semi-pruned SSA form. For details about SSA and the algorightm, @@ -1922,7 +1903,7 @@ private: } }; -class SideEffectsChecker: public ExprVisitor +class SideEffectsChecker { bool _sideEffect; @@ -1931,11 +1912,14 @@ public: : _sideEffect(false) {} + ~SideEffectsChecker() + {} + bool hasSideEffects(Expr *expr) { bool sideEffect = false; qSwap(_sideEffect, sideEffect); - expr->accept(this); + visit(expr); qSwap(_sideEffect, sideEffect); return sideEffect; } @@ -1948,12 +1932,35 @@ protected: bool seenSideEffects() const { return _sideEffect; } -protected: - void visitConst(Const *) Q_DECL_OVERRIDE {} - void visitString(IR::String *) Q_DECL_OVERRIDE {} - void visitRegExp(IR::RegExp *) Q_DECL_OVERRIDE {} + void visit(Expr *e) + { + if (auto n = e->asName()) { + visitName(n); + } else if (auto t = e->asTemp()) { + visitTemp(t); + } else if (auto c = e->asClosure()) { + visitClosure(c); + } else if (auto c = e->asConvert()) { + visitConvert(c); + } else if (auto u = e->asUnop()) { + visitUnop(u); + } else if (auto b = e->asBinop()) { + visitBinop(b); + } else if (auto c = e->asCall()) { + visitCall(c); + } else if (auto n = e->asNew()) { + visitNew(n); + } else if (auto s = e->asSubscript()) { + visitSubscript(s); + } else if (auto m = e->asMember()) { + visitMember(m); + } + } - void visitName(Name *e) Q_DECL_OVERRIDE { + virtual void visitTemp(Temp *) {} + +private: + void visitName(Name *e) { if (e->freeOfSideEffects) return; // TODO: maybe we can distinguish between built-ins of which we know that they do not have @@ -1962,15 +1969,12 @@ protected: markAsSideEffect(); } - void visitTemp(Temp *) Q_DECL_OVERRIDE {} - void visitArgLocal(ArgLocal *) Q_DECL_OVERRIDE {} - - void visitClosure(Closure *) Q_DECL_OVERRIDE { + void visitClosure(Closure *) { markAsSideEffect(); } - void visitConvert(Convert *e) Q_DECL_OVERRIDE { - e->expr->accept(this); + void visitConvert(Convert *e) { + visit(e->expr); switch (e->expr->type) { case QObjectType: @@ -1983,8 +1987,8 @@ protected: } } - void visitUnop(Unop *e) Q_DECL_OVERRIDE { - e->expr->accept(this); + void visitUnop(Unop *e) { + visit(e->expr); switch (e->op) { case OpUPlus: @@ -2001,7 +2005,7 @@ protected: } } - void visitBinop(Binop *e) Q_DECL_OVERRIDE { + void visitBinop(Binop *e) { // TODO: prune parts that don't have a side-effect. For example, in: // function f(x) { +x+1; return 0; } // we can prune the binop and leave the unop/conversion. @@ -2013,30 +2017,30 @@ protected: markAsSideEffect(); } - void visitSubscript(Subscript *e) Q_DECL_OVERRIDE { - e->base->accept(this); - e->index->accept(this); + void visitSubscript(Subscript *e) { + visit(e->base); + visit(e->index); markAsSideEffect(); } - void visitMember(Member *e) Q_DECL_OVERRIDE { - e->base->accept(this); + void visitMember(Member *e) { + visit(e->base); if (e->freeOfSideEffects) return; markAsSideEffect(); } - void visitCall(Call *e) Q_DECL_OVERRIDE { - e->base->accept(this); + void visitCall(Call *e) { + visit(e->base); for (ExprList *args = e->args; args; args = args->next) - args->expr->accept(this); + visit(args->expr); markAsSideEffect(); // TODO: there are built-in functions that have no side effect. } - void visitNew(New *e) Q_DECL_OVERRIDE { - e->base->accept(this); + void visitNew(New *e) { + visit(e->base); for (ExprList *args = e->args; args; args = args->next) - args->expr->accept(this); + visit(args->expr); markAsSideEffect(); // TODO: there are built-in types that have no side effect. } }; @@ -2045,21 +2049,19 @@ class EliminateDeadCode: public SideEffectsChecker { DefUses &_defUses; StatementWorklist &_worklist; - QVector<Temp *> _collectedTemps; + QVarLengthArray<Temp *, 8> _collectedTemps; public: EliminateDeadCode(DefUses &defUses, StatementWorklist &worklist) : _defUses(defUses) , _worklist(worklist) - { - _collectedTemps.reserve(8); - } + {} void run(Expr *&expr, Stmt *stmt) { _collectedTemps.clear(); if (!hasSideEffects(expr)) { expr = 0; - foreach (Temp *t, _collectedTemps) { + for (Temp *t : _collectedTemps) { _defUses.removeUse(stmt, *t); _worklist += _defUses.defStmt(*t); } @@ -2067,13 +2069,13 @@ public: } protected: - void visitTemp(Temp *e) Q_DECL_OVERRIDE + void visitTemp(Temp *e) Q_DECL_OVERRIDE Q_DECL_FINAL { _collectedTemps.append(e); } }; -class PropagateTempTypes: public StmtVisitor, ExprVisitor +class PropagateTempTypes { const DefUses &defUses; UntypedTemp theTemp; @@ -2089,64 +2091,31 @@ public: newType = type; theTemp = temp; if (Stmt *defStmt = defUses.defStmt(temp.temp)) - defStmt->accept(this); + visit(defStmt); foreach (Stmt *use, defUses.uses(temp.temp)) - use->accept(this); - } - -protected: - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *e) { - if (theTemp == UntypedTemp(*e)) { - e->type = static_cast<Type>(newType.type); - e->memberResolver = newType.memberResolver; - } - } - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitConvert(Convert *e) { e->expr->accept(this); } - virtual void visitUnop(Unop *e) { e->expr->accept(this); } - virtual void visitBinop(Binop *e) { e->left->accept(this); e->right->accept(this); } - - virtual void visitCall(Call *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - virtual void visitNew(New *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - virtual void visitSubscript(Subscript *e) { - e->base->accept(this); - e->index->accept(this); - } - - virtual void visitMember(Member *e) { - e->base->accept(this); + visit(use); } - virtual void visitExp(Exp *s) {s->expr->accept(this);} - virtual void visitMove(Move *s) { - s->source->accept(this); - s->target->accept(this); +private: + void visit(Stmt *s) + { + STMT_VISIT_ALL_KINDS(s); } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { s->cond->accept(this); } - virtual void visitRet(Ret *s) { s->expr->accept(this); } - virtual void visitPhi(Phi *s) { - s->targetTemp->accept(this); - foreach (Expr *e, s->incoming) - e->accept(this); + void visit(Expr *e) + { + if (auto temp = e->asTemp()) { + if (theTemp == UntypedTemp(*temp)) { + temp->type = static_cast<Type>(newType.type); + temp->memberResolver = newType.memberResolver; + } + } else { + EXPR_VISIT_ALL_KINDS(e); + } } }; -class TypeInference: public StmtVisitor, public ExprVisitor +class TypeInference { enum { DebugTypeInference = 0 }; @@ -2248,7 +2217,7 @@ private: TypingResult ty; std::swap(_ty, ty); std::swap(_currentStmt, s); - _currentStmt->accept(this); + visit(_currentStmt); std::swap(_currentStmt, s); std::swap(_ty, ty); return ty.fullyTyped; @@ -2257,7 +2226,7 @@ private: TypingResult run(Expr *e) { TypingResult ty; std::swap(_ty, ty); - e->accept(this); + visit(e); std::swap(_ty, ty); if (ty.type != UnknownType) @@ -2299,39 +2268,74 @@ private: } } -protected: - virtual void visitConst(Const *e) { - if (e->type & NumberType) { - if (canConvertToSignedInteger(e->value)) +private: + void visit(Expr *e) + { + if (auto c = e->asConst()) { + visitConst(c); + } else if (auto s = e->asString()) { + visitString(s); + } else if (auto r = e->asRegExp()) { + visitRegExp(r); + } else if (auto n = e->asName()) { + visitName(n); + } else if (auto t = e->asTemp()) { + visitTemp(t); + } else if (auto a = e->asArgLocal()) { + visitArgLocal(a); + } else if (auto c = e->asClosure()) { + visitClosure(c); + } else if (auto c = e->asConvert()) { + visitConvert(c); + } else if (auto u = e->asUnop()) { + visitUnop(u); + } else if (auto b = e->asBinop()) { + visitBinop(b); + } else if (auto c = e->asCall()) { + visitCall(c); + } else if (auto n = e->asNew()) { + visitNew(n); + } else if (auto s = e->asSubscript()) { + visitSubscript(s); + } else if (auto m = e->asMember()) { + visitMember(m); + } else { + Q_UNREACHABLE(); + } + } + + void visitConst(Const *c) { + if (c->type & NumberType) { + if (canConvertToSignedInteger(c->value)) _ty = TypingResult(SInt32Type); - else if (canConvertToUnsignedInteger(e->value)) + else if (canConvertToUnsignedInteger(c->value)) _ty = TypingResult(UInt32Type); else - _ty = TypingResult(e->type); + _ty = TypingResult(c->type); } else - _ty = TypingResult(e->type); + _ty = TypingResult(c->type); } - virtual void visitString(IR::String *) { _ty = TypingResult(StringType); } - virtual void visitRegExp(IR::RegExp *) { _ty = TypingResult(VarType); } - virtual void visitName(Name *) { _ty = TypingResult(VarType); } - virtual void visitTemp(Temp *e) { + void visitString(IR::String *) { _ty = TypingResult(StringType); } + void visitRegExp(IR::RegExp *) { _ty = TypingResult(VarType); } + void visitName(Name *) { _ty = TypingResult(VarType); } + void visitTemp(Temp *e) { if (e->memberResolver && e->memberResolver->isValid()) _ty = TypingResult(e->memberResolver); else _ty = TypingResult(_tempTypes[e->index]); setType(e, _ty.type); } - virtual void visitArgLocal(ArgLocal *e) { + void visitArgLocal(ArgLocal *e) { _ty = TypingResult(VarType); setType(e, _ty.type); } - virtual void visitClosure(Closure *) { _ty = TypingResult(VarType); } - virtual void visitConvert(Convert *e) { + void visitClosure(Closure *) { _ty = TypingResult(VarType); } + void visitConvert(Convert *e) { _ty = TypingResult(e->type); } - virtual void visitUnop(Unop *e) { + void visitUnop(Unop *e) { _ty = run(e->expr); switch (e->op) { case OpUPlus: _ty.type = DoubleType; return; @@ -2348,7 +2352,7 @@ protected: } } - virtual void visitBinop(Binop *e) { + void visitBinop(Binop *e) { TypingResult leftTy = run(e->left); TypingResult rightTy = run(e->right); _ty.fullyTyped = leftTy.fullyTyped && rightTy.fullyTyped; @@ -2406,24 +2410,24 @@ protected: } } - virtual void visitCall(Call *e) { + void visitCall(Call *e) { _ty = run(e->base); for (ExprList *it = e->args; it; it = it->next) _ty.fullyTyped &= run(it->expr).fullyTyped; _ty.type = VarType; } - virtual void visitNew(New *e) { + void visitNew(New *e) { _ty = run(e->base); for (ExprList *it = e->args; it; it = it->next) _ty.fullyTyped &= run(it->expr).fullyTyped; _ty.type = VarType; } - virtual void visitSubscript(Subscript *e) { + void visitSubscript(Subscript *e) { _ty.fullyTyped = run(e->base).fullyTyped && run(e->index).fullyTyped; _ty.type = VarType; } - virtual void visitMember(Member *e) { + void visitMember(Member *e) { _ty = run(e->base); if (_ty.fullyTyped && _ty.type.memberResolver && _ty.type.memberResolver->isValid()) { @@ -2433,8 +2437,27 @@ protected: _ty.type = VarType; } - virtual void visitExp(Exp *s) { _ty = run(s->expr); } - virtual void visitMove(Move *s) { + void visit(Stmt *s) + { + if (auto e = s->asExp()) { + visitExp(e); + } else if (auto m = s->asMove()) { + visitMove(m); + } else if (auto j = s->asJump()) { + visitJump(j); + } else if (auto c = s->asCJump()) { + visitCJump(c); + } else if (auto r = s->asRet()) { + visitRet(r); + } else if (auto p = s->asPhi()) { + visitPhi(p); + } else { + Q_UNREACHABLE(); + } + } + + void visitExp(Exp *s) { _ty = run(s->expr); } + void visitMove(Move *s) { if (Temp *t = s->target->asTemp()) { if (Name *n = s->source->asName()) { if (n->builtin == Name::builtin_qml_context) { @@ -2455,10 +2478,10 @@ protected: _ty.fullyTyped &= sourceTy.fullyTyped; } - virtual void visitJump(Jump *) { _ty = TypingResult(MissingType); } - virtual void visitCJump(CJump *s) { _ty = run(s->cond); } - virtual void visitRet(Ret *s) { _ty = run(s->expr); } - virtual void visitPhi(Phi *s) { + void visitJump(Jump *) { _ty = TypingResult(MissingType); } + void visitCJump(CJump *s) { _ty = run(s->cond); } + void visitRet(Ret *s) { _ty = run(s->expr); } + void visitPhi(Phi *s) { _ty = run(s->incoming[0]); for (int i = 1, ei = s->incoming.size(); i != ei; ++i) { TypingResult ty = run(s->incoming[i]); @@ -2677,14 +2700,15 @@ void convertConst(Const *c, Type targetType) c->type = targetType; } -class TypePropagation: public StmtVisitor, public ExprVisitor { +class TypePropagation +{ DefUses &_defUses; Type _ty; IR::Function *_f; bool run(Expr *&e, Type requestedType = UnknownType, bool insertConversion = true) { qSwap(_ty, requestedType); - e->accept(this); + visit(e); qSwap(_ty, requestedType); if (requestedType != UnknownType) { @@ -2731,7 +2755,7 @@ public: for (Stmt *s : bb->statements()) { _currStmt = s; - s->accept(this); + visit(s); } foreach (const Conversion &conversion, _conversions) { @@ -2817,8 +2841,29 @@ public: } } -protected: - virtual void visitConst(Const *c) { +private: + void visit(Expr *e) + { + if (auto c = e->asConst()) { + visitConst(c); + } else if (auto c = e->asConvert()) { + run(c->expr, c->type); + } else if (auto u = e->asUnop()) { + run(u->expr, u->type); + } else if (auto b = e->asBinop()) { + visitBinop(b); + } else if (auto c = e->asCall()) { + visitCall(c); + } else if (auto n = e->asNew()) { + visitNew(n); + } else if (auto s = e->asSubscript()) { + visitSubscript(s); + } else if (auto m = e->asMember()) { + visitMember(m); + } + } + + void visitConst(Const *c) { if (_ty & NumberType && c->type & NumberType) { if (_ty == SInt32Type) c->value = QV4::Primitive::toInt32(c->value); @@ -2828,15 +2873,7 @@ protected: } } - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitConvert(Convert *e) { run(e->expr, e->type); } - virtual void visitUnop(Unop *e) { run(e->expr, e->type); } - virtual void visitBinop(Binop *e) { + void visitBinop(Binop *e) { // FIXME: This routine needs more tuning! switch (e->op) { case OpAdd: @@ -2887,20 +2924,36 @@ protected: Q_UNREACHABLE(); } } - virtual void visitCall(Call *e) { + void visitCall(Call *e) { run(e->base); for (ExprList *it = e->args; it; it = it->next) run(it->expr); } - virtual void visitNew(New *e) { + void visitNew(New *e) { run(e->base); for (ExprList *it = e->args; it; it = it->next) run(it->expr); } - virtual void visitSubscript(Subscript *e) { run(e->base); run(e->index); } - virtual void visitMember(Member *e) { run(e->base); } - virtual void visitExp(Exp *s) { run(s->expr); } - virtual void visitMove(Move *s) { + void visitSubscript(Subscript *e) { run(e->base); run(e->index); } + void visitMember(Member *e) { run(e->base); } + + void visit(Stmt *s) + { + if (auto e = s->asExp()) { + visitExp(e); + } else if (auto m = s->asMove()) { + visitMove(m); + } else if (auto c = s->asCJump()) { + visitCJump(c); + } else if (auto r = s->asRet()) { + visitRet(r); + } else if (auto p = s->asPhi()) { + visitPhi(p); + } + } + + void visitExp(Exp *s) { run(s->expr); } + void visitMove(Move *s) { if (s->source->asConvert()) return; // this statement got inserted for a phi-node type conversion @@ -2925,12 +2978,11 @@ protected: run(s->source, s->target->type, !inhibitConversion); } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { + void visitCJump(CJump *s) { run(s->cond, BoolType); } - virtual void visitRet(Ret *s) { run(s->expr); } - virtual void visitPhi(Phi *s) { + void visitRet(Ret *s) { run(s->expr); } + void visitPhi(Phi *s) { Type ty = s->targetTemp->type; for (int i = 0, ei = s->incoming.size(); i != ei; ++i) run(s->incoming[i], ty); @@ -3299,6 +3351,15 @@ class BlockScheduler // this is a loop, where there in -> candidate edge is the jump back to the top of the loop. continue; + if (in == 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 } @@ -3504,7 +3565,7 @@ static Expr *clone(Expr *e, IR::Function *function) { } } -class ExprReplacer: public StmtVisitor, public ExprVisitor +class ExprReplacer { DefUses &_defUses; IR::Function* _function; @@ -3535,7 +3596,7 @@ public: // qout << " " << uses.size() << " uses:"<<endl; foreach (Stmt *use, uses) { // qout<<" ";use->dump(qout);qout<<"\n"; - use->accept(this); + visit(use); // qout<<" -> ";use->dump(qout);qout<<"\n"; W += use; if (newUses) @@ -3546,45 +3607,101 @@ public: qSwap(_toReplace, toReplace); } -protected: - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitConvert(Convert *e) { check(e->expr); } - virtual void visitUnop(Unop *e) { check(e->expr); } - virtual void visitBinop(Binop *e) { check(e->left); check(e->right); } - virtual void visitCall(Call *e) { +private: + void visit(Expr *e) + { + if (auto c = e->asConst()) { + visitConst(c); + } else if (auto s = e->asString()) { + visitString(s); + } else if (auto r = e->asRegExp()) { + visitRegExp(r); + } else if (auto n = e->asName()) { + visitName(n); + } else if (auto t = e->asTemp()) { + visitTemp(t); + } else if (auto a = e->asArgLocal()) { + visitArgLocal(a); + } else if (auto c = e->asClosure()) { + visitClosure(c); + } else if (auto c = e->asConvert()) { + visitConvert(c); + } else if (auto u = e->asUnop()) { + visitUnop(u); + } else if (auto b = e->asBinop()) { + visitBinop(b); + } else if (auto c = e->asCall()) { + visitCall(c); + } else if (auto n = e->asNew()) { + visitNew(n); + } else if (auto s = e->asSubscript()) { + visitSubscript(s); + } else if (auto m = e->asMember()) { + visitMember(m); + } else { + Q_UNREACHABLE(); + } + } + + void visitConst(Const *) {} + void visitString(IR::String *) {} + void visitRegExp(IR::RegExp *) {} + void visitName(Name *) {} + void visitTemp(Temp *) {} + void visitArgLocal(ArgLocal *) {} + void visitClosure(Closure *) {} + void visitConvert(Convert *e) { check(e->expr); } + void visitUnop(Unop *e) { check(e->expr); } + void visitBinop(Binop *e) { check(e->left); check(e->right); } + void visitCall(Call *e) { check(e->base); for (ExprList *it = e->args; it; it = it->next) check(it->expr); } - virtual void visitNew(New *e) { + void visitNew(New *e) { check(e->base); for (ExprList *it = e->args; it; it = it->next) check(it->expr); } - virtual void visitSubscript(Subscript *e) { check(e->base); check(e->index); } - virtual void visitMember(Member *e) { check(e->base); } - virtual void visitExp(Exp *s) { check(s->expr); } - virtual void visitMove(Move *s) { check(s->target); check(s->source); } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { check(s->cond); } - virtual void visitRet(Ret *s) { check(s->expr); } - virtual void visitPhi(Phi *s) { + void visitSubscript(Subscript *e) { check(e->base); check(e->index); } + void visitMember(Member *e) { check(e->base); } + + void visit(Stmt *s) + { + if (auto e = s->asExp()) { + visitExp(e); + } else if (auto m = s->asMove()) { + visitMove(m); + } else if (auto j = s->asJump()) { + visitJump(j); + } else if (auto c = s->asCJump()) { + visitCJump(c); + } else if (auto r = s->asRet()) { + visitRet(r); + } else if (auto p = s->asPhi()) { + visitPhi(p); + } else { + Q_UNREACHABLE(); + } + } + + void visitExp(Exp *s) { check(s->expr); } + void visitMove(Move *s) { check(s->target); check(s->source); } + void visitJump(Jump *) {} + void visitCJump(CJump *s) { check(s->cond); } + void visitRet(Ret *s) { check(s->expr); } + void visitPhi(Phi *s) { for (int i = 0, ei = s->incoming.size(); i != ei; ++i) check(s->incoming[i]); } private: void check(Expr *&e) { - if (equals(e, _toReplace)) + if (equals(e, _toReplace)) { e = clone(_replacement, _function); - else - e->accept(this); + } else { + visit(e); + } } // This only calculates equality for everything needed by constant propagation @@ -3625,6 +3742,8 @@ namespace { void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUses, StatementWorklist &W, DominatorTree &dt) { + enum { DebugUnlinking = 0 }; + struct Util { static void removeIncomingEdge(BasicBlock *from, BasicBlock *to, DefUses &defUses, StatementWorklist &W) { @@ -3663,11 +3782,17 @@ void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUs } }; + Q_ASSERT(!from->isRemoved()); + Q_ASSERT(!to->isRemoved()); + // don't purge blocks that are entry points for catch statements. They might not be directly // connected, but are required anyway if (to->isExceptionHandler()) return; + if (DebugUnlinking) + qDebug("Unlinking L%d -> L%d...", from->index(), to->index()); + // First, unlink the edge from->out.removeOne(to); Util::removeIncomingEdge(from, to, defUses, W); @@ -3677,8 +3802,12 @@ void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUs // Check if the target is still reachable... if (Util::isReachable(to, dt)) { // yes, recalculate the immediate dominator, and we're done. + if (DebugUnlinking) + qDebug(".. L%d is still reachable, recalulate idom.", to->index()); dt.collectSiblings(to, siblings); } else { + if (DebugUnlinking) + qDebug(".. L%d is unreachable, purging it:", to->index()); // The target is unreachable, so purge it: QVector<BasicBlock *> toPurge; toPurge.reserve(8); @@ -3686,6 +3815,8 @@ void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUs while (!toPurge.isEmpty()) { BasicBlock *bb = toPurge.first(); toPurge.removeFirst(); + if (DebugUnlinking) + qDebug("... purging L%d", bb->index()); if (bb->isRemoved()) continue; @@ -3729,6 +3860,8 @@ void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUs } dt.recalculateIDoms(siblings); + if (DebugUnlinking) + qDebug("Unlinking done."); } bool tryOptimizingComparison(Expr *&expr) @@ -3748,42 +3881,42 @@ bool tryOptimizingComparison(Expr *&expr) switch (b->op) { case OpGt: - leftConst->value = Runtime::compareGreaterThan(l, r); + leftConst->value = Runtime::method_compareGreaterThan(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpLt: - leftConst->value = Runtime::compareLessThan(l, r); + leftConst->value = Runtime::method_compareLessThan(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpGe: - leftConst->value = Runtime::compareGreaterEqual(l, r); + leftConst->value = Runtime::method_compareGreaterEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpLe: - leftConst->value = Runtime::compareLessEqual(l, r); + leftConst->value = Runtime::method_compareLessEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpStrictEqual: - leftConst->value = Runtime::compareStrictEqual(l, r); + leftConst->value = Runtime::method_compareStrictEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpEqual: - leftConst->value = Runtime::compareEqual(l, r); + leftConst->value = Runtime::method_compareEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpStrictNotEqual: - leftConst->value = Runtime::compareStrictNotEqual(l, r); + leftConst->value = Runtime::method_compareStrictNotEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpNotEqual: - leftConst->value = Runtime::compareNotEqual(l, r); + leftConst->value = Runtime::method_compareNotEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; @@ -4153,7 +4286,8 @@ void optimizeSSA(StatementWorklist &W, DefUses &defUses, DominatorTree &df) } //### TODO: use DefUses from the optimizer, because it already has all this information -class InputOutputCollector: protected StmtVisitor, protected ExprVisitor { +class InputOutputCollector +{ void setOutput(Temp *out) { Q_ASSERT(!output); @@ -4170,48 +4304,33 @@ public: void collect(Stmt *s) { inputs.resize(0); output = 0; - s->accept(this); + visit(s); } -protected: - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *e) { - inputs.push_back(e); - } - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitConvert(Convert *e) { e->expr->accept(this); } - virtual void visitUnop(Unop *e) { e->expr->accept(this); } - virtual void visitBinop(Binop *e) { e->left->accept(this); e->right->accept(this); } - virtual void visitCall(Call *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - virtual void visitNew(New *e) { - e->base->accept(this); - for (ExprList *it = e->args; it; it = it->next) - it->expr->accept(this); - } - virtual void visitSubscript(Subscript *e) { e->base->accept(this); e->index->accept(this); } - virtual void visitMember(Member *e) { e->base->accept(this); } - virtual void visitExp(Exp *s) { s->expr->accept(this); } - virtual void visitMove(Move *s) { - s->source->accept(this); - if (Temp *t = s->target->asTemp()) { - setOutput(t); +private: + void visit(Expr *e) + { + if (auto t = e->asTemp()) { + inputs.push_back(t); } else { - s->target->accept(this); + EXPR_VISIT_ALL_KINDS(e); } } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { s->cond->accept(this); } - virtual void visitRet(Ret *s) { s->expr->accept(this); } - virtual void visitPhi(Phi *) { - // Handled separately + + void visit(Stmt *s) + { + if (auto m = s->asMove()) { + visit(m->source); + if (Temp *t = m->target->asTemp()) { + setOutput(t); + } else { + visit(m->target); + } + } else if (s->asPhi()) { + // Handled separately + } else { + STMT_VISIT_ALL_KINDS(s); + } } }; @@ -4224,7 +4343,59 @@ protected: * See LifeTimeIntervals::renumber for details on the numbering. */ class LifeRanges { - typedef QSet<Temp> LiveRegs; + class LiveRegs + { + typedef std::vector<int> Storage; + Storage regs; + + public: + void insert(int r) + { + if (find(r) == end()) + regs.push_back(r); + } + + void unite(const LiveRegs &other) + { + if (other.empty()) + return; + if (empty()) { + regs = other.regs; + return; + } + for (int r : other.regs) + insert(r); + } + + typedef Storage::iterator iterator; + iterator find(int r) + { return std::find(regs.begin(), regs.end(), r); } + + iterator begin() + { return regs.begin(); } + + iterator end() + { return regs.end(); } + + void erase(iterator it) + { regs.erase(it); } + + void remove(int r) + { + iterator it = find(r); + if (it != end()) + erase(it); + } + + bool empty() const + { return regs.empty(); } + + int size() const + { return int(regs.size()); } + + int at(int idx) const + { return regs.at(idx); } + }; std::vector<LiveRegs> _liveIn; std::vector<LifeTimeInterval *> _intervals; @@ -4232,14 +4403,21 @@ class LifeRanges { LifeTimeInterval &interval(const Temp *temp) { - LifeTimeInterval *<i = _intervals[temp->index]; - if (Q_UNLIKELY(!lti)) { - lti = new LifeTimeInterval; - lti->setTemp(*temp); - } + LifeTimeInterval *lti = _intervals[temp->index]; + Q_ASSERT(lti); return *lti; } + void ensureInterval(const IR::Temp &temp) + { + Q_ASSERT(!temp.isInvalid()); + LifeTimeInterval *<i = _intervals[temp.index]; + if (lti) + return; + lti = new LifeTimeInterval; + lti->setTemp(temp); + } + int defPosition(IR::Stmt *s) const { return usePosition(s) + 1; @@ -4293,13 +4471,13 @@ public: IRPrinter printer(&qout); for (size_t i = 0, ei = _liveIn.size(); i != ei; ++i) { qout << "L" << i <<" live-in: "; - QList<Temp> live = QList<Temp>::fromSet(_liveIn.at(i)); - if (live.isEmpty()) + auto live = _liveIn.at(i); + if (live.empty()) qout << "(none)"; std::sort(live.begin(), live.end()); for (int i = 0; i < live.size(); ++i) { if (i > 0) qout << ", "; - printer.print(&live[i]); + qout << '%' << live.at(i); } qout << endl; } @@ -4318,8 +4496,10 @@ private: for (Stmt *s : successor->statements()) { if (Phi *phi = s->asPhi()) { - if (Temp *t = phi->incoming.at(bbIndex)->asTemp()) - live.insert(*t); + if (Temp *t = phi->incoming.at(bbIndex)->asTemp()) { + ensureInterval(*t); + live.insert(t->index); + } } else { break; } @@ -4328,14 +4508,15 @@ private: const QVector<Stmt *> &statements = bb->statements(); - foreach (const Temp &opd, live) - interval(&opd).addRange(start(bb), end(bb)); + for (int reg : live) + _intervals[reg]->addRange(start(bb), end(bb)); InputOutputCollector collector; for (int i = statements.size() - 1; i >= 0; --i) { Stmt *s = statements.at(i); if (Phi *phi = s->asPhi()) { - LiveRegs::iterator it = live.find(*phi->targetTemp); + ensureInterval(*phi->targetTemp); + LiveRegs::iterator it = live.find(phi->targetTemp->index); if (it == live.end()) { // a phi node target that is only defined, but never used interval(phi->targetTemp).setFrom(start(bb)); @@ -4348,25 +4529,27 @@ private: collector.collect(s); //### TODO: use DefUses from the optimizer, because it already has all this information if (Temp *opd = collector.output) { + ensureInterval(*opd); LifeTimeInterval <i = interval(opd); lti.setFrom(defPosition(s)); - live.remove(lti.temp()); + live.remove(lti.temp().index); _sortedIntervals->add(<i); } //### TODO: use DefUses from the optimizer, because it already has all this information for (size_t i = 0, ei = collector.inputs.size(); i != ei; ++i) { Temp *opd = collector.inputs[i]; + ensureInterval(*opd); interval(opd).addRange(start(bb), usePosition(s)); - live.insert(*opd); + live.insert(opd->index); } } if (loopEnd) { // Meaning: bb is a loop header, because loopEnd is set to non-null. - foreach (const Temp &opd, live) - interval(&opd).addRange(start(bb), usePosition(loopEnd->terminator())); + for (int reg : live) + _intervals[reg]->addRange(start(bb), usePosition(loopEnd->terminator())); } - _liveIn[bb->index()] = live; + _liveIn[bb->index()] = std::move(live); } }; @@ -4381,7 +4564,7 @@ void removeUnreachleBlocks(IR::Function *function) function->renumberBasicBlocks(); } -class ConvertArgLocals: protected StmtVisitor, protected ExprVisitor +class ConvertArgLocals { public: ConvertArgLocals(IR::Function *function) @@ -4419,10 +4602,13 @@ public: } } - for (BasicBlock *bb : function->basicBlocks()) - if (!bb->isRemoved()) - for (Stmt *s : bb->statements()) - s->accept(this); + for (BasicBlock *bb : function->basicBlocks()) { + if (!bb->isRemoved()) { + for (Stmt *s : bb->statements()) { + visit(s); + } + } + } if (convertArgs && function->formals.size() > 0) function->basicBlock(0)->prependStatements(extraMoves); @@ -4430,39 +4616,45 @@ public: function->locals.clear(); } -protected: - virtual void visitConst(Const *) {} - virtual void visitString(IR::String *) {} - virtual void visitRegExp(IR::RegExp *) {} - virtual void visitName(Name *) {} - virtual void visitTemp(Temp *) {} - virtual void visitArgLocal(ArgLocal *) {} - virtual void visitClosure(Closure *) {} - virtual void visitConvert(Convert *e) { check(e->expr); } - virtual void visitUnop(Unop *e) { check(e->expr); } - virtual void visitBinop(Binop *e) { check(e->left); check(e->right); } - virtual void visitCall(Call *e) { - check(e->base); - for (ExprList *it = e->args; it; it = it->next) - check(it->expr); - } - virtual void visitNew(New *e) { - check(e->base); - for (ExprList *it = e->args; it; it = it->next) - check(it->expr); +private: + void visit(Stmt *s) + { + if (auto e = s->asExp()) { + check(e->expr); + } else if (auto m = s->asMove()) { + check(m->target); check(m->source); + } else if (auto c = s->asCJump()) { + check(c->cond); + } else if (auto r = s->asRet()) { + check(r->expr); + } } - virtual void visitSubscript(Subscript *e) { check(e->base); check(e->index); } - virtual void visitMember(Member *e) { check(e->base); } - virtual void visitExp(Exp *s) { check(s->expr); } - virtual void visitMove(Move *s) { check(s->target); check(s->source); } - virtual void visitJump(Jump *) {} - virtual void visitCJump(CJump *s) { check(s->cond); } - virtual void visitRet(Ret *s) { check(s->expr); } - virtual void visitPhi(Phi *) { - Q_UNREACHABLE(); + + void visit(Expr *e) + { + if (auto c = e->asConvert()) { + check(c->expr); + } else if (auto u = e->asUnop()) { + check(u->expr); + } else if (auto b = e->asBinop()) { + check(b->left); check(b->right); + } else if (auto c = e->asCall()) { + check(c->base); + for (ExprList *it = c->args; it; it = it->next) { + check(it->expr); + } + } else if (auto n = e->asNew()) { + check(n->base); + for (ExprList *it = n->args; it; it = it->next) { + check(it->expr); + } + } else if (auto s = e->asSubscript()) { + check(s->base); check(s->index); + } else if (auto m = e->asMember()) { + check(m->base); + } } -private: void check(Expr *&e) { if (ArgLocal *al = e->asArgLocal()) { if (al->kind == ArgLocal::Local) { @@ -4475,7 +4667,7 @@ private: e = t; } } else { - e->accept(this); + visit(e); } } @@ -4498,7 +4690,7 @@ private: std::vector<int> tempForLocal; }; -class CloneBasicBlock: protected IR::StmtVisitor, protected CloneExpr +class CloneBasicBlock: protected CloneExpr { public: BasicBlock *operator()(IR::BasicBlock *originalBlock) @@ -4506,38 +4698,37 @@ public: block = new BasicBlock(originalBlock->function, 0); for (Stmt *s : originalBlock->statements()) { - s->accept(this); + visit(s); clonedStmt->location = s->location; } return block; } -protected: - virtual void visitExp(Exp *stmt) - { clonedStmt = block->EXP(clone(stmt->expr)); } - - virtual void visitMove(Move *stmt) - { clonedStmt = block->MOVE(clone(stmt->target), clone(stmt->source)); } - - virtual void visitJump(Jump *stmt) - { clonedStmt = block->JUMP(stmt->target); } - - virtual void visitCJump(CJump *stmt) - { clonedStmt = block->CJUMP(clone(stmt->cond), stmt->iftrue, stmt->iffalse); } - - virtual void visitRet(Ret *stmt) - { clonedStmt = block->RET(clone(stmt->expr)); } - - virtual void visitPhi(Phi *stmt) +private: + void visit(Stmt *s) { - Phi *phi = block->function->NewStmt<Phi>(); - clonedStmt = phi; - - phi->targetTemp = clone(stmt->targetTemp); - foreach (Expr *in, stmt->incoming) - phi->incoming.append(clone(in)); - block->appendStatement(phi); + if (auto e = s->asExp()) { + clonedStmt = block->EXP(clone(e->expr)); + } else if (auto m = s->asMove()) { + clonedStmt = block->MOVE(clone(m->target), clone(m->source)); + } else if (auto j = s->asJump()) { + clonedStmt = block->JUMP(j->target); + } else if (auto c = s->asCJump()) { + clonedStmt = block->CJUMP(clone(c->cond), c->iftrue, c->iffalse); + } else if (auto r = s->asRet()) { + clonedStmt = block->RET(clone(r->expr)); + } else if (auto p = s->asPhi()) { + Phi *phi = block->function->NewStmt<Phi>(); + clonedStmt = phi; + + phi->targetTemp = clone(p->targetTemp); + foreach (Expr *in, p->incoming) + phi->incoming.append(clone(in)); + block->appendStatement(phi); + } else { + Q_UNREACHABLE(); + } } private: @@ -4706,13 +4897,20 @@ static void verifyCFG(IR::Function *function) Q_ASSERT(function->basicBlock(bb->index()) == bb); // Check the terminators: - if (Jump *jump = bb->terminator()->asJump()) { + Stmt *terminator = bb->terminator(); + if (terminator == nullptr) { + Stmt *last = bb->statements().last(); + Call *call = last->asExp()->expr->asCall(); + Name *baseName = call->base->asName(); + Q_ASSERT(baseName->builtin == Name::builtin_rethrow); + Q_UNUSED(baseName); + } else if (Jump *jump = terminator->asJump()) { Q_UNUSED(jump); Q_ASSERT(jump->target); Q_ASSERT(!jump->target->isRemoved()); Q_ASSERT(bb->out.size() == 1); Q_ASSERT(bb->out.first() == jump->target); - } else if (CJump *cjump = bb->terminator()->asCJump()) { + } else if (CJump *cjump = terminator->asCJump()) { Q_UNUSED(cjump); Q_ASSERT(bb->out.size() == 2); Q_ASSERT(cjump->iftrue); @@ -4721,7 +4919,7 @@ static void verifyCFG(IR::Function *function) Q_ASSERT(cjump->iffalse); Q_ASSERT(!cjump->iffalse->isRemoved()); Q_ASSERT(cjump->iffalse == bb->out[1]); - } else if (bb->terminator()->asRet()) { + } else if (terminator->asRet()) { Q_ASSERT(bb->out.size() == 0); } else { Q_UNREACHABLE(); @@ -4769,7 +4967,7 @@ static void verifyNoPointerSharing(IR::Function *function) if (!DoVerification) return; - class : public StmtVisitor, public ExprVisitor { + class { public: void operator()(IR::Function *f) { @@ -4777,44 +4975,23 @@ static void verifyNoPointerSharing(IR::Function *function) if (bb->isRemoved()) continue; - for (Stmt *s : bb->statements()) - s->accept(this); + for (Stmt *s : bb->statements()) { + visit(s); + } } } - protected: - virtual void visitExp(Exp *s) { check(s); s->expr->accept(this); } - virtual void visitMove(Move *s) { check(s); s->target->accept(this); s->source->accept(this); } - virtual void visitJump(Jump *s) { check(s); } - virtual void visitCJump(CJump *s) { check(s); s->cond->accept(this); } - virtual void visitRet(Ret *s) { check(s); s->expr->accept(this); } - virtual void visitPhi(Phi *s) + private: + void visit(Stmt *s) { check(s); - s->targetTemp->accept(this); - foreach (Expr *e, s->incoming) - e->accept(this); - } - - virtual void visitConst(Const *e) { check(e); } - virtual void visitString(IR::String *e) { check(e); } - virtual void visitRegExp(IR::RegExp *e) { check(e); } - virtual void visitName(Name *e) { check(e); } - virtual void visitTemp(Temp *e) { check(e); } - virtual void visitArgLocal(ArgLocal *e) { check(e); } - virtual void visitClosure(Closure *e) { check(e); } - virtual void visitConvert(Convert *e) { check(e); e->expr->accept(this); } - virtual void visitUnop(Unop *e) { check(e); e->expr->accept(this); } - virtual void visitBinop(Binop *e) { check(e); e->left->accept(this); e->right->accept(this); } - virtual void visitCall(Call *e) { check(e); e->base->accept(this); check(e->args); } - virtual void visitNew(New *e) { check(e); e->base->accept(this); check(e->args); } - virtual void visitSubscript(Subscript *e) { check(e); e->base->accept(this); e->index->accept(this); } - virtual void visitMember(Member *e) { check(e); e->base->accept(this); } - - void check(ExprList *l) + STMT_VISIT_ALL_KINDS(s); + } + + void visit(Expr *e) { - for (ExprList *it = l; it; it = it->next) - check(it->expr); + check(e); + EXPR_VISIT_ALL_KINDS(e); } private: @@ -4836,7 +5013,7 @@ static void verifyNoPointerSharing(IR::Function *function) V(function); } -class RemoveLineNumbers: public SideEffectsChecker, public StmtVisitor +class RemoveLineNumbers: private SideEffectsChecker { public: static void run(IR::Function *function) @@ -4854,28 +5031,99 @@ public: } private: + ~RemoveLineNumbers() {} + static bool hasSideEffects(Stmt *stmt) { RemoveLineNumbers checker; - stmt->accept(&checker); + if (auto e = stmt->asExp()) { + checker.visit(e->expr); + } else if (auto m = stmt->asMove()) { + checker.visit(m->source); + if (!checker.seenSideEffects()) { + checker.visit(m->target); + } + } else if (auto c = stmt->asCJump()) { + checker.visit(c->cond); + } else if (auto r = stmt->asRet()) { + checker.visit(r->expr); + } return checker.seenSideEffects(); } - void visitExp(Exp *s) Q_DECL_OVERRIDE { s->expr->accept(this); } - void visitMove(Move *s) Q_DECL_OVERRIDE { s->source->accept(this); s->target->accept(this); } - void visitJump(Jump *) Q_DECL_OVERRIDE {} - void visitCJump(CJump *s) Q_DECL_OVERRIDE { s->cond->accept(this); } - void visitRet(Ret *s) Q_DECL_OVERRIDE { s->expr->accept(this); } - void visitPhi(Phi *) Q_DECL_OVERRIDE {} + void visitTemp(Temp *) Q_DECL_OVERRIDE Q_DECL_FINAL {} }; +void mergeBasicBlocks(IR::Function *function, DefUses *du, DominatorTree *dt) +{ + enum { DebugBlockMerging = 0 }; + + if (function->hasTry) + return; + + showMeTheCode(function, "Before basic block merging"); + + // Now merge a basic block with its successor when there is one outgoing edge, and the + // successor has one incoming edge. + for (int i = 0, ei = function->basicBlockCount(); i != ei; ++i) { + BasicBlock *bb = function->basicBlock(i); + + bb->nextLocation = QQmlJS::AST::SourceLocation(); // make sure appendStatement doesn't mess with the line info + + if (bb->isRemoved()) continue; // the block has been removed, so ignore it + if (bb->out.size() != 1) continue; // more than one outgoing edge + BasicBlock *successor = bb->out.first(); + if (successor->in.size() != 1) continue; // more than one incoming edge + + // Loop header? No efficient way to update the other blocks that refer to this as containing group, + // so don't do merging yet. + if (successor->isGroupStart()) continue; + + // Ok, we can merge the two basic blocks. + if (DebugBlockMerging) { + qDebug("Merging L%d into L%d", successor->index(), bb->index()); + } + Q_ASSERT(bb->terminator()->asJump()); + bb->removeStatement(bb->statementCount() - 1); // remove the terminator, and replace it with: + for (Stmt *s : successor->statements()) { + bb->appendStatement(s); // add all statements from the successor to the current basic block + if (auto cjump = s->asCJump()) + cjump->parent = bb; + } + bb->out = successor->out; // set the outgoing edges to the successor's so they're now in sync with our new terminator + for (auto newSuccessor : bb->out) { + for (auto &backlink : newSuccessor->in) { + if (backlink == successor) { + backlink = bb; // for all successors of our successor: set the incoming edges to come from bb, because we'll now jump there. + } + } + } + if (du) { + // all statements in successor have moved to bb, so make sure that the containing blocks + // stored in DefUses get updated (meaning: point to bb) + du->replaceBasicBlock(successor, bb); + } + if (dt) { + // update the immediate dominators to: any block that was dominated by the successor + // will now need to point to bb's immediate dominator. The reason is that bb itself + // won't be anyones immediate dominator, because it had just one outgoing edge. + dt->mergeIntoPredecessor(successor); + } + function->removeBasicBlock(successor); + --i; // re-run on the current basic-block, so any chain gets collapsed. + } + + showMeTheCode(function, "After basic block merging"); + verifyCFG(function); +} + } // anonymous namespace void LifeTimeInterval::setFrom(int from) { Q_ASSERT(from > 0); if (_ranges.isEmpty()) { // this is the case where there is no use, only a define - _ranges.push_front(Range(from, from)); + _ranges.prepend(Range(from, from)); if (_end == InvalidPosition) _end = from; } else { @@ -4889,7 +5137,7 @@ void LifeTimeInterval::addRange(int from, int to) { Q_ASSERT(to >= from); if (_ranges.isEmpty()) { - _ranges.push_front(Range(from, to)); + _ranges.prepend(Range(from, to)); _end = to; return; } @@ -4904,12 +5152,12 @@ void LifeTimeInterval::addRange(int from, int to) { break; p1->start = qMin(p->start, p1->start); p1->end = qMax(p->end, p1->end); - _ranges.pop_front(); + _ranges.remove(0); p = &_ranges.first(); } } else { if (to < p->start) { - _ranges.push_front(Range(from, to)); + _ranges.prepend(Range(from, to)); } else { Q_ASSERT(from > _ranges.last().end); _ranges.push_back(Range(from, to)); @@ -4950,7 +5198,7 @@ LifeTimeInterval LifeTimeInterval::split(int atPosition, int newStart) } if (newInterval._ranges.first().end == atPosition) - newInterval._ranges.removeFirst(); + newInterval._ranges.remove(0); if (newStart == InvalidPosition) { // the temp stays inactive for the rest of its lifetime @@ -4970,7 +5218,7 @@ LifeTimeInterval LifeTimeInterval::split(int atPosition, int newStart) break; } else { // the temp stays inactive for this interval, so remove it. - newInterval._ranges.removeFirst(); + newInterval._ranges.remove(0); } } Q_ASSERT(!newInterval._ranges.isEmpty()); @@ -5001,15 +5249,6 @@ void LifeTimeInterval::dump(QTextStream &out) const { out << " (register " << _reg << ")"; } -bool LifeTimeInterval::lessThan(const LifeTimeInterval *r1, const LifeTimeInterval *r2) { - if (r1->_ranges.first().start == r2->_ranges.first().start) { - if (r1->isSplitFromInterval() == r2->isSplitFromInterval()) - return r1->_ranges.last().end < r2->_ranges.last().end; - else - return r1->isSplitFromInterval(); - } else - return r1->_ranges.first().start < r2->_ranges.first().start; -} bool LifeTimeInterval::lessThanForTemp(const LifeTimeInterval *r1, const LifeTimeInterval *r2) { @@ -5103,6 +5342,8 @@ void Optimizer::run(QQmlEnginePrivate *qmlEngine, bool doTypeInference, bool pee if (!function->hasTry && !function->hasWith && !function->module->debugMode && doSSA && statementCount <= 300) { // qout << "SSA for " << (function->name ? qPrintable(*function->name) : "<anonymous>") << endl; + mergeBasicBlocks(function, nullptr, nullptr); + ConvertArgLocals(function).toTemps(); showMeTheCode(function, "After converting arguments to locals"); @@ -5178,6 +5419,7 @@ void Optimizer::run(QQmlEnginePrivate *qmlEngine, bool doTypeInference, bool pee } verifyNoPointerSharing(function); + mergeBasicBlocks(function, &defUses, &df); // Basic-block cycles that are unreachable (i.e. for loops in a then-part where the // condition is calculated to be always false) are not yet removed. This will choke the diff --git a/src/qml/compiler/qv4ssa_p.h b/src/qml/compiler/qv4ssa_p.h index 5d4b12e275..3a787f0347 100644 --- a/src/qml/compiler/qv4ssa_p.h +++ b/src/qml/compiler/qv4ssa_p.h @@ -75,7 +75,7 @@ public: bool covers(int position) const { return start <= position && position <= end; } }; - typedef QVector<Range> Ranges; + typedef QVarLengthArray<Range, 4> Ranges; private: Temp _temp; @@ -89,7 +89,7 @@ public: enum { InvalidPosition = -1 }; enum { InvalidRegister = -1 }; - explicit LifeTimeInterval(int rangeCapacity = 2) + explicit LifeTimeInterval(int rangeCapacity = 4) : _end(InvalidPosition) , _reg(InvalidRegister) , _isFixedInterval(0) @@ -146,6 +146,17 @@ public: } }; +inline bool LifeTimeInterval::lessThan(const LifeTimeInterval *r1, const LifeTimeInterval *r2) +{ + if (r1->_ranges.first().start == r2->_ranges.first().start) { + if (r1->isSplitFromInterval() == r2->isSplitFromInterval()) + return r1->_ranges.last().end < r2->_ranges.last().end; + else + return r1->isSplitFromInterval(); + } else + return r1->_ranges.first().start < r2->_ranges.first().start; +} + class LifeTimeIntervals { Q_DISABLE_COPY(LifeTimeIntervals) @@ -379,7 +390,7 @@ protected: _unhandled.removeLast(); } - s->accept(this); + visit(s); } if (IR::Jump *jump = s->asJump()) { @@ -402,7 +413,7 @@ protected: moves.order(); QList<IR::Move *> newMoves = moves.insertMoves(_currentBasicBlock, _function, true); foreach (IR::Move *move, newMoves) - move->accept(this); + visit(move); } } |